From 879ab9afdc0d87cc80d3c77c51bbb39c0de51e15 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 13 Mar 2025 19:10:07 +0200 Subject: [PATCH 001/190] feat(effects): Expose more gradient overlay options Expose blend mode, dither and method options for gradient overlays. --- src/helpers/effects.py | 6 +++--- src/schema/adobe.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index c6ef4f16..b55555ae 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -270,7 +270,7 @@ def apply_fx_gradient_overlay(action: ActionDescriptor, fx: EffectGradientOverla d5 = ActionDescriptor() color_list = ActionList() transparency_list = ActionList() - d1.putEnumerated(sID("mode"), sID("blendMode"), sID("normal")) + d1.putEnumerated(sID("mode"), sID("blendMode"), sID(fx.blend_mode)) d1.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) d2.putEnumerated(sID("gradientForm"), sID("gradientForm"), sID("customStops")) d2.putDouble(sID("interfaceIconFrameDimmed"), fx.size) @@ -295,8 +295,8 @@ def apply_fx_gradient_overlay(action: ActionDescriptor, fx: EffectGradientOverla d1.putUnitDouble(sID("angle"), sID("angleUnit"), fx.rotation) d1.putEnumerated(sID("type"), sID("gradientType"), sID("linear")) d1.putBoolean(sID("reverse"), False) - d1.putBoolean(sID("dither"), False) - d1.putEnumerated(cID("gs99"), sID("gradientInterpolationMethodType"), sID("classic")) + d1.putBoolean(sID("dither"), fx.dither) + d1.putEnumerated(cID("gs99"), sID("gradientInterpolationMethodType"), sID(fx.method)) d1.putBoolean(sID("align"), True) d1.putUnitDouble(sID("scale"), sID("percentUnit"), fx.scale) d5.putUnitDouble(sID("horizontal"), sID("percentUnit"), 0) diff --git a/src/schema/adobe.py b/src/schema/adobe.py index 196db2f8..ab93bd48 100644 --- a/src/schema/adobe.py +++ b/src/schema/adobe.py @@ -31,6 +31,37 @@ class LayerDimensions(Schema): * Layer Effects """ +BlendMode = Literal[ + "normal", + "dissolve", + "darken", + "multiply", + "colorBurn", + "linearBurn", + "darkerColor", + "lighten", + "screen", + "colorDodge", + "linearDodge", + "lighterColor", + "overlay", + "softLight", + "hardLight", + "vividLight", + "linearLight", + "pinLight", + "hardMix", + "difference", + "exclusion", + "subtract", + "divide", + "hue", + "saturation", + "color", + "luminosity", +] +GradientMethod = Literal["perceptual", "linear", "classic", "smooth", "stripes"] + class EffectBevel(ArbitrarySchema): """Layer Effect: Bevel""" @@ -66,10 +97,13 @@ class EffectDropShadow(ArbitrarySchema): class EffectGradientOverlay(ArbitrarySchema): """Layer Effect: Drop Shadow""" colors: list[GradientColor] = [] + blend_mode: BlendMode = "normal" + dither: bool = False opacity: int | float = 100 rotation: int | float = 45 scale: int | float = 70 size: int | float = 4096 + method: GradientMethod = "classic" class EffectStroke(ArbitrarySchema): From eeceb735c0e36182cd746156ff53a0bf0d3cff18 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 13 Mar 2025 19:16:15 +0200 Subject: [PATCH 002/190] fix(saga.py): Fix Saga layout check Adds a missing cached_property decorator to Saga layout check. --- src/templates/saga.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templates/saga.py b/src/templates/saga.py index 769ee1a2..41c7a2c1 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -46,6 +46,7 @@ def __init__(self, layout: SagaLayout, **kwargs): * Layout Checks """ + @cached_property def is_layout_saga(self) -> bool: """Checks whether the card matches SagaLayout.""" return isinstance(self.layout, SagaLayout) From 09bdfdd44035f9517acacdc71c3aaed98077ad53 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 13 Mar 2025 19:23:01 +0200 Subject: [PATCH 003/190] feat(colors.py): Allow generating pinline gradients for 5 and more colors Generalizes the way pinline gradient definitions are constructed, which enables using an arbitrary amount of colors. --- src/helpers/colors.py | 86 ++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 59 deletions(-) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index 33cdd13a..5b59ee13 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -2,7 +2,7 @@ * Helpers: Colors """ # Standard Library Imports -from typing import Union, Optional +from typing import TypedDict, Union # Third Party Imports from photoshop.api import SolidColor, DialogModes, ActionList, ActionDescriptor, ColorModel, LayerKind @@ -185,11 +185,17 @@ def get_text_item_color(item: TextItem) -> SolidColor: return rgb_black() +class GradientConfig(TypedDict): + color: ColorObject + location: int | float + midpoint: int | float + + def get_pinline_gradient( colors: str, - color_map: Optional[dict] = None, - location_map: dict = None -) -> Union[list[int], list[dict]]: + color_map: dict[str, ColorObject] | None = None, + location_map: dict[int, list[Union[int, float]]] | None = None +) -> ColorObject | list[ColorObject] | list[GradientConfig]: """Return a gradient color list notation for some given pinline colors. Args: @@ -213,63 +219,25 @@ def get_pinline_gradient( return color_map.get('Artifact', [0, 0, 0]) if len(colors) == 1: return color_map.get(colors, [0, 0, 0]) - if len(colors) == 2: - return [ - { - 'color': color_map.get(colors[0], [0, 0, 0]), - 'location': location_map[2][0] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[1], [0, 0, 0]), - 'location': location_map[2][1] * 4096, 'midpoint': 50, - } - ] - if len(colors) == 3: - return [ - { - 'color': color_map.get(colors[0], [0, 0, 0]), - 'location': location_map[3][0] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[1], [0, 0, 0]), - 'location': location_map[3][1] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[1], [0, 0, 0]), - 'location': location_map[3][2] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[2], [0, 0, 0]), - 'location': location_map[3][3] * 4096, 'midpoint': 50 - } - ] - if len(colors) == 4 and colors not in [LAYERS.LAND, LAYERS.GOLD]: + + if (len_colors := len(colors)) > 1 and colors not in [LAYERS.LAND, LAYERS.GOLD]: return [ - { - 'color': color_map.get(colors[0], [0, 0, 0]), - 'location': location_map[4][0] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[1], [0, 0, 0]), - 'location': location_map[4][1] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[1], [0, 0, 0]), - 'location': location_map[4][2] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[2], [0, 0, 0]), - 'location': location_map[4][3] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[2], [0, 0, 0]), - 'location': location_map[4][4] * 4096, 'midpoint': 50 - }, - { - 'color': color_map.get(colors[3], [0, 0, 0]), - 'location': location_map[4][5] * 4096, 'midpoint': 50 - } + conf + for i in range(len_colors - 1) + for conf in [ + { + "color": color_map.get(colors[i], [0, 0, 0]), + "location": location_map[len_colors][2 * i] * 4096, + "midpoint": 50, + }, + { + "color": color_map.get(colors[i + 1], [0, 0, 0]), + "location": location_map[len_colors][2 * i + 1] * 4096, + "midpoint": 50, + }, + ] ] + return color_map.get(colors, [0, 0, 0]) From 7e54af717699d6fba7f35807084b06b0d747b4ca Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 13 Mar 2025 19:26:13 +0200 Subject: [PATCH 004/190] fix(cast): Remove erroneous type casts --- src/templates/_cosmetic.py | 4 ++-- src/templates/normal.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/templates/_cosmetic.py b/src/templates/_cosmetic.py index b87fbfab..f50f6445 100644 --- a/src/templates/_cosmetic.py +++ b/src/templates/_cosmetic.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from functools import cached_property -from typing import Optional, Callable, Union, cast +from typing import Optional, Callable, Union # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -142,7 +142,7 @@ def background_group(self) -> Optional[LayerSet]: if self.is_nyx: if layer := psd.getLayerSet(LAYERS.NYX): return layer - return cast(super().background_group, Optional[LayerSet]) + return super().background_group class CompanionMod (BaseTemplate): diff --git a/src/templates/normal.py b/src/templates/normal.py index 1e76b5af..6a8fbef2 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from functools import cached_property -from typing import Optional, Union, cast +from typing import Optional, Union # Third Party Imports from photoshop.api import AnchorPosition, SolidColor @@ -908,7 +908,7 @@ def background_group(self) -> Optional[LayerSet]: """Optional[LayerSet]: No background for 'Colorless' cards.""" if self.is_colorless: return - return cast(super().background_group, Optional[LayerSet]) + return super().background_group """ * Masks @@ -1502,7 +1502,7 @@ def pt_group(self) -> Optional[LayerSet]: return if self.is_textless and self.is_pt_enabled: return psd.getLayerSet(f'{LAYERS.PT_BOX} {LAYERS.TEXTLESS}') - return cast(super().pt_group, Optional[LayerSet]) + return super().pt_group @cached_property def crown_group(self) -> LayerSet: @@ -1514,7 +1514,7 @@ def textbox_group(self) -> Optional[LayerSet]: """Optional[LayerSet]: Textbox group if not a 'Textless' render.""" if self.is_textless: return - return cast(super().textbox_group, Optional[LayerSet]) + return super().textbox_group """ * Text Layers From 1af75244694ea0c6227e3807ede1f35bbb2887a2 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 12 Apr 2025 16:51:26 +0300 Subject: [PATCH 005/190] feat(process_card_data): Assign Saga layout to Saga Creature cards --- src/cards.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cards.py b/src/cards.py index 8cd9db0a..3ccea5d7 100644 --- a/src/cards.py +++ b/src/cards.py @@ -208,11 +208,17 @@ def process_card_data(data: dict, card: CardDetails) -> dict: if 'Mutate' in data.get('keywords', []): data['layout'] = 'mutate' return data + + type_line = data.get('type_line', '') # Add Planeswalker layout - if 'Planeswalker' in data.get('type_line', ''): + if 'Planeswalker' in type_line: data['layout'] = 'planeswalker' return data + + # Check for Saga Creature layout + if 'Saga' in type_line and 'Creature' in type_line: + data['layout'] = 'saga' # Return updated data return data From f0a753e0ecb69f84372a313dcaffa39985e408de Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 13 Apr 2025 17:37:05 +0300 Subject: [PATCH 006/190] feat(CaseLayout): Add support for rendering Case cards --- src/enums/layers.py | 3 + src/enums/mtg.py | 4 + src/helpers/position.py | 6 +- src/layouts.py | 11 +++ src/templates/case.py | 162 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/templates/case.py diff --git a/src/enums/layers.py b/src/enums/layers.py index 816e9922..96bf7e86 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -172,6 +172,9 @@ class LAYERS (StrConstant): BANNER = 'Banner' STRIPE = 'Stripe' + # Case + CASE = 'Case' + # Class CLASS = 'Class' STAGE = 'Stage' diff --git a/src/enums/mtg.py b/src/enums/mtg.py index a65c8955..b6fdf506 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -17,6 +17,7 @@ class LayoutCategory(StrConstant): """Card layout category, broad naming used for displaying on GUI elements.""" Adventure = 'Adventure' Battle = 'Battle' + Case = 'Case' Class = 'Class' Leveler = 'Leveler' MDFC = 'MDFC' @@ -37,6 +38,7 @@ class LayoutType(StrConstant): """Card layout type, fine-grained naming separated by front/back where applicable.""" Adventure = 'adventure' Battle = 'battle' + Case = 'case' Class = 'class' Leveler = 'leveler' MDFCBack = 'mdfc_back' @@ -65,6 +67,7 @@ class LayoutScryfall(StrConstant): MDFC = 'modal_dfc' Meld = 'meld' Leveler = 'leveler' + Case = 'case' Class = 'class' Saga = 'saga' Adventure = 'adventure' @@ -97,6 +100,7 @@ class LayoutScryfall(StrConstant): LayoutCategory.PlaneswalkerMDFC: [LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack], LayoutCategory.PlaneswalkerTransform: [LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack], LayoutCategory.Saga: [LayoutType.Saga], + LayoutCategory.Case: [LayoutType.Case], LayoutCategory.Class: [LayoutType.Class], LayoutCategory.Mutate: [LayoutType.Mutate], LayoutCategory.Prototype: [LayoutType.Prototype], diff --git a/src/helpers/position.py b/src/helpers/position.py index 782843d1..e4802df2 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -3,7 +3,7 @@ """ # Standard Library Imports import math -from typing import Optional, Union +from typing import Optional, Sequence, Union # Third Party Imports from photoshop.api import DialogModes, AnchorPosition @@ -157,8 +157,8 @@ def position_between_layers( def position_dividers( - dividers: list[Union[ArtLayer, LayerSet]], - layers: list[Union[ArtLayer, LayerSet]], + dividers: Sequence[ArtLayer | LayerSet], + layers: Sequence[ArtLayer | LayerSet], docref: Optional[Document] = None ) -> None: """Positions a list of dividers between a list of layers. diff --git a/src/layouts.py b/src/layouts.py index 19705915..ee0e9728 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1216,6 +1216,16 @@ def class_lines(self) -> list[dict]: # Otherwise add line to the previous ability abilities[-1]['text'] += f'\n{line}' return abilities + + +class CaseLayout(NormalLayout): + """Case card layout, introduced in Murders at Karlov Manor.""" + card_class: str = LayoutType.Case + + @cached_property + def case_lines(self) -> list[str]: + """Split Case text into sections.""" + return self.oracle_text.split("\n") class BattleLayout(TransformLayout): @@ -1561,6 +1571,7 @@ def card_count(self) -> Optional[int]: LayoutScryfall.MDFC: ModalDoubleFacedLayout, LayoutScryfall.Meld: TransformLayout, LayoutScryfall.Leveler: LevelerLayout, + LayoutScryfall.Case: CaseLayout, LayoutScryfall.Class: ClassLayout, LayoutScryfall.Saga: SagaLayout, LayoutScryfall.Adventure: AdventureLayout, diff --git a/src/templates/case.py b/src/templates/case.py new file mode 100644 index 00000000..2d7787f2 --- /dev/null +++ b/src/templates/case.py @@ -0,0 +1,162 @@ +from functools import cached_property +from typing import Callable + +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet + +from src.enums.layers import LAYERS +from src.helpers.bounds import get_layer_height +from src.helpers.layers import getLayer, getLayerSet +from src.helpers.position import position_dividers, spread_layers_over_reference +from src.helpers.text import scale_text_layers_to_height +from src.layouts import CaseLayout +from src.templates._core import NormalTemplate +from src.text_layers import FormattedTextField + + +class CaseMod(NormalTemplate): + """ + * A template modifier for Case cards introduced in Murders at Karlov Manor. + + Adds: + * Evenly spaced ability sections and dividers. + """ + + def __init__(self, layout: CaseLayout, **kwargs: None): + self.line_layers: list[ArtLayer] = [] + self.divider_layers: list[ArtLayer] = [] + super().__init__(layout, **kwargs) + + """ + * Checks + """ + + @cached_property + def is_case_layout(self) -> bool: + """bool: Checks if this card uses Case layout.""" + return isinstance(self.layout, CaseLayout) + + """ + * Mixin Methods + """ + + @cached_property + def text_layer_methods(self) -> list[Callable[[], None]]: + """Add Case text layers.""" + funcs = [self.text_layers_case] if self.is_case_layout else [] + return [*super().text_layer_methods, *funcs] + + @cached_property + def frame_layer_methods(self) -> list[Callable[[], None]]: + """Add Case frame layers.""" + funcs = [self.frame_layers_case] if self.is_case_layout else [] + return [*super().frame_layer_methods, *funcs] + + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: + """Position Case abilities and dividers.""" + funcs = [self.layer_positioning_case] if self.is_case_layout else [] + return [*super().post_text_methods, *funcs] + + """ + * Groups + """ + + @cached_property + def case_group(self) -> LayerSet | None: + return getLayerSet(LAYERS.CASE) + + """ + * Text Layers + """ + + @cached_property + def text_layer_ability(self) -> ArtLayer | None: + return getLayer(LAYERS.TEXT, self.case_group) + + @cached_property + def case_ability_divider(self) -> ArtLayer | None: + return getLayer(LAYERS.DIVIDER, self.case_group) + + """ + * Layer Methods + """ + + def rules_text_and_pt_layers(self) -> None: + if self.is_case_layout and not self.is_creature: + return + return super().rules_text_and_pt_layers() + + """ + * Text Layer Methods + """ + + def text_layers_case(self) -> None: + """Add and modify text layers relating to Case type cards.""" + + skip_divider_for = len(self.layout.case_lines) - 1 + + # Add text fields for each line + for i, line in enumerate(self.layout.case_lines): + # Create a new ability line + if layer := self.text_layer_ability: + line_layer: ArtLayer = layer if i == 0 else layer.duplicate() + self.line_layers.append(line_layer) + self.text.append(FormattedTextField(layer=line_layer, contents=line)) + + # Use existing ability divider or create a new one + if i != skip_divider_for and (layer := self.case_ability_divider): + divider: ArtLayer = ( + self.case_ability_divider + if i == 0 + else self.case_ability_divider.duplicate() + ) + self.divider_layers.append(divider) + + """ + * Frame Layer Methods + """ + + def frame_layers_case(self) -> None: + """Enable frame layers required by Case cards. None by default.""" + pass + + """ + * Positioning Methods + """ + + def layer_positioning_case(self) -> None: + """Positions and sizes Case ability layers and dividers.""" + + # Core vars + spacing = self.app.scale_by_dpi(80) + spaces = len(self.line_layers) + divider_height = get_layer_height(self.divider_layers[0]) + ref_height: float | int = self.textbox_reference.dims["height"] + spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) + total_height = ref_height - spacing_total + + # Resize text items till they fit in the available space + scale_text_layers_to_height( + text_layers=self.line_layers, ref_height=total_height + ) + + # Get the exact gap between each layer left over + layer_heights = sum([get_layer_height(lyr) for lyr in self.line_layers]) + gap = (ref_height - layer_heights) * (spacing / spacing_total) + inside_gap = (ref_height - layer_heights) * ( + (spacing + divider_height) / spacing_total + ) + + # Space lines evenly apart + spread_layers_over_reference( + layers=self.line_layers, + ref=self.textbox_reference, + gap=gap, + inside_gap=inside_gap, + ) + + # Position a divider between each ability line + position_dividers( + dividers=self.divider_layers, layers=self.line_layers, docref=self.docref + ) From 1c80a612d094cfe323548da89b9ec7a717c3417b Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 14 Apr 2025 18:54:30 +0300 Subject: [PATCH 007/190] fix(update_template): Unpack 7zipped template updates after download --- src/_loader.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/_loader.py b/src/_loader.py index 919df483..687fa7f8 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -11,11 +11,12 @@ from functools import cached_property import os from pathlib import Path -from traceback import print_tb +from traceback import format_exc, print_tb from types import ModuleType from typing import Optional, TypedDict, NotRequired, Any, Callable, Union # Third Party Imports +from py7zr import SevenZipFile import yarl from omnitils.api.gdrive import gdrive_get_metadata, gdrive_download_file from omnitils.files import load_data_file, ensure_file, mkdir_full_perms @@ -1073,13 +1074,20 @@ def update_template(self, callback: Callable) -> bool: url=self.url_amazon, path=self.path_download, callback=callback) + + # If downloaded file is a 7z archive, extract the template from it + if self.path_download.suffix == ".7z": + with SevenZipFile(self.path_7z, "r") as archive: + root = self.plugin.path_templates if self.plugin else PATH.TEMPLATES + archive.extractall(root) + self.path_7z.unlink() # Return result status return result # Exception caught while downloading / unpacking except Exception as e: - print(e) + print(f"Failed to update template: {self.name}", e, format_exc()) return False def mark_updated(self) -> None: From 953778e24a254200d15509e7112dffb1a3d3acab Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 16 Apr 2025 19:15:45 +0300 Subject: [PATCH 008/190] fix(CaseMod): Allow case templates not to use a text divider --- src/templates/case.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/templates/case.py b/src/templates/case.py index 2d7787f2..f607a513 100644 --- a/src/templates/case.py +++ b/src/templates/case.py @@ -131,7 +131,11 @@ def layer_positioning_case(self) -> None: # Core vars spacing = self.app.scale_by_dpi(80) spaces = len(self.line_layers) - divider_height = get_layer_height(self.divider_layers[0]) + divider_height = ( + get_layer_height(self.divider_layers[0]) + if len(self.divider_layers) > 0 + else 0 + ) ref_height: float | int = self.textbox_reference.dims["height"] spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) total_height = ref_height - spacing_total @@ -157,6 +161,9 @@ def layer_positioning_case(self) -> None: ) # Position a divider between each ability line - position_dividers( - dividers=self.divider_layers, layers=self.line_layers, docref=self.docref - ) + if len(self.divider_layers) == len(self.line_layers) - 1: + position_dividers( + dividers=self.divider_layers, + layers=self.line_layers, + docref=self.docref, + ) From 75431aa71712f5c660e8b983426dd44baadacb5f Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 19 Apr 2025 14:36:49 +0300 Subject: [PATCH 009/190] feat(scale_text_layers_to_height): Allow finer control of text scaling increments --- src/helpers/text.py | 48 +++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/helpers/text.py b/src/helpers/text.py index ea95e890..fc6fdd03 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -2,7 +2,7 @@ * Helpers: Text Items """ # Standard Library Imports -from typing import Union, Optional, Any +from typing import Sequence, Union, Optional, Any # Third Party Imports from photoshop.api import ( @@ -764,7 +764,7 @@ def scale_text_layers_to_height( text_layers: list[ArtLayer], ref_height: Union[int, float], font_size: Optional[float] = None, - step: float = 0.4 + step_sizes: Sequence[float] | None = None ) -> Optional[float]: """Scale multiple text layers until they all can fit within the same given height dimension. @@ -772,8 +772,10 @@ def scale_text_layers_to_height( text_layers: List of TextLayers to check. ref_height: Height to fit inside. font_size: Starting font size of the text layers, calculated if not provided. - step: Points to step down the text layers to fit. + step_sizes: Descending sequence, which determines the increments used for text scaling. """ + step_sizes = step_sizes or (0.4, 0.2) + # Check initial fit total_layer_height = sum([get_layer_height(layer) for layer in text_layers]) if total_layer_height <= ref_height: @@ -782,26 +784,30 @@ def scale_text_layers_to_height( # Establish font size if font_size is None: font_size = get_font_size(text_layers[0]) - half_step = step / 2 - # Compare height of all 3 elements vs total reference height - while total_layer_height > ref_height: - total_layer_height = 0 - font_size -= step - for i, layer in enumerate(text_layers): - set_text_size_and_leading(layer, font_size, font_size) - total_layer_height += get_layer_height(layer) + uneven_round = False + # Adjust text size down and up in decreasing steps + for idx, step_size in enumerate(step_sizes): + uneven_round = bool(idx % 2) + + # Compare height of all 3 elements vs total reference height + while total_layer_height < ref_height if uneven_round else total_layer_height > ref_height: + total_layer_height = 0 + + if uneven_round: + font_size += step_size + else: + font_size -= step_size - # Increase by a half step - font_size += half_step - total_layer_height = 0 - for i, layer in enumerate(text_layers): - set_text_size_and_leading(layer, font_size, font_size) - total_layer_height += get_layer_height(layer) + for layer in text_layers: + set_text_size_and_leading(layer, font_size, font_size) + total_layer_height += get_layer_height(layer) - # If out of bounds, revert half step - if total_layer_height > ref_height: - font_size -= half_step - for i, layer in enumerate(text_layers): + # If the last round was uneven we have to go one step down, + # because we are over the reference height + if uneven_round: + font_size -= step_sizes[-1] + for layer in text_layers: set_text_size_and_leading(layer, font_size, font_size) + return font_size From c2f6a3f92f349ac07b7e5d5b11e8f5ff1c405548 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 19 Apr 2025 14:39:02 +0300 Subject: [PATCH 010/190] feat(PlaneswalkerMod): Allow inheriting Planeswalker templates to more easily affect ability text spacing --- src/templates/planeswalker.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/templates/planeswalker.py b/src/templates/planeswalker.py index a5c6c204..f10366fb 100644 --- a/src/templates/planeswalker.py +++ b/src/templates/planeswalker.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from functools import cached_property -from typing import Optional, Callable +from typing import Optional, Callable, Sequence # Third Party Imports from photoshop.api import ElementPlacement, ColorBlendMode @@ -69,6 +69,14 @@ def art_frame_vertical(self): if self.is_colorless: return LAYERS.BORDERLESS_FRAME return LAYERS.FULL_ART_FRAME + + @cached_property + def ability_text_spacing(self) -> float | int: + return 64 + + @cached_property + def ability_text_scaling_step_sizes(self) -> Sequence[float] | None: + return None """ * Planeswalker Details @@ -240,14 +248,15 @@ def pw_layer_positioning(self) -> None: """Position Planeswalker ability layers and icons.""" # Auto-position the ability text, colons, and shields. - spacing = self.app.scale_by_dpi(64) + spacing = self.app.scale_by_dpi(self.ability_text_spacing) spaces = len(self.ability_layers) + 1 total_height = self.textbox_reference.dims['height'] - (spacing * spaces) # Resize text items till they fit in the available space font_size = scale_text_layers_to_height( text_layers=self.ability_layers, - ref_height=total_height) + ref_height=total_height, + step_sizes=self.ability_text_scaling_step_sizes) # Space abilities evenly apart uniform_gap = True if len(self.ability_layers) < 3 or not self.layout.loyalty else False From 6dd7b7b2a1abfcac94d542e18d85cab8e93684ed Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 21 Apr 2025 11:36:41 +0300 Subject: [PATCH 011/190] fix(get_pinline_gradient): Fix getting Artifact pinline gradient --- src/helpers/colors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index 5b59ee13..369f1606 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -220,7 +220,11 @@ def get_pinline_gradient( if len(colors) == 1: return color_map.get(colors, [0, 0, 0]) - if (len_colors := len(colors)) > 1 and colors not in [LAYERS.LAND, LAYERS.GOLD]: + if (len_colors := len(colors)) > 1 and colors not in [ + LAYERS.ARTIFACT, + LAYERS.LAND, + LAYERS.GOLD, + ]: return [ conf for i in range(len_colors - 1) From d5538678a3bbd876ba9b4f5366796a0ed94abdf5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 27 Apr 2025 16:12:09 +0300 Subject: [PATCH 012/190] fix(SagaLayout): Fix SagaLayout text when reminder text removal is active SagaLayout text processing depends on the number of leading lines, but it doesn't take into account that the first line might have been removed as a result of reminder text removal. This commit aims to fix that. --- src/layouts.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index 19705915..c15d74e7 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1144,12 +1144,16 @@ def is_transform(self) -> bool: @cached_property def saga_text(self) -> str: """Text comprised of saga ability lines.""" - return strip_lines(self.oracle_text, 1) + return ( + self.oracle_text + if CFG.remove_reminder + else strip_lines(self.oracle_text, 1) + ) @cached_property def saga_description(self) -> str: """Description at the top of the Saga card""" - return get_line(self.oracle_text, 0) + return "" if CFG.remove_reminder else get_line(self.oracle_text, 0) @cached_property def saga_lines(self) -> list[dict]: From 4cee51f562097853ecf528d3f8ac7d651e09c243 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 5 May 2025 18:15:44 +0300 Subject: [PATCH 013/190] fix(ClassLayout): Fix ClassLayout text when reminder text removal is active --- src/layouts.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index c15d74e7..dafca58e 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1191,12 +1191,16 @@ class ClassLayout(NormalLayout): @cached_property def class_text(self) -> str: """Text comprised of class ability lines.""" - return strip_lines(self.oracle_text, 1) + return ( + self.oracle_text + if CFG.remove_reminder + else strip_lines(self.oracle_text, 1) + ) @cached_property def class_description(self) -> str: """Description at the top of the Class card.""" - return get_line(self.oracle_text, 0) + return "" if CFG.remove_reminder else get_line(self.oracle_text, 0) @cached_property def class_lines(self) -> list[dict]: From 0914a7300a055d8079e0af207edcc33b634e264a Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 11 May 2025 13:42:12 +0300 Subject: [PATCH 014/190] feat(SagaLayout): Separeta non-chapter ability text for Saga creatures --- src/layouts.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index dafca58e..5c9b8e82 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -3,6 +3,7 @@ """ # Standard Library Imports from datetime import date, datetime +import re from typing import Optional, Match, Union, Type, ForwardRef from os import path as osp from pathlib import Path @@ -1128,6 +1129,8 @@ class SagaLayout(NormalLayout): """Saga card layout, introduced in Dominaria.""" card_class: str = LayoutType.Saga + _chapter_regex = re.compile(r"^[ ,IVXLCDM]+ — ") + """ * Bool Properties """ @@ -1143,12 +1146,30 @@ def is_transform(self) -> bool: @cached_property def saga_text(self) -> str: - """Text comprised of saga ability lines.""" - return ( - self.oracle_text - if CFG.remove_reminder - else strip_lines(self.oracle_text, 1) + """Text comprised of Saga ability lines.""" + return "\n".join( + ( + text + for text in self.oracle_text.splitlines() + if self._chapter_regex.match(text) + ) ) + + @cached_property + def ability_text(self) -> str: + """ + Rules text that's separate from the Saga chapters. + Seen e.g. in Saga creatures. + """ + # Drop reminder text or first chapter text. Which was dropped + # doesn't matter as long as there's at least one chapter. + stripped = strip_lines(self.oracle_text, 1) + # Return all text that comes after chapter lines. + lines = stripped.splitlines() + for idx, line in enumerate(lines): + if not self._chapter_regex.match(line): + return "\n".join(lines[idx:]) + return "" @cached_property def saga_description(self) -> str: From eb9f1eaae7d3eb59857b2553610fd143de221702 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 16 May 2025 19:39:58 +0300 Subject: [PATCH 015/190] fix(SagaLayout): Prevent chapter abilities from ending up in saga_description Not all Saga cards have a reminder description as the first line, which broke the existing Saga text parsing. --- src/layouts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index 5c9b8e82..b0871319 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1174,7 +1174,10 @@ def ability_text(self) -> str: @cached_property def saga_description(self) -> str: """Description at the top of the Saga card""" - return "" if CFG.remove_reminder else get_line(self.oracle_text, 0) + if CFG.remove_reminder: + return "" + line = get_line(self.oracle_text, 0) + return "" if self._chapter_regex.match(line) else line @cached_property def saga_lines(self) -> list[dict]: From 5c8f61b301abdbd50d5c2c0c3d07f4cf1457087f Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 17 May 2025 14:38:08 +0300 Subject: [PATCH 016/190] fix(Case): Import Case templates in the template module's __init__.py --- src/templates/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templates/__init__.py b/src/templates/__init__.py index 42fc6b49..6db0dbb3 100644 --- a/src/templates/__init__.py +++ b/src/templates/__init__.py @@ -13,6 +13,7 @@ from src.templates.saga import * from src.templates.token import * from src.templates.mutate import * +from src.templates.case import * from src.templates.classes import * from src.templates.battle import * from src.templates.prototype import * From 71ba5355c4b95227b480f1ba8ea0f50ef5d4583c Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 17 May 2025 18:01:23 +0300 Subject: [PATCH 017/190] fix(get_pinline_gradient): Fix lookup of color keywords from color map --- src/helpers/colors.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index 369f1606..f3564f1d 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -216,15 +216,12 @@ def get_pinline_gradient( # Return our colors if not colors: - return color_map.get('Artifact', [0, 0, 0]) - if len(colors) == 1: - return color_map.get(colors, [0, 0, 0]) - - if (len_colors := len(colors)) > 1 and colors not in [ - LAYERS.ARTIFACT, - LAYERS.LAND, - LAYERS.GOLD, - ]: + return color_map.get(LAYERS.ARTIFACT, [0, 0, 0]) + + if colors in color_map: + return color_map[colors] + + if (len_colors := len(colors)) > 1: return [ conf for i in range(len_colors - 1) @@ -242,7 +239,7 @@ def get_pinline_gradient( ] ] - return color_map.get(colors, [0, 0, 0]) + return [0, 0, 0] """ From 572e15733903d36e5df827c1b2120148825662e3 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 24 May 2025 18:11:01 +0300 Subject: [PATCH 018/190] feat(LevelerMod): Improve LevelerMod compatibility with other Mod classes --- src/templates/leveler.py | 58 +++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/templates/leveler.py b/src/templates/leveler.py index ebb3b1e4..b8dab13c 100644 --- a/src/templates/leveler.py +++ b/src/templates/leveler.py @@ -15,6 +15,7 @@ from src.layouts import LevelerLayout from src.templates._core import NormalTemplate import src.text_layers as text_classes +from src.utils.adobe import ReferenceLayer """ * Modifier Classes @@ -31,6 +32,14 @@ class LevelerMod(NormalTemplate): * Level requirements for second and third stage. """ + """ + * Checks + """ + + @cached_property + def is_leveler(self) -> bool: + return isinstance(self.layout, LevelerLayout) + """ * Mixin Methods """ @@ -38,7 +47,7 @@ class LevelerMod(NormalTemplate): @cached_property def text_layer_methods(self) -> list[Callable]: """Add Adventure text layers.""" - funcs = [self.text_layers_leveler] if isinstance(self.layout, LevelerLayout) else [] + funcs = [self.text_layers_leveler] if self.is_leveler else [] return [*super().text_layer_methods, *funcs] """ @@ -56,7 +65,9 @@ def leveler_group(self) -> Optional[LayerSet]: @cached_property def pt_layer(self) -> Optional[ArtLayer]: - return psd.getLayer(self.twins, LAYERS.PT_AND_LEVEL_BOXES) + if self.is_leveler: + return psd.getLayer(self.twins, LAYERS.PT_AND_LEVEL_BOXES) + return super().pt_layer """ * Text Layers @@ -64,11 +75,15 @@ def pt_layer(self) -> Optional[ArtLayer]: @cached_property def text_layer_rules(self) -> Optional[ArtLayer]: - return psd.getLayer("Rules Text - Level Up", self.leveler_group) + if self.is_leveler: + return psd.getLayer("Rules Text - Level Up", self.leveler_group) + return super().text_layer_rules @cached_property def text_layer_pt(self) -> Optional[ArtLayer]: - return psd.getLayer("Top Power / Toughness", self.leveler_group) + if self.is_leveler: + return psd.getLayer("Top Power / Toughness", self.leveler_group) + return super().text_layer_pt """ * Leveler Text Layers @@ -103,8 +118,10 @@ def text_layer_pt_bottom(self) -> Optional[ArtLayer]: """ @cached_property - def textbox_reference(self) -> Optional[ArtLayer]: - return psd.get_reference_layer(f'{LAYERS.TEXTBOX_REFERENCE} - Level Text', self.leveler_group) + def textbox_reference(self) -> Optional[ReferenceLayer]: + if self.is_leveler: + return psd.get_reference_layer(f'{LAYERS.TEXTBOX_REFERENCE} - Level Text', self.leveler_group) + return super().textbox_reference """ * Leveler References @@ -124,19 +141,22 @@ def textbox_reference_z(self) -> Optional[ArtLayer]: def rules_text_and_pt_layers(self) -> None: """Add rules and power/toughness text.""" - - # Level-Up text and starting P/T - self.text.extend([ - text_classes.FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.level_up_text, - reference=self.textbox_reference - ), - text_classes.TextField( - layer=self.text_layer_pt, - contents=str(self.layout.power) + "/" + str(self.layout.toughness) - ) - ]) + + if self.is_leveler: + # Level-Up text and starting P/T + self.text.extend([ + text_classes.FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.level_up_text, + reference=self.textbox_reference + ), + text_classes.TextField( + layer=self.text_layer_pt, + contents=str(self.layout.power) + "/" + str(self.layout.toughness) + ) + ]) + else: + return super().rules_text_and_pt_layers() def text_layers_leveler(self): """Add and modify text layers required by Leveler cards.""" From a81790fe372650680fadb1d7ffa2d3bda58fbfa3 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 31 May 2025 19:05:44 +0300 Subject: [PATCH 019/190] refactor(Split): Refactor Split template related code for type safe inheritance --- src/enums/mtg.py | 1 + src/layouts.py | 167 ++++++-- src/templates/split.py | 940 ++++++++++++++++++++++++----------------- 3 files changed, 668 insertions(+), 440 deletions(-) diff --git a/src/enums/mtg.py b/src/enums/mtg.py index a65c8955..b6522c27 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -374,6 +374,7 @@ class CardTextPatterns: # Text - Reminder TEXT_REMINDER: re.Pattern = re.compile(r"\([^()]*\)") + TEXT_REMINDER_ENDING = re.compile(r"[\s\S]*(\([^()]*\))$", ) # Text - Italicised Ability TEXT_ABILITY: re.Pattern = re.compile(r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE) diff --git a/src/layouts.py b/src/layouts.py index 19705915..3a40bd63 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1249,7 +1249,6 @@ class SplitLayout(NormalLayout): is_creature: bool = False is_legendary: bool = False is_companion: bool = False - is_colorless: bool = False toughness: str = '' power: str = '' @@ -1263,26 +1262,34 @@ def __str__(self): """ @cached_property - def art_file(self) -> list[Path]: + def art_file(self) -> Path: + return self.art_files[0] + + @cached_property + def art_files(self) -> list[Path]: """list[Path]: Two image files, second is appended during render process.""" - return [self.file['file']] + return [Path(self.file['file'])] @cached_property def display_name(self) -> str: """Both side names.""" - return f"{self.name[0]} // {self.name[1]}" + return f"{self.names[0]} // {self.names[1]}" + + @cached_property + def card(self) -> dict: + return self.cards[0] @cached_property - def card(self) -> list[dict]: + def cards(self) -> list[dict]: """Both side objects.""" - return [c for c in self.scryfall.get('card_faces', [])] + return [*self.scryfall.get('card_faces', [])] """ * Colors """ @cached_property - def color_identity(self) -> list: + def color_identity(self) -> list[str]: """Color identity is shared by both halves, use raw Scryfall instead of 'card' data.""" return self.scryfall.get('color_identity', []) @@ -1325,10 +1332,14 @@ def artist(self) -> str: """ @cached_property - def watermark(self) -> list[Optional[str]]: + def watermark(self) -> str | None: + return self.watermarks[0] + + @cached_property + def watermarks(self) -> list[str | None]: """Name of the card's watermark file that is actually used, if provided.""" watermarks: list[Optional[str]] = [] - for wm in self.watermark_svg: + for wm in self.watermark_svgs: if not wm: watermarks.append(None) elif wm.stem.upper() == 'WM': @@ -1338,12 +1349,20 @@ def watermark(self) -> list[Optional[str]]: return watermarks @cached_property - def watermark_raw(self) -> list[Optional[str]]: + def watermark_raw(self) -> str | None: + return self.watermarks_raw[0] + + @cached_property + def watermarks_raw(self) -> list[str | None]: """Name of the card's watermark from raw Scryfall data, if provided.""" - return [c.get('watermark', '') for c in self.card] + return [c.get('watermark', '') for c in self.cards] + + @cached_property + def watermark_svg(self) -> Path | None: + return self.watermark_svgs[0] @cached_property - def watermark_svg(self) -> list[Optional[Path]]: + def watermark_svgs(self) -> list[Path | None]: """Path to the watermark SVG file, if provided.""" def _find_watermark_svg(wm: str) -> Optional[Path]: """Try to find a watermark SVG asset, allowing for special cases and set code fallbacks. @@ -1372,7 +1391,7 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: # Find a watermark SVG for each face watermarks = [] - for w in self.watermark_raw: + for w in self.watermarks_raw: if CFG.watermark_mode == WatermarkMode.Disabled: # Disabled Mode watermarks.append(None) @@ -1400,90 +1419,154 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: """ @cached_property - def name(self) -> list[str]: + def name(self) -> str: + return self.names[0] + + @cached_property + def names(self) -> list[str]: """Both side names.""" if self.is_alt_lang: - return [c.get('printed_name', c.get('name', '')) for c in self.card] - return [c.get('name', '') for c in self.card] + return [c.get('printed_name', c.get('name', '')) for c in self.cards] + return [c.get('name', '') for c in self.cards] @cached_property def name_raw(self) -> str: """Sanitized card name to use for rendered image file.""" return f"{self.name[0]} _ {self.name[1]}" + + @cached_property + def type_line(self) -> str: + return self.type_lines[0] @cached_property - def type_line(self) -> list[str]: + def type_lines(self) -> list[str]: """Both side type lines.""" if self.is_alt_lang: - return [c.get('printed_type_line', c.get('type_line', '')) for c in self.card] - return [c.get('type_line', '') for c in self.card] + return [c.get('printed_type_line', c.get('type_line', '')) for c in self.cards] + return [c.get('type_line', '') for c in self.cards] + + @cached_property + def mana_cost(self) -> str: + return self.mana_costs[0] @cached_property - def mana_cost(self) -> list[str]: + def mana_costs(self) -> list[str]: """Both side mana costs.""" - return [c.get('mana_cost', '') for c in self.card] + return [c.get('mana_cost', '') for c in self.cards] + + @cached_property + def oracle_text(self) -> str: + return self.oracle_texts[0] @cached_property - def oracle_text(self) -> list[str]: + def oracle_texts(self) -> list[str]: """Both side oracle texts.""" text = [] for t in [ c.get('printed_text', c.get('oracle_text', '')) if self.is_alt_lang else c.get('oracle_text', '') - for c in self.card + for c in self.cards ]: - # Remove Fuse if present in oracle text data - t = ''.join(t.split('\n')[:-1]) if 'Fuse' in self.keywords else t + if 'Fuse' in self.keywords: + # Remove Fuse if present in oracle text data + t = ''.join(t.split('\n')[:-1]) + elif self.shared_reminder: + # Remove shared reminder if present + t = t[0:-len(self.shared_reminder)].rstrip() + text.append(t) return text + + @cached_property + def flavor_text(self) -> str: + return self.flavor_texts[0] @cached_property - def flavor_text(self) -> list[str]: + def flavor_texts(self) -> list[str]: """Both sides flavor text.""" - return [c.get('flavor_text', '') for c in self.card] + return [c.get('flavor_text', '') for c in self.cards] + + @cached_property + def shared_reminder(self) -> str: + prev_match: str = "" + for oracle_text in [ + c.get('printed_text', c.get('oracle_text', '')) + if self.is_alt_lang else c.get('oracle_text', '') + for c in self.cards + ]: + match = CardTextPatterns.TEXT_REMINDER_ENDING.match(oracle_text) + curr_match = match.group(1) if match else "" + if not match or (prev_match and prev_match != curr_match): + return "" + prev_match = curr_match + return prev_match """ * Bool Data """ @cached_property - def is_hybrid(self) -> list[bool]: + def is_hybrid(self) -> bool: + return self.hybrid_checks[0] + + @cached_property + def hybrid_checks(self) -> list[bool]: """Both sides hybrid check.""" - return [f['is_hybrid'] for f in self.frame] + return [f['is_hybrid'] for f in self.frames] @cached_property - def is_colorless(self) -> list[bool]: + def is_colorless(self) -> bool: + return self.colorless_checks[0] + + @cached_property + def colorless_checks(self) -> list[bool]: """Both sides colorless check.""" - return [f['is_colorless'] for f in self.frame] + return [f['is_colorless'] for f in self.frames] """ * Frame Details """ - + @cached_property - def frame(self) -> list[FrameDetails]: + def frames(self) -> list[FrameDetails]: """Both sides frame data.""" - return [get_frame_details(c) for c in self.card] + return [get_frame_details(c) for c in self.cards] @cached_property - def pinlines(self) -> list[str]: + def pinlines(self) -> str: + return get_ordered_colors(self.color_identity) + + @cached_property + def pinline_identities(self) -> list[str]: """Both sides pinlines identity.""" - return [f['pinlines'] for f in self.frame] + return [f['pinlines'] for f in self.frames] @cached_property - def twins(self) -> list[str]: + def twins(self) -> str: + return self.pinlines + + @cached_property + def twins_identities(self) -> list[str]: """Both sides twins identity.""" - return [f['twins'] for f in self.frame] + return [f['twins'] for f in self.frames] @cached_property - def background(self) -> list[str]: + def background(self) -> str: + return self.pinlines + + @cached_property + def backgrounds(self) -> list[str]: """Both sides background identity.""" - return [f['background'] for f in self.frame] + return [f['background'] for f in self.frames] @cached_property - def identity(self) -> list[str]: + def identity(self) -> str: + return self.pinlines + + @cached_property + def identities(self) -> list[str]: """Both sides frame accurate color identity.""" - return [f['identity'] for f in self.frame] + return [f['identity'] for f in self.frames] class TokenLayout(NormalLayout): diff --git a/src/templates/split.py b/src/templates/split.py index 4f806ae9..9b4dfa64 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -1,368 +1,456 @@ """ * Templates: Split """ -# Standard Library Imports + +from _ctypes import COMError from functools import cached_property from pathlib import Path -from typing import Union, Optional +from typing import Callable -# Third Party Imports from omnitils.strings import normalize_str -from photoshop.api import ElementPlacement, SolidColor, BlendMode +from photoshop.api import BlendMode, ElementPlacement, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src import CFG, CON, ENV -from src.enums.layers import LAYERS import src.helpers as psd +from src import CFG, CON +from src.enums.layers import LAYERS from src.helpers import LayerEffects +from src.helpers.colors import GradientConfig +from src.helpers.effects import copy_layer_fx +from src.helpers.layers import get_reference_layer from src.layouts import SplitLayout from src.schema.adobe import EffectColorOverlay, EffectGradientOverlay -from src.schema.colors import GradientColor +from src.schema.colors import ColorObject, GradientColor from src.templates import BaseTemplate -from src.text_layers import FormattedTextField, FormattedTextArea, ScaledTextField +from src.text_layers import FormattedTextArea, FormattedTextField, ScaledTextField from src.utils.adobe import ReferenceLayer -""" -* Template Classes -""" - -class SplitTemplate(BaseTemplate): +class SplitMod(BaseTemplate): """ - * A template for split cards introduced in Invasion. - - Adds: - * Must return all properties shared by both halves as a list of two items (left, right). - * Must overwrite a lot of core functionality to navigate rendering 2 cards in one template. + * A modifier class for split cards introduced in Invasion. - Todo: - * Formalize as a vector template, implement 'Modifier' class. + Adds for each side: + * Art + * Text fields + * Color definitions """ - is_vehicle = False - is_artifact = False - - # Color and gradient maps - fuse_gradient_locations = { - **CON.gradient_locations.copy(), - 2: [.50, .54], - 3: [.28, .33, .71, .76], - 4: [.28, .33, .50, .54, .71, .76] - } - pinline_gradient_locations = [ - {**CON.gradient_locations.copy(), 2: [.28, .33]}, - {**CON.gradient_locations.copy(), 2: [.71, .76]} - ] - - def __init__(self, layout: SplitLayout, **kwargs): - super().__init__(layout, **kwargs) - """ - * Mixin Methods - """ + sides: list[str] = [LAYERS.LEFT, LAYERS.RIGHT] - @property - def post_text_methods(self): - """Rotate card sideways.""" - return [*super().post_text_methods, psd.rotate_counter_clockwise] + # refion Color maps - """ - * Bool Properties - """ + @cached_property + def fuse_gradient_locations(self) -> dict[int, list[int | float]]: + return { + **CON.gradient_locations, + 2: [0.50, 0.54], + 3: [0.28, 0.33, 0.71, 0.76], + 4: [0.28, 0.33, 0.50, 0.54, 0.71, 0.76], + } @cached_property - def is_centered(self) -> list[bool]: - """Allow centered text for each side independently.""" + def pinline_gradient_locations(self) -> list[dict[int, list[int | float]]]: return [ - bool( - len(self.layout.flavor_text[i]) <= 1 - and len(self.layout.oracle_text[i]) <= 70 - and "\n" not in self.layout.oracle_text[i] - ) for i in range(2) + {**CON.gradient_locations, 2: [0.28, 0.33]}, + {**CON.gradient_locations, 2: [0.71, 0.76]}, ] + # endregion Color maps + + # region Settings + + @cached_property + def color_limit(self) -> int: + """One more than the max number of colors this card can split by.""" + setting = CFG.get_setting( + section="COLORS", key="Max.Colors", default="2", is_bool=False + ) + if isinstance(setting, str): + return int(setting) + 1 + raise ValueError(f"Received invalid value for color limit: {setting}") + + # endregion Settings + + # region Checks + + @cached_property + def is_split(self) -> bool: + return isinstance(self.layout, SplitLayout) + + @cached_property + def text_centerings(self) -> list[bool]: + """Allow centered text for each side independently.""" + if isinstance(self.layout, SplitLayout): + return [ + bool( + len(self.layout.flavor_texts[i]) <= 1 + and len(self.layout.oracle_texts[i]) <= 70 + and "\n" not in self.layout.oracle_texts[i] + ) + for i in range(len(self.sides)) + ] + return [] + @cached_property def is_fuse(self) -> bool: """Determine if this is a 'Fuse' split card.""" - return bool('Fuse' in self.layout.keywords) + return bool("Fuse" in self.layout.keywords) - """ - * Colors - """ + @cached_property + def has_unified_typeline(self) -> bool: + """Determine if the card has only one shared typeline.""" + if isinstance(self.layout, SplitLayout): + return " Room" in self.layout.type_line + return False + + # endregion Checks + + # region Mixin methods @cached_property - def color_limit(self) -> int: - """One more than the max number of colors this card can split by.""" - return 3 + def frame_layer_methods(self) -> list[Callable[[], None]]: + methods = super().frame_layer_methods + if self.is_split: + methods.append(self.frame_layers_split) + return methods + + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: + """Rotate card sideways.""" + methods = super().post_text_methods + if self.is_split: + methods.append(psd.rotate_counter_clockwise) + return methods + + # endregion Mixin methods + + # region Colors @cached_property def fuse_pinlines(self) -> str: """Merged pinline colors of each side.""" - if self.pinlines[0] != self.pinlines[1]: - return self.pinlines[0] + self.pinlines[1] - return self.pinlines[0] + if isinstance(self.layout, SplitLayout): + if self.layout.pinline_identities[0] != self.layout.pinline_identities[1]: + return ( + self.layout.pinline_identities[0] + + self.layout.pinline_identities[1] + ) + return self.layout.pinline_identities[0] + raise NotImplementedError("Fuse colors doesn't support this layout.") @cached_property def fuse_textbox_colors(self) -> str: - """Gold if Fuse colors are more than 3, otherwise use Fuse colors.""" - if len(self.fuse_pinlines) > 3: + """Gold if Fuse colors are more than color_limit, otherwise use Fuse colors.""" + if len(self.fuse_pinlines) > self.color_limit: return LAYERS.GOLD return self.fuse_pinlines @cached_property - def fuse_pinline_colors(self) -> Union[list[int], list[dict]]: + def fuse_pinline_colors( + self, + ) -> ColorObject | list[ColorObject] | list[GradientConfig]: """Color definition for Fuse pinlines.""" return psd.get_pinline_gradient( - self.fuse_pinlines, location_map=self.fuse_gradient_locations) + self.fuse_pinlines, location_map=self.fuse_gradient_locations + ) @cached_property - def fuse_pinlines_action(self) -> Union[psd.create_color_layer, psd.create_gradient_layer]: - """Action used to render Fuse pinlines.""" - return psd.create_color_layer if isinstance( - self.fuse_pinline_colors, SolidColor - ) else psd.create_gradient_layer - - @cached_property - def pinlines_colors(self) -> list[Union[list[int], list[dict]]]: + def pinlines_colors_split( + self, + ) -> list[(ColorObject | list[ColorObject] | list[GradientConfig])]: """Color definitions used for pinlines of each side.""" - return [ - psd.get_pinline_gradient(p, location_map=self.pinline_gradient_locations[i]) - for i, p in enumerate(self.pinlines)] + if isinstance(self.layout, SplitLayout): + return [ + psd.get_pinline_gradient( + p, location_map=self.pinline_gradient_locations[i] + ) + for i, p in enumerate(self.layout.pinline_identities) + ] + return [] - @cached_property - def pinlines_action(self) -> list[Union[psd.create_color_layer, psd.create_gradient_layer]]: - """Action used to render the pinlines of each side.""" - return [ - psd.create_color_layer if isinstance( - colors, SolidColor - ) else psd.create_gradient_layer - for colors in self.pinlines_colors] + # endregion Colors - """ - * Layer Groups - """ + # region Groups @cached_property - def fuse_group(self) -> Optional[LayerSet]: + def fuse_group(self) -> LayerSet | None: """Fuse elements parent group.""" - return psd.getLayerSet('Fuse') + return psd.getLayerSet("Fuse") @cached_property - def fuse_textbox_group(self) -> Optional[LayerSet]: + def fuse_textbox_group(self) -> LayerSet | None: """Fuse textbox group.""" return psd.getLayerSet(LAYERS.TEXTBOX, self.fuse_group) @cached_property - def fuse_pinlines_group(self) -> Optional[LayerSet]: + def fuse_pinlines_group(self) -> LayerSet | None: """Fuse pinlines group.""" return psd.getLayerSet(LAYERS.PINLINES, self.fuse_group) @cached_property - def text_group(self) -> Optional[LayerSet]: - """One text and icons group located in the 'Left' side group.""" - return psd.getLayerSet(LAYERS.TEXT_AND_ICONS, LAYERS.LEFT) + def text_groups(self) -> list[LayerSet | None]: + """Text and icons group for each side.""" + return [psd.getLayerSet(LAYERS.TEXT_AND_ICONS, side) for side in self.sides] @cached_property - def card_groups(self): + def card_groups(self) -> list[LayerSet | None]: """Left and Right side parent groups.""" - return [ - psd.getLayerSet(LAYERS.LEFT), - psd.getLayerSet(LAYERS.RIGHT) - ] + return [psd.getLayerSet(side) for side in self.sides] @cached_property - def pinlines_groups(self) -> list[LayerSet]: + def pinlines_groups_split(self) -> list[LayerSet | None]: """Pinlines group for each side.""" return [psd.getLayerSet(LAYERS.PINLINES, group) for group in self.card_groups] @cached_property - def twins_groups(self) -> list[LayerSet]: + def twins_groups(self) -> list[LayerSet | None]: """Twins group for each side.""" return [psd.getLayerSet(LAYERS.TWINS, group) for group in self.card_groups] @cached_property - def textbox_groups(self) -> list[LayerSet]: + def textbox_groups(self) -> list[LayerSet | None]: """Textbox group for each side.""" return [psd.getLayerSet(LAYERS.TEXTBOX, group) for group in self.card_groups] @cached_property - def background_groups(self) -> list[LayerSet]: + def background_groups(self) -> list[LayerSet | None]: """Background group for each side.""" return [psd.getLayerSet(LAYERS.BACKGROUND, group) for group in self.card_groups] - """ - * References - """ - - @cached_property - def textbox_reference(self) -> list[ArtLayer]: - """Textbox positioning reference for each side.""" - return [ - psd.get_reference_layer( - LAYERS.TEXTBOX_REFERENCE + ' Fuse' if self.is_fuse else LAYERS.TEXTBOX_REFERENCE, - psd.getLayerSet(LAYERS.TEXT_AND_ICONS, group) - ) for group in self.card_groups] - - @cached_property - def name_reference(self) -> list[ArtLayer]: - """list[ArtLayer]: Name reference for each side.""" - return self.text_layer_name - - @cached_property - def type_reference(self) -> list[ArtLayer]: - """list[ArtLayer]: Typeline reference for each side.""" - return [ - n or self.expansion_reference[i] - for i, n in enumerate(self.expansion_symbols)] - - @cached_property - def twins_reference(self) -> list[ArtLayer]: - """Twins positioning reference for each side.""" - return [psd.getLayer('Reference', [group, LAYERS.TWINS]) for group in self.card_groups] + # endregion Groups - @cached_property - def background_reference(self) -> list[ArtLayer]: - """Background positioning reference for each side.""" - return [psd.getLayer('Reference', [group, LAYERS.BACKGROUND]) for group in self.card_groups] + # region Reference layers @cached_property - def art_reference(self) -> list[ArtLayer]: - """Art layer positioning reference for each side.""" - return [psd.getLayer(LAYERS.ART_FRAME, group) for group in self.card_groups] - - """ - * Text Layers - """ + def name_references(self) -> list[ReferenceLayer | None]: + """Name reference for each side.""" + return [ReferenceLayer(layer) for layer in self.text_layers_mana] @cached_property - def text_layer_name(self) -> list[ArtLayer]: - """Name text layer for each side.""" - return [psd.getLayer(LAYERS.NAME, [self.card_groups[i], LAYERS.TEXT_AND_ICONS]) for i in range(2)] + def type_references(self) -> list[ReferenceLayer | None]: + """Typeline reference for each side.""" + return self.expansion_references @cached_property - def text_layer_rules(self) -> list[ArtLayer]: - """Rules text layer for each side.""" - return [psd.getLayer(LAYERS.RULES_TEXT, [self.card_groups[i], LAYERS.TEXT_AND_ICONS]) for i in range(2)] + def expansion_reference(self) -> ArtLayer | None: + if self.is_split: + return self.expansion_references[-1 if self.has_unified_typeline else 0] + return super().expansion_reference @cached_property - def text_layer_type(self) -> list[ArtLayer]: - """Typeline text layer for each side.""" - return [psd.getLayer(LAYERS.TYPE_LINE, [self.card_groups[i], LAYERS.TEXT_AND_ICONS]) for i in range(2)] + def expansion_references(self) -> list[ReferenceLayer | None]: + """Expansion symbol reference for each side.""" + return [ + get_reference_layer(LAYERS.EXPANSION_REFERENCE, group) + for group in self.text_groups + ] @cached_property - def text_layer_mana(self) -> list[ArtLayer]: - """Mana cost text layer for each side.""" - return [psd.getLayer(LAYERS.MANA_COST, [self.card_groups[i], LAYERS.TEXT_AND_ICONS]) for i in range(2)] - - """ - * Expansion Symbol - """ + def textbox_references(self) -> list[ReferenceLayer | None]: + """Textbox positioning reference for each side.""" + return [ + psd.get_reference_layer( + LAYERS.TEXTBOX_REFERENCE + " Fuse" + if self.is_fuse + else LAYERS.TEXTBOX_REFERENCE, + psd.getLayerSet(LAYERS.TEXT_AND_ICONS, group), + ) + for group in self.card_groups + ] @cached_property - def expansion_references(self) -> list[ArtLayer]: - """Expansion reference for each side.""" - return [self.expansion_reference, self.expansion_reference_right] + def background_reference(self) -> list[ArtLayer | None]: + """Background positioning reference for each side.""" + return [ + psd.getLayer("Reference", [group, LAYERS.BACKGROUND]) + for group in self.card_groups + ] @cached_property - def expansion_reference_right(self) -> None: - """Right side expansion symbol reference.""" - return psd.getLayer(LAYERS.EXPANSION_REFERENCE, [LAYERS.RIGHT, LAYERS.TEXT_AND_ICONS]) + def art_references(self) -> list[ReferenceLayer | None]: + """Art layer positioning reference for each side.""" + return [ + get_reference_layer(LAYERS.ART_FRAME, group) for group in self.card_groups + ] - @cached_property - def expansion_symbols(self) -> list[Optional[ArtLayer]]: - """Expansion symbol layers for each side. Right side is generated duplicating the left side.""" - if self.expansion_symbol_layer: - layer = self.expansion_symbol_layer.duplicate(self.expansion_reference_right, ElementPlacement.PlaceAfter) - psd.align_right(layer, self.expansion_reference_right) - return [self.expansion_symbol_layer, layer] - return [None, None] + # endregion Reference layers - """ - * Layers - """ + # region Shape layers @cached_property - def art_layer(self) -> list[ArtLayer]: + def art_layers(self) -> list[ArtLayer | None]: """Art layer for each side.""" return [psd.getLayer(LAYERS.DEFAULT, group) for group in self.card_groups] @cached_property - def background_layer(self) -> list[ArtLayer]: - """Background layer for each side.""" - return [psd.getLayer(b, LAYERS.BACKGROUND) for b in self.background] + def rules_text_dividers(self) -> list[ArtLayer | None]: + """Divider layer for each side.""" + return [None] * len(self.sides) - @cached_property - def twins_layer(self) -> list[ArtLayer]: - """Twins layer for each side.""" - return [psd.getLayer(t, LAYERS.TWINS) for t in self.twins] + # endregion Shape layers + + # region Text layers @cached_property - def textbox_layer(self) -> list[ArtLayer]: - """Textbox layer for each side.""" - return [psd.getLayer(t, LAYERS.TEXTBOX) for t in self.pinlines] + def text_layers_name(self) -> list[ArtLayer | None]: + """Name text layer for each side.""" + return [psd.getLayer(LAYERS.NAME, [group]) for group in self.text_groups] @cached_property - def divider_layer(self) -> list[Optional[ArtLayer]]: - """Divider layer for each side. List updated if either side has flavor text.""" - return [None, None] + def text_layers_rules(self) -> list[ArtLayer | None]: + """Rules text layer for each side.""" + return [psd.getLayer(LAYERS.RULES_TEXT, [group]) for group in self.text_groups] - """ - * Blending Masks - """ + @cached_property + def text_layers_type(self) -> list[ArtLayer | None]: + """Typeline text layer for each side.""" + return [psd.getLayer(LAYERS.TYPE_LINE, [group]) for group in self.text_groups] @cached_property - def mask_layers(self) -> list[ArtLayer]: - """Blending masks supported by this template.""" - return [psd.getLayer(LAYERS.HALF, LAYERS.MASKS)] + def text_layers_mana(self) -> list[ArtLayer | None]: + """Mana cost text layer for each side.""" + return [psd.getLayer(LAYERS.MANA_COST, [group]) for group in self.text_groups] + + # endregion Text layers + + # region Expansion Symbol + + def load_expansion_symbol(self) -> None: + super().load_expansion_symbol() + if self.is_split: + self.expansion_symbols + + @cached_property + def expansion_symbols(self) -> list[ArtLayer | None]: + """Expansion symbol layer for each side.""" + if not self.has_unified_typeline and self.expansion_symbol_layer: + symbols: list[ArtLayer | None] = [self.expansion_symbol_layer] + for layer_ref in self.expansion_references[1:]: + if layer_ref: + symbol_duplicate = self.expansion_symbol_layer.duplicate( + layer_ref, ElementPlacement.PlaceAfter + ) + psd.align_right(symbol_duplicate, layer_ref) + try: + copy_layer_fx(self.expansion_symbol_layer, symbol_duplicate) + except COMError: + pass + symbols.append(symbol_duplicate) + else: + symbols.append(None) + return symbols + return [self.expansion_symbol_layer] + + # endregion Expansion Symbol + + # region Artwork - """ - * Watermarks - """ + def load_artwork( + self, + art_file: str | Path | None = None, + art_layer: ArtLayer | None = None, + art_reference: ReferenceLayer | None = None, + ) -> None: + if not self.is_fullart and self.is_split: + return self.load_artworks([art_file] if art_file else None) + else: + return super().load_artwork(art_file, art_layer, art_reference) - @cached_property - def watermark_colors(self) -> list[list[SolidColor]]: + def load_artworks( + self, + art_files: list[str | Path] | None = None, + art_layers: list[ArtLayer | None] | None = None, + art_references: list[ReferenceLayer | None] | None = None, + ) -> None: + """Loads art for each side.""" + + if isinstance(self.layout, SplitLayout): + # Set default values + art_files = art_files or [*self.layout.art_files] + art_layers = art_layers or self.art_layers + art_references = art_references or self.art_references + + # Second art not provided + if len(art_files) == 1: + # Manually select a second art + self.console.update("Please select the second split art!") + file: list[str] = self.app.openDialog() + if not file: + self.console.update("No art selected, cancelling render.") + self.console.cancel_thread(thr=self.event) + return + + # Place new art in the correct order + if normalize_str(self.layout.names[0]) == normalize_str( + self.layout.file["name"] + ): + art_files.append(file[0]) + else: + art_files.insert(0, file[0]) + + # Load art for each side + for i, (art_file, layer, ref) in enumerate( + zip(art_files, art_layers, art_references) + ): + if art_file and layer and ref: + super().load_artwork( + art_file=art_files[i], + art_layer=art_layers[i], + art_reference=ref, + ) + + # endregion Artwork + + # region Watermarks + + @cached_property + def watermarks_colors(self) -> list[list[SolidColor | list[int]]]: """A list of 'SolidColor' objects for each face.""" - colors = [] - for i, pinline in enumerate(self.pinlines): - if pinline in self.watermark_color_map: - # Named pinline colors - colors.append([self.watermark_color_map.get(pinline, self.RGB_WHITE)]) - elif len(self.identity[i]) < 3: - # Dual color based on identity - colors.append([ - self.watermark_color_map.get(c, self.RGB_WHITE) - for c in self.identity[i]]) - else: - colors.append([]) - return colors - - @cached_property - def watermark_fx(self) -> list[list[LayerEffects]]: + if isinstance(self.layout, SplitLayout): + colors: list[list[SolidColor | list[int]]] = [] + for i, pinline in enumerate(self.layout.pinline_identities): + if pinline in self.watermark_color_map: + # Named pinline colors + colors.append( + [self.watermark_color_map.get(pinline, self.RGB_WHITE)] + ) + elif len(self.identity[i]) < self.color_limit: + # Dual color based on identity + colors.append( + [ + self.watermark_color_map.get(c, self.RGB_WHITE) + for c in self.identity[i] + ] + ) + else: + colors.append([]) + raise NotImplementedError("Watermarks colors doesn't support this layout.") + + @cached_property + def watermark_fxs(self) -> list[list[LayerEffects]]: """A list of LayerEffects' objects for each face.""" fx: list[list[LayerEffects]] = [] - for color in self.watermark_colors: + for color in self.watermarks_colors: if len(color) == 1: # Single color watermark - fx.append([EffectColorOverlay( - opacity=100, - color=color[0] - )]) + fx.append([EffectColorOverlay(opacity=100, color=color[0])]) elif len(color) == 2: # Dual color watermark - fx.append([EffectGradientOverlay( - rotation=0, - colors=[ - GradientColor( - color=color[0], - location=0, - midpoint=50), - GradientColor( - color=color[1], - location=4096, - midpoint=50) + fx.append( + [ + EffectGradientOverlay( + rotation=0, + colors=[ + GradientColor(color=color[0], location=0, midpoint=50), + GradientColor( + color=color[1], location=4096, midpoint=50 + ), + ], + ) ] - )]) + ) else: fx.append([]) return fx @@ -370,174 +458,230 @@ def watermark_fx(self) -> list[list[LayerEffects]]: def create_watermark(self) -> None: """Render a watermark for each side that has one.""" - # Add watermark to each side if needed - for i, watermark in enumerate(self.layout.watermark): - - # Required values to generate a Watermark - if not all([ - self.layout.watermark_svg[i], - self.textbox_reference[i], - self.watermark_colors[i], - watermark - ]): - return - - # Get watermark custom settings if available - wm_details = CON.watermarks.get(watermark, {}) - - # Import and frame the watermark - wm = psd.import_svg( - path=self.layout.watermark_svg[i], - ref=self.textbox_reference[i], - placement=ElementPlacement.PlaceAfter, - docref=self.docref) - psd.frame_layer( - layer=wm, - ref=self.textbox_reference[i], - smallest=True, - scale=wm_details.get('scale', 80)) - - # Apply opacity, blending, and effects - wm.opacity = wm_details.get('opacity', CFG.watermark_opacity) - wm.blendMode = BlendMode.ColorBurn - psd.apply_fx(wm, self.watermark_fx[i]) + if isinstance(self.layout, SplitLayout): + # Add watermark to each side if needed + for i, watermark in enumerate(self.layout.watermarks): + # Required values to generate a Watermark + if ( + watermark + and (mark := self.layout.watermark_svgs[i]) + and (textbox_ref := self.textbox_references[i]) + ): + # Get watermark custom settings if available + wm_details = CON.watermarks.get(watermark, {}) + + # Import and frame the watermark + wm = psd.import_svg( + path=mark, + ref=textbox_ref, + placement=ElementPlacement.PlaceAfter, + docref=self.docref, + ) + psd.frame_layer( + layer=wm, + ref=textbox_ref, + smallest=True, + scale=wm_details.get("scale", 80), + ) + + # Apply opacity, blending, and effects + wm.opacity = wm_details.get("opacity", CFG.watermark_opacity) + wm.blendMode = BlendMode.ColorBurn + psd.apply_fx(wm, self.watermark_fxs[i]) + else: + return super().create_watermark() + + # endregion Watermarks + + # region Frame layer methods + + def frame_layers_split(self) -> None: + """Enable frame layers required by Split cards. None by default.""" + pass + + # endregion Frame layer methods + + # region Text layer methods + def basic_text_layers(self) -> None: + """Add basic text layers for each side.""" + if isinstance(self.layout, SplitLayout): + for i in range(len(self.sides)): + if (name := self.text_layers_name[i]) and ( + mana := self.text_layers_mana[i] + ): + self.text.extend( + [ + FormattedTextField( + layer=mana, + contents=self.layout.mana_costs[i], + ), + ScaledTextField( + layer=name, + contents=self.layout.names[i], + reference=self.name_references[i], + ), + ] + ) + + if (not self.has_unified_typeline or i == 0) and ( + layer := self.text_layers_type[i] + ): + self.text.append( + ScaledTextField( + layer=layer, + contents=self.layout.type_lines[i], + reference=self.type_references[ + -1 if self.has_unified_typeline else i + ], + ) + ) + else: + return super().basic_text_layers() + + def rules_text_and_pt_layers(self) -> None: + """Add rules and P/T text for each side.""" + if isinstance(self.layout, SplitLayout): + for i in range(len(self.sides)): + if layer := self.text_layers_rules[i]: + self.text.append( + FormattedTextArea( + layer=layer, + contents=self.layout.oracle_texts[i], + flavor=self.layout.flavor_texts[i], + reference=self.textbox_references[i], + divider=self.rules_text_dividers[i], + centered=self.text_centerings[i], + ) + ) + else: + return super().rules_text_and_pt_layers() + + # endregion Text layer methods + + +class SplitTemplate(SplitMod): """ - * Loading Files + * A template for split cards. + + In addition to SplitMod, adds for each side: + * Frame layers """ - def load_artwork( - self, - art_file: Optional[str | Path | list[str | Path]] = None, - art_layer: Optional[list[ArtLayer]] = None, - art_reference: Optional[list[ReferenceLayer]] = None - ) -> None: - """Loads the specified art file into the specified layer. - - Args: - art_file: Optional path (as str or Path) to art file. Will use `self.layout.art_file` - if not provided. - art_layer: Optional `ArtLayer` where art image should be placed when imported. Will use `self.art_layer` - property if not provided. - art_reference: Optional `ReferenceLayer` that should be used to position and scale the imported - image. Will use `self.art_reference` property if not provided.` - """ - - # Set default values - art_file = art_file or self.layout.art_file - art_layer = art_layer or self.art_layer - art_reference = art_reference or self.art_reference - - # Double up art for test mode - if ENV.TEST_MODE and not isinstance(art_file, list): - art_file = [art_file] * 2 - - # Second art not provided - if len(art_file) == 1: - - # Manually select a second art - self.console.update("Please select the second split art!") - file = self.app.openDialog() - if not file: - self.console.update('No art selected, cancelling render.') - self.console.cancel_thread(thr=self.event) - return - - # Place new art in the correct order - if normalize_str(self.layout.name[0]) == normalize_str(self.layout.file['name']): - art_file.append(file[0]) - else: - art_file.insert(0, file[0]) + # region Reference layers - # Load art for each side - for i, ref in enumerate(art_reference): - super().load_artwork( - art_file=art_file[i], - art_layer=art_layer[i], - art_reference=ref) + @cached_property + def twins_references(self) -> list[ArtLayer | None]: + """Twins positioning reference for each side.""" + return [ + psd.getLayer("Reference", [group, LAYERS.TWINS]) + for group in self.card_groups + ] - """ - * Frame Layer Methods - """ + # endregion Reference layers - def enable_frame_layers(self) -> None: - """Enable frame layers for each side. Add Fuse layers if required.""" + # region Shape layers - # Frame layers - for i in range(2): - # Copy twins and position - self.twins_layer[i].visible = True - twins = self.twins_layer[i].parent.duplicate(self.twins_groups[i], ElementPlacement.PlaceBefore) - self.twins_layer[i].visible = False - twins.visible = True - psd.align_horizontal(twins, self.twins_reference[i]) - - # Copy background and position - background = self.background_layer[i].duplicate( - self.background_groups[i], ElementPlacement.PlaceInside) - background.visible = True - psd.align_horizontal(background, self.background_reference[i]) - - # Copy textbox and position - textbox = self.textbox_layer[i].duplicate( - self.textbox_groups[i], ElementPlacement.PlaceInside) - textbox.visible = True - self.active_layer = textbox - psd.align_horizontal(textbox, self.textbox_reference[i].dims) - if self.is_fuse: - psd.select_bounds(self.textbox_reference[i].bounds, self.doc_selection) - self.doc_selection.invert() - self.doc_selection.clear() - self.doc_selection.deselect() - - # Apply pinlines - self.generate_layer( - group=self.pinlines_groups[i], - colors=self.pinlines_colors[i]) - - # Fuse addone - if self.is_fuse: - psd.getLayer(f'{LAYERS.BORDER} Fuse').visible = True - self.fuse_group.visible = True - self.generate_layer( - group=self.fuse_pinlines_group, - colors=self.fuse_pinline_colors) - self.generate_layer( - group=self.fuse_textbox_group, - colors=self.fuse_textbox_colors, - masks=self.mask_layers) + @cached_property + def background_layers(self) -> list[ArtLayer | None]: + """Background layer for each side.""" + if isinstance(self.layout, SplitLayout): + return [psd.getLayer(b, LAYERS.BACKGROUND) for b in self.layout.backgrounds] + return [None] * len(self.sides) - """ - * Text Layer Methods - """ + @cached_property + def twins_layers(self) -> list[ArtLayer | None]: + """Twins layer for each side.""" + if isinstance(self.layout, SplitLayout): + return [psd.getLayer(t, LAYERS.TWINS) for t in self.layout.twins_identities] + return [None] * len(self.sides) - def basic_text_layers(self) -> None: - """Add basic text layers for each side.""" - for i in range(2): - self.text.extend([ - FormattedTextField( - layer=self.text_layer_mana[i], - contents=self.layout.mana_cost[i] - ), - ScaledTextField( - layer=self.text_layer_name[i], - contents=self.layout.name[i], - reference=self.name_reference[i] - ), - ScaledTextField( - layer=self.text_layer_type[i], - contents=self.layout.type_line[i], - reference=self.type_reference[i] - )]) + @cached_property + def textbox_layers(self) -> list[ArtLayer | None]: + """Textbox layer for each side.""" + if isinstance(self.layout, SplitLayout): + return [ + psd.getLayer(t, LAYERS.TEXTBOX) for t in self.layout.pinline_identities + ] + return [None] * len(self.sides) - def rules_text_and_pt_layers(self) -> None: - """Add rules and P/T text for each face.""" - for i in range(2): - self.text.append( - FormattedTextArea( - layer=self.text_layer_rules[i], - contents=self.layout.oracle_text[i], - flavor=self.layout.flavor_text[i], - reference=self.textbox_reference[i], - divider=self.divider_layer[i], - centered=self.is_centered[i])) + # endregion Shape layers + + # region Blending masks + + @cached_property + def mask_layers(self) -> list[ArtLayer]: + """Blending masks supported by this template.""" + if self.is_split: + if layer := psd.getLayer(LAYERS.HALF, LAYERS.MASKS): + return [layer] + return [] + return super().mask_layers + + # endregion Blending masks + + # region Frame layer methods + + def frame_layers_split(self) -> None: + """Enable frame layers for each side. Add Fuse layers if required.""" + + # Frame layers + for i in range(len(self.sides)): + if (group := self.twins_groups[i]) and (layer := self.twins_layers[i]): + # Copy twins and position + layer.visible = True + twins = layer.parent.duplicate(group, ElementPlacement.PlaceBefore) + layer.visible = False + twins.visible = True + + if ref := self.twins_references[i]: + psd.align_horizontal(twins, ref) + + if layer := self.background_layers[i]: + # Copy background and position + background = layer.duplicate( + self.background_groups[i], ElementPlacement.PlaceInside + ) + background.visible = True + + if ref := self.background_reference[i]: + psd.align_horizontal(background, ref) + + if layer := self.textbox_layers[i]: + # Copy textbox and position + textbox = layer.duplicate( + self.textbox_groups[i], ElementPlacement.PlaceInside + ) + textbox.visible = True + + if ref := self.textbox_references[i]: + self.active_layer = textbox + psd.align_horizontal(textbox, ref.dims) + if self.is_fuse: + psd.select_bounds(ref.bounds, self.doc_selection) + self.doc_selection.invert() + self.doc_selection.clear() + self.doc_selection.deselect() + + if group := self.pinlines_groups_split[i]: + # Apply pinlines + self.generate_layer(group=group, colors=self.pinlines_colors_split[i]) + + # Fuse addon + if self.is_fuse: + if layer := psd.getLayer(f"{LAYERS.BORDER} Fuse"): + layer.visible = True + if self.fuse_group: + self.fuse_group.visible = True + if self.fuse_pinlines_group: + self.generate_layer( + group=self.fuse_pinlines_group, colors=self.fuse_pinline_colors + ) + if self.fuse_textbox_group: + self.generate_layer( + group=self.fuse_textbox_group, + colors=self.fuse_textbox_colors, + masks=self.mask_layers, + ) + + # endregion Frame layer methods From 4b546dce4e00db8c8630e0e35efe3cd16f0727b1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 15 Jun 2025 14:45:22 +0300 Subject: [PATCH 020/190] feat(Photoshop): Adds an option to minimize Photoshop when rendering starts --- poetry.lock | 326 +++++++++++++++++++++++++++++++------- pyproject.toml | 2 + src/_config.py | 1 + src/console.py | 18 ++- src/data/config/base.toml | 6 + src/gui/console.py | 19 ++- src/templates/_core.py | 6 +- src/utils/adobe.py | 25 ++- src/utils/windows.py | 75 +++++++++ 9 files changed, 416 insertions(+), 62 deletions(-) create mode 100644 src/utils/windows.py diff --git a/poetry.lock b/poetry.lock index e54f74e1..ba664f87 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aggdraw" @@ -6,6 +6,7 @@ version = "1.3.19" description = "High quality drawing interface for PIL." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "aggdraw-1.3.19-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:cfbb072ee0a6de6cb6a782724df6e8ab9648d9566fb033118dcebbe9dc06326e"}, {file = "aggdraw-1.3.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:789fc905d0bad232fe4d783ee115ed34a1c0904957dc384fc5c45a40df517975"}, @@ -41,6 +42,7 @@ version = "0.17.4" description = "Python graph (network) package" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, @@ -52,6 +54,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -63,6 +66,7 @@ version = "3.5.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, @@ -77,6 +81,7 @@ version = "0.7.0" description = "A thin layer that helps to wrap a callback-style API in an async/await-style API" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ {file = "asyncgui-0.7.0-py3-none-any.whl", hash = "sha256:c1e53b5c0c4df8817b28aa58bda3bc13752ea65519b14c2d7fa1c1721c9bb362"}, {file = "asyncgui-0.7.0.tar.gz", hash = "sha256:6a5cfdfee34f6478e857a55ff88981995479930c3ddc577c5ff9d85a79f12a4a"}, @@ -91,6 +96,7 @@ version = "0.7.0" description = "Async library for Kivy" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ {file = "asynckivy-0.7.0-py3-none-any.whl", hash = "sha256:4855d730ff3283203dcb325bc4018301737a307732f1075b6ef61c2db590bd10"}, {file = "asynckivy-0.7.0.tar.gz", hash = "sha256:83396cecceb513393040b40a3cf6d920ed6190e78660aae5edf48c9d3fd1140b"}, @@ -105,18 +111,19 @@ version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] [[package]] name = "babel" @@ -124,6 +131,7 @@ version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, @@ -138,6 +146,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -149,6 +158,7 @@ version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, @@ -170,6 +180,7 @@ version = "2.5.post1" description = "Bash style brace expander." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, @@ -181,6 +192,8 @@ version = "1.1.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\"" files = [ {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, @@ -192,6 +205,10 @@ files = [ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, + {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, @@ -204,8 +221,14 @@ files = [ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, + {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, + {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, @@ -216,8 +239,24 @@ files = [ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, + {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, + {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, + {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, + {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, + {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, + {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, + {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, @@ -227,6 +266,10 @@ files = [ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, + {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, @@ -238,6 +281,10 @@ files = [ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, + {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, @@ -250,6 +297,10 @@ files = [ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, + {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, @@ -262,6 +313,10 @@ files = [ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, + {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, @@ -273,6 +328,8 @@ version = "1.1.0.0" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.7" +groups = ["main"] +markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, @@ -312,6 +369,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -326,6 +384,7 @@ version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, @@ -337,6 +396,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -416,6 +477,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -427,6 +489,7 @@ version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, @@ -541,6 +604,7 @@ version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, @@ -555,10 +619,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "commitizen" @@ -566,6 +632,7 @@ version = "3.30.0" description = "Python commitizen client tool" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "commitizen-3.30.0-py3-none-any.whl", hash = "sha256:8dc226a136aee61207e396101fcd89e73de67a57c06e066db982310863caaf65"}, {file = "commitizen-3.30.0.tar.gz", hash = "sha256:ae67a47c1a700b4f35ac12de0c35c7ba96f152b9377d22b6226bb87372c527b0"}, @@ -589,6 +656,7 @@ version = "1.4.8" description = "Pure Python COM package" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "comtypes-1.4.8-py3-none-any.whl", hash = "sha256:773109b12aa0bec630d5b2272dd983cbaa25605a12fc1319f99730c9d0b72f79"}, {file = "comtypes-1.4.8.zip", hash = "sha256:bb2286cfb3b96f838307200a85b00b98e1bdebf1e58ec3c28b36b1ccfafac01f"}, @@ -600,6 +668,7 @@ version = "1.3.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, @@ -684,6 +753,7 @@ version = "0.9.5" description = "A python port of YUI CSS Compressor" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05"}, ] @@ -694,6 +764,7 @@ version = "0.12.1" description = "Composable style cycles" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, @@ -709,6 +780,7 @@ version = "0.6.2" description = "Minimal, easy-to-use, declarative cli tool" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed"}, {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, @@ -720,6 +792,7 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -731,6 +804,7 @@ version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -741,6 +815,7 @@ version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -752,6 +827,7 @@ version = "3.2.6" description = "The dynamic configurator for your Python Project" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "dynaconf-3.2.6-py2.py3-none-any.whl", hash = "sha256:3911c740d717df4576ed55f616c7cbad6e06bc8ef23ffca444b6e2a12fb1c34c"}, {file = "dynaconf-3.2.6.tar.gz", hash = "sha256:74cc1897396380bb957730eb341cc0976ee9c38bbcb53d3307c50caed0aedfb8"}, @@ -776,6 +852,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version == \"3.10\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -790,6 +868,7 @@ version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, @@ -798,7 +877,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "fonttools" @@ -806,6 +885,7 @@ version = "4.54.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, @@ -858,18 +938,18 @@ files = [ ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] -type1 = ["xattr"] +type1 = ["xattr ; sys_platform == \"darwin\""] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] name = "ghp-import" @@ -877,6 +957,7 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -894,6 +975,7 @@ version = "4.0.11" description = "Git Object Database" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, @@ -908,6 +990,7 @@ version = "3.1.43" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, @@ -918,7 +1001,7 @@ gitdb = ">=4.0.1,<5" [package.extras] doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "griffe" @@ -926,6 +1009,7 @@ version = "1.5.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860"}, {file = "griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b"}, @@ -940,6 +1024,7 @@ version = "0.3.7" description = "A comprehensive library of Magic the Gathering API utilities." optional = false python-versions = "<3.13,>=3.10" +groups = ["main"] files = [ {file = "hexproof-0.3.7-py3-none-any.whl", hash = "sha256:bdfbe1d6107091ea5c74421af26f35874e5e36da050da7b3b2791dc78bec14fb"}, {file = "hexproof-0.3.7.tar.gz", hash = "sha256:71a3db263c81ef2ccbce084a6b7ce2e78f1d35645f6204208773fa7f97c0eccf"}, @@ -961,6 +1046,7 @@ version = "0.1.13" description = "An HTML Minifier" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2"}, ] @@ -971,6 +1057,7 @@ version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, @@ -985,6 +1072,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -999,6 +1087,7 @@ version = "2.36.0" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "imageio-2.36.0-py3-none-any.whl", hash = "sha256:471f1eda55618ee44a3c9960911c35e647d9284c68f077e868df633398f137f0"}, {file = "imageio-2.36.0.tar.gz", hash = "sha256:1c8f294db862c256e9562354d65aa54725b8dafed7f10f02bb3ec20ec1678850"}, @@ -1032,6 +1121,7 @@ version = "1.0.0" description = "deflate64 compression/decompression library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a90c0bdf4a7ecddd8a64cc977181810036e35807f56b0bcacee9abb0fcfd18dc"}, {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe7c14aebf1c5a74fc3b70d355be1280a011521a76aa3895486e62454f4242"}, @@ -1099,6 +1189,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -1110,6 +1201,7 @@ version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, @@ -1127,6 +1219,7 @@ version = "3.0.1" description = "JavaScript minifier." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"}, ] @@ -1137,6 +1230,7 @@ version = "2.3.0" description = "An open-source Python framework for developing GUI apps that work cross-platform, including desktop, mobile and embedded platforms." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "Kivy-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcd8fdc742ae10d27e578df2052b4c3e99a754e91baad77d1f2e4f4d1238917f"}, {file = "Kivy-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7492593a5d5d916c48b14a06fbe177341b1efb5753c9984be2fb84e3b3313c89"}, @@ -1181,14 +1275,14 @@ pygments = "*" pypiwin32 = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -angle = ["kivy-deps.angle (>=0.4.0,<0.5.0)"] -base = ["docutils", "kivy-deps.angle (>=0.4.0,<0.5.0)", "kivy-deps.glew (>=0.3.1,<0.4.0)", "kivy-deps.sdl2 (>=0.7.0,<0.8.0)", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32", "requests"] -dev = ["flake8", "funcparserlib (==1.0.0a0)", "kivy-deps.glew-dev (>=0.3.1,<0.4.0)", "kivy-deps.gstreamer-dev (>=0.3.3,<0.4.0)", "kivy-deps.sdl2-dev (>=0.7.0,<0.8.0)", "pre-commit", "pyinstaller", "pytest (>=3.6)", "pytest-asyncio (!=0.11.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout", "responses", "sphinx (<=6.2.1)", "sphinxcontrib-actdiag", "sphinxcontrib-blockdiag", "sphinxcontrib-jquery", "sphinxcontrib-nwdiag", "sphinxcontrib-seqdiag"] -full = ["docutils", "ffpyplayer", "kivy-deps.angle (>=0.4.0,<0.5.0)", "kivy-deps.glew (>=0.3.1,<0.4.0)", "kivy-deps.gstreamer (>=0.3.3,<0.4.0)", "kivy-deps.sdl2 (>=0.7.0,<0.8.0)", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32"] -glew = ["kivy-deps.glew (>=0.3.1,<0.4.0)"] -gstreamer = ["kivy-deps.gstreamer (>=0.3.3,<0.4.0)"] -media = ["ffpyplayer", "kivy-deps.gstreamer (>=0.3.3,<0.4.0)"] -sdl2 = ["kivy-deps.sdl2 (>=0.7.0,<0.8.0)"] +angle = ["kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\""] +base = ["docutils", "kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\"", "kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32 ; sys_platform == \"win32\"", "requests"] +dev = ["flake8", "funcparserlib (==1.0.0a0)", "kivy-deps.glew-dev (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer-dev (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2-dev (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pre-commit", "pyinstaller", "pytest (>=3.6)", "pytest-asyncio (!=0.11.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout", "responses", "sphinx (<=6.2.1)", "sphinxcontrib-actdiag", "sphinxcontrib-blockdiag", "sphinxcontrib-jquery", "sphinxcontrib-nwdiag", "sphinxcontrib-seqdiag"] +full = ["docutils", "ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\"", "kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32 ; sys_platform == \"win32\""] +glew = ["kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\""] +gstreamer = ["kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] +media = ["ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] +sdl2 = ["kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\""] tuio = ["oscpy"] [[package]] @@ -1197,6 +1291,8 @@ version = "0.4.0" description = "Repackaged binary dependency of Kivy." optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "kivy_deps.angle-0.4.0-cp310-cp310-win32.whl", hash = "sha256:7873a551e488afa5044c4949a4aa42c4a4c4290469f0a6dd861e6b95283c9638"}, {file = "kivy_deps.angle-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71f2f01a3a7bbe1d4790e2a64e64a0ea8ae154418462ea407799ed66898b2c1f"}, @@ -1219,6 +1315,8 @@ version = "0.3.1" description = "Repackaged binary dependency of Kivy." optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "kivy_deps.glew-0.3.1-cp310-cp310-win32.whl", hash = "sha256:8f4b3ed15acb62474909b6d41661ffb4da9eb502bb5684301fb2da668f288a58"}, {file = "kivy_deps.glew-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef2d2a93f129d8425c75234e7f6cc0a34b59a4aee67f6d2cd7a5fdfa9915b53"}, @@ -1241,6 +1339,8 @@ version = "0.7.0" description = "Repackaged binary dependency of Kivy." optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "kivy_deps.sdl2-0.7.0-cp310-cp310-win32.whl", hash = "sha256:3c4b2bf1e473e6124563e1ff58cf3475c4f19fe9248940872c9e3c248bac3cb4"}, {file = "kivy_deps.sdl2-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac0f4a6fe989899a60bbdb39516f45e4d90e2499864ab5d63e3706001cde48e8"}, @@ -1262,6 +1362,7 @@ version = "0.1.5" description = "" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "Kivy Garden-0.1.5.tar.gz", hash = "sha256:2b8377378e87501d5d271f33d94f0e44c089884572c64f89c9d609b1f86a2748"}, {file = "Kivy_Garden-0.1.5-py3-none-any.whl", hash = "sha256:ef50f44b96358cf10ac5665f27a4751bb34ef54051c54b93af891f80afe42929"}, @@ -1276,6 +1377,7 @@ version = "1.4.7" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, @@ -1399,6 +1501,7 @@ version = "0.4" description = "Makes it easy to load subpackages and functions on demand." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, @@ -1418,6 +1521,7 @@ version = "0.7.2" description = "Python logging made (stupidly) simple" optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, @@ -1428,7 +1532,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +dev = ["Sphinx (==7.2.5) ; python_version >= \"3.9\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.2.2) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "mypy (==v1.5.1) ; python_version >= \"3.8\"", "pre-commit (==3.4.0) ; python_version >= \"3.8\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==7.4.0) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==4.1.0) ; python_version >= \"3.8\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.0.0) ; python_version >= \"3.8\"", "sphinx-autobuild (==2021.3.14) ; python_version >= \"3.9\"", "sphinx-rtd-theme (==1.3.0) ; python_version >= \"3.9\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.11.0) ; python_version >= \"3.8\""] [[package]] name = "macholib" @@ -1436,6 +1540,8 @@ version = "1.16.3" description = "Mach-O header analysis and editing" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"darwin\"" files = [ {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, @@ -1450,6 +1556,7 @@ version = "3.7" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, @@ -1465,6 +1572,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1489,6 +1597,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1559,6 +1668,7 @@ version = "3.9.2" description = "Python plotting package" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, @@ -1622,6 +1732,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1633,6 +1744,7 @@ version = "0.61.0" description = "A module for monitoring memory usage of a python program" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"}, {file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"}, @@ -1647,6 +1759,7 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1658,6 +1771,7 @@ version = "1.6.1" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1680,7 +1794,7 @@ watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autolinks-plugin" @@ -1688,6 +1802,7 @@ version = "0.7.1" description = "An MkDocs plugin" optional = false python-versions = ">=3.4" +groups = ["dev"] files = [ {file = "mkdocs-autolinks-plugin-0.7.1.tar.gz", hash = "sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151"}, {file = "mkdocs_autolinks_plugin-0.7.1-py3-none-any.whl", hash = "sha256:5c6c17f6649b68e79a9ef0b2648d59f3072e18002b90ee1586a64c505f11ab12"}, @@ -1702,6 +1817,7 @@ version = "1.2.0" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, @@ -1718,6 +1834,7 @@ version = "0.5.0" description = "MkDocs plugin to programmatically generate documentation pages during the build" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea"}, {file = "mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc"}, @@ -1732,6 +1849,7 @@ version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, @@ -1748,6 +1866,7 @@ version = "0.3.2" description = "MkDocs plugin for setting revision date from git per markdown file." optional = false python-versions = ">=3.4" +groups = ["dev"] files = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, ] @@ -1763,6 +1882,7 @@ version = "6.2.2" description = "Mkdocs Markdown includer plugin." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_include_markdown_plugin-6.2.2-py3-none-any.whl", hash = "sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7"}, {file = "mkdocs_include_markdown_plugin-6.2.2.tar.gz", hash = "sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d"}, @@ -1781,6 +1901,7 @@ version = "9.5.42" description = "Documentation that simply works" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_material-9.5.42-py3-none-any.whl", hash = "sha256:452a7c5d21284b373f36b981a2cbebfff59263feebeede1bc28652e9c5bbe316"}, {file = "mkdocs_material-9.5.42.tar.gz", hash = "sha256:92779b5e9b5934540c574c11647131d217dc540dce72b05feeda088c8eb1b8f2"}, @@ -1810,6 +1931,7 @@ version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, @@ -1821,6 +1943,7 @@ version = "0.7.2" description = "An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs-minify-plugin-0.7.2.tar.gz", hash = "sha256:6a551e22d6517eaef9e1890afd60021dc1dcd1255de02d266f588d1ace040713"}, {file = "mkdocs_minify_plugin-0.7.2-py3-none-any.whl", hash = "sha256:ae8bfc4a68806883e990ea025938b3f989da7b9fa08ea8390dba47adf25e0c5b"}, @@ -1838,6 +1961,7 @@ version = "2.6" description = "Plugin to extend MkDocs Material theme." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_pymdownx_material_extras-2.6-py3-none-any.whl", hash = "sha256:9a005c933c70fdfd2bdb320022b23ddce0b3fc5595aeef7084c8b7613804190b"}, {file = "mkdocs_pymdownx_material_extras-2.6.tar.gz", hash = "sha256:2aae99cd91a604811ec8b750cc9526f8a404961d90cee30fd43ff112f651d8b1"}, @@ -1852,6 +1976,7 @@ version = "0.1.3" description = "MkDocs plugin to allow placing mkdocs.yml in the same directory as documentation" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocs_same_dir-0.1.3-py3-none-any.whl", hash = "sha256:3d094649e2e47efcf90a8b0051a4c2b837aaf4137a28c8e334ba9465804a317e"}, {file = "mkdocs_same_dir-0.1.3.tar.gz", hash = "sha256:c849556b1d79ae270947f41bb89d442aa1e858ab6ec6423eb178ae76a7f984fc"}, @@ -1866,6 +1991,7 @@ version = "0.23.0" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocstrings-0.23.0-py3-none-any.whl", hash = "sha256:051fa4014dfcd9ed90254ae91de2dbb4f24e166347dae7be9a997fe16316c65e"}, {file = "mkdocstrings-0.23.0.tar.gz", hash = "sha256:d9c6a37ffbe7c14a7a54ef1258c70b8d394e6a33a1c80832bce40b9567138d1c"}, @@ -1891,6 +2017,7 @@ version = "1.8.0" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mkdocstrings_python-1.8.0-py3-none-any.whl", hash = "sha256:4209970cc90bec194568682a535848a8d8489516c6ed4adbe58bbc67b699ca9d"}, {file = "mkdocstrings_python-1.8.0.tar.gz", hash = "sha256:1488bddf50ee42c07d9a488dddc197f8e8999c2899687043ec5dd1643d057192"}, @@ -1906,6 +2033,7 @@ version = "6.1.0" description = "multidict implementation" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, @@ -2010,6 +2138,7 @@ version = "0.2.3" description = "multi volume file wrapper library" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678"}, {file = "multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6"}, @@ -2026,6 +2155,7 @@ version = "1.13.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, @@ -2079,6 +2209,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -2090,6 +2221,7 @@ version = "3.4.2" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -2109,6 +2241,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -2120,6 +2253,7 @@ version = "2.1.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, @@ -2182,6 +2316,7 @@ version = "1.4.6" description = "Universal reusable Python utils for the modern human." optional = false python-versions = "<3.13,>=3.10" +groups = ["main"] files = [ {file = "omnitils-1.4.6-py3-none-any.whl", hash = "sha256:47028f3e73c450715ca3b187534617fd7feecab50a6f19f0ff5ee6616c462d20"}, {file = "omnitils-1.4.6.tar.gz", hash = "sha256:b70b39c89f32dd038d858b4285dabb8c86e9c97b684f2af8be76236c633a58dc"}, @@ -2210,6 +2345,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -2221,6 +2357,7 @@ version = "0.5.7" description = "Divides large result sets into pages for easier browsing" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, @@ -2236,6 +2373,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -2247,6 +2385,7 @@ version = "3.2.1" description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pathvalidate-3.2.1-py3-none-any.whl", hash = "sha256:9a6255eb8f63c9e2135b9be97a5ce08f10230128c4ae7b3e935378b82b22c4c9"}, {file = "pathvalidate-3.2.1.tar.gz", hash = "sha256:f5d07b1e2374187040612a1fcd2bcb2919f8db180df254c9581bb90bf903377d"}, @@ -2263,6 +2402,8 @@ version = "2024.8.26" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, @@ -2274,6 +2415,7 @@ version = "0.22.4" description = "Python API for Photoshop." optional = false python-versions = ">=3.8,<4.0" +groups = ["main"] files = [ {file = "photoshop_python_api-0.22.4-py3-none-any.whl", hash = "sha256:18e206f615a648427c884daa2c0a4b02159cd461a1779a64d69e831730e4dff9"}, {file = "photoshop_python_api-0.22.4.tar.gz", hash = "sha256:ad080c61d4c850f71165dbf75c6fc23b8b0a1695d3192c0d2439e4101523e5bb"}, @@ -2289,6 +2431,7 @@ version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, @@ -2377,7 +2520,7 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -2386,6 +2529,7 @@ version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -2402,6 +2546,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2417,6 +2562,7 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -2435,6 +2581,7 @@ version = "3.0.36" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.6.2" +groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, @@ -2449,6 +2596,7 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -2556,6 +2704,7 @@ version = "1.10.2" description = "Python package for working with Adobe Photoshop PSD files" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "psd_tools-1.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0d905987d593b0a15647235418cf993ab1d228b7705fe2ebf3b9362af96b51e"}, {file = "psd_tools-1.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0f6c7ffa7685d174232aefbb39448b0b6aaeba9e85b28e0b1c16f0bc92890c"}, @@ -2624,6 +2773,7 @@ version = "6.1.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, @@ -2643,6 +2793,7 @@ files = [ {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, ] +markers = {main = "sys_platform != \"cygwin\""} [package.extras] dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] @@ -2654,6 +2805,7 @@ version = "0.22.0" description = "Pure python 7-zip library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "py7zr-0.22.0-py3-none-any.whl", hash = "sha256:993b951b313500697d71113da2681386589b7b74f12e48ba13cc12beca79d078"}, {file = "py7zr-0.22.0.tar.gz", hash = "sha256:c6c7aea5913535184003b73938490f9a4d8418598e533f9ca991d3b8e45a139e"}, @@ -2684,6 +2836,7 @@ version = "1.0.2" description = "bcj filter library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bff28d97e47047d69a4ac6bf59adda738cf1d00adde8819117fdb65d966bdbc"}, {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:198e0b4768b4025eb3309273d7e81dc53834b9a50092be6e0d9b3983cfd35c35"}, @@ -2738,6 +2891,7 @@ version = "2.7.6" description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pyclean-2.7.6-py3-none-any.whl", hash = "sha256:db5f74f6d2fedb79366e3260b260f5ab0053e1df28af14fc2c860800bf00fe96"}, {file = "pyclean-2.7.6.tar.gz", hash = "sha256:cde95ad4fbc194e970fd7ad009e82718f6c1d1309b195be9018d334d79661cca"}, @@ -2749,6 +2903,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2760,6 +2916,7 @@ version = "3.21.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] files = [ {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, @@ -2801,6 +2958,7 @@ version = "2.9.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, @@ -2813,7 +2971,7 @@ typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] [[package]] name = "pydantic-core" @@ -2821,6 +2979,7 @@ version = "2.23.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, @@ -2922,6 +3081,7 @@ version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, @@ -2936,6 +3096,7 @@ version = "5.13.2" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.13,>=3.7" +groups = ["dev"] files = [ {file = "pyinstaller-5.13.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:16cbd66b59a37f4ee59373a003608d15df180a0d9eb1a29ff3bfbfae64b23d0f"}, {file = "pyinstaller-5.13.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8f6dd0e797ae7efdd79226f78f35eb6a4981db16c13325e962a83395c0ec7420"}, @@ -2969,6 +3130,7 @@ version = "2024.9" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyinstaller_hooks_contrib-2024.9-py3-none-any.whl", hash = "sha256:1ddf9ba21d586afa84e505bb5c65fca10b22500bf3fdb89ee2965b99da53b891"}, {file = "pyinstaller_hooks_contrib-2024.9.tar.gz", hash = "sha256:4793869f370d1dc4806c101efd2890e3c3e703467d8d27bb5a3db005ebfb008d"}, @@ -2984,6 +3146,7 @@ version = "10.11.2" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf"}, {file = "pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049"}, @@ -3002,6 +3165,7 @@ version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, @@ -3016,6 +3180,8 @@ version = "223" description = "" optional = false python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"}, {file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"}, @@ -3030,6 +3196,7 @@ version = "1.1.0" description = "PPMd compression/decompression library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5cd428715413fe55abf79dc9fc54924ba7e518053e1fc0cbdf80d0d99cf1442"}, {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e96cc43f44b7658be2ea764e7fa99c94cb89164dbb7cdf209178effc2168319"}, @@ -3116,6 +3283,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -3138,6 +3306,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -3148,29 +3317,28 @@ six = ">=1.5" [[package]] name = "pywin32" -version = "308" +version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" -files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, +groups = ["main"] +files = [ + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] [[package]] @@ -3179,6 +3347,8 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -3190,6 +3360,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -3252,6 +3423,7 @@ version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, @@ -3266,6 +3438,7 @@ version = "0.16.2" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" +groups = ["main"] files = [ {file = "pyzstd-0.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:637376c8f8cbd0afe1cab613f8c75fd502bd1016bf79d10760a2d5a00905fe62"}, {file = "pyzstd-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e7a7118cbcfa90ca2ddbf9890c7cb582052a9a8cf2b7e2c1bbaf544bee0f16a"}, @@ -3358,6 +3531,7 @@ version = "2.0.1" description = "Python library to build pretty command line user prompts ⭐️" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"}, {file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"}, @@ -3372,6 +3546,7 @@ version = "2.2.1" description = "API rate limit decorator" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "ratelimit-2.2.1.tar.gz", hash = "sha256:af8a9b64b821529aca09ebaf6d8d279100d766f19e90b5059ac6a718ca6dee42"}, ] @@ -3382,6 +3557,7 @@ version = "2024.9.11" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, @@ -3485,6 +3661,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -3506,6 +3683,7 @@ version = "13.9.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, @@ -3525,6 +3703,7 @@ version = "0.18.6" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, @@ -3543,6 +3722,8 @@ version = "0.2.12" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, @@ -3550,6 +3731,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -3558,6 +3740,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -3566,6 +3749,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -3574,6 +3758,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -3582,6 +3767,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -3593,6 +3779,7 @@ version = "0.24.0" description = "Image processing in Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"}, {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"}, @@ -3630,7 +3817,7 @@ tifffile = ">=2022.8.12" [package.extras] build = ["Cython (>=3.0.4)", "build", "meson-python (>=0.15)", "ninja", "numpy (>=2.0.0rc1)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.8)", "wheel"] data = ["pooch (>=1.6.0)"] -developer = ["ipython", "pre-commit", "tomli"] +developer = ["ipython", "pre-commit", "tomli ; python_version < \"3.11\""] docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.15.2)", "pytest-doctestplus", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.6)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"] test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] @@ -3641,6 +3828,7 @@ version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, @@ -3683,7 +3871,7 @@ numpy = ">=1.23.5,<2.3" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" @@ -3691,6 +3879,7 @@ version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, @@ -3698,7 +3887,7 @@ files = [ [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf", "pytest-ruff ; sys_platform != \"cygwin\"", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -3707,6 +3896,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main", "dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -3718,6 +3908,7 @@ version = "5.0.1" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, @@ -3729,6 +3920,7 @@ version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -3740,6 +3932,7 @@ version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, @@ -3754,6 +3947,7 @@ version = "1.7.0" description = "module to create simple ASCII tables" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, @@ -3765,6 +3959,7 @@ version = "2024.9.20" description = "Read and write TIFF files" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "tifffile-2024.9.20-py3-none-any.whl", hash = "sha256:c54dc85bc1065d972cb8a6ffb3181389d597876aa80177933459733e4ed243dd"}, {file = "tifffile-2024.9.20.tar.gz", hash = "sha256:3fbf3be2f995a7051a8ae05a4be70c96fc0789f22ed6f1c4104c973cf68a640b"}, @@ -3787,6 +3982,7 @@ version = "2.0.2" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, @@ -3798,6 +3994,7 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -3809,6 +4006,7 @@ version = "4.66.5" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, @@ -3823,12 +4021,25 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "types-pywin32" +version = "310.0.0.20250516" +description = "Typing stubs for pywin32" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_pywin32-310.0.0.20250516-py3-none-any.whl", hash = "sha256:f9ef83a1ec3e5aae2b0e24c5f55ab41272b5dfeaabb9a0451d33684c9545e41a"}, + {file = "types_pywin32-310.0.0.20250516.tar.gz", hash = "sha256:91e5bfc033f65c9efb443722eff8101e31d690dd9a540fa77525590d3da9cc9d"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3840,13 +4051,14 @@ version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3857,6 +4069,7 @@ version = "20.27.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, @@ -3869,7 +4082,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchdog" @@ -3877,6 +4090,7 @@ version = "5.0.3" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, @@ -3919,6 +4133,7 @@ version = "10.0" description = "Wildcard/glob file name matcher." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, @@ -3933,6 +4148,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -3944,6 +4160,7 @@ version = "0.42.0" description = "A built-package format for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, @@ -3958,13 +4175,15 @@ version = "1.1.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] [package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] +dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "yarl" @@ -3972,6 +4191,7 @@ version = "1.16.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058"}, {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2"}, @@ -4063,6 +4283,6 @@ multidict = ">=4.0" propcache = ">=0.2.0" [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "5744170dc66674bba70eed965a8bae60ecc3f9e9fb4a36a18d755c097667284c" +content-hash = "c655e1888fdc9e79d2520f88895b813e0f92968100b59793006dacf1eefc55de" diff --git a/pyproject.toml b/pyproject.toml index 1c99008f..b91bffec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ omnitils = "^1.4.4" dynaconf = {extras = ["yaml"], version = "^3.2.6"} hexproof = "^0.3.7" rich = "^13.8.1" +pywin32 = "^310" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" @@ -63,6 +64,7 @@ mkdocs-same-dir = "^0.1.2" mkdocs-git-revision-date-plugin = "^0.3.2" mkdocstrings = {extras = ["python"], version = "^0.23.0"} memory-profiler = "^0.61.0" +types-pywin32 = "^310.0.0.20250516" [build-system] requires = ["poetry-core"] diff --git a/src/_config.py b/src/_config.py index 7de5bccd..71c8ad23 100644 --- a/src/_config.py +++ b/src/_config.py @@ -80,6 +80,7 @@ def update_definitions(self): # BASE - TEMPLATES self.exit_early = self.file.getboolean('BASE.TEMPLATES', 'Manual.Edit', fallback=False) + self.minimize_photoshop = self.file.getboolean('BASE.TEMPLATES', 'Minimize.Photoshop', fallback=False) self.import_scryfall_scan = self.file.getboolean('BASE.TEMPLATES', 'Import.Scryfall.Scan', fallback=False) self.border_color = self.get_option('BASE.TEMPLATES', 'Border.Color', BorderColor) diff --git a/src/console.py b/src/console.py index a4a7e487..d19ca2f9 100644 --- a/src/console.py +++ b/src/console.py @@ -1,13 +1,15 @@ """ * Console Module """ +from __future__ import annotations + # Standard Library import os import time from threading import Lock, Event, Thread from datetime import datetime as dt from functools import cached_property -from typing import Optional +from typing import Optional, TYPE_CHECKING # Third Party Imports from omnitils.enums import StrConstant @@ -15,8 +17,13 @@ from omnitils.metaclass import Singleton # Local Imports +from src import CFG from src._config import AppConfig from src._state import AppEnvironment, PATH +from src.utils.windows import WindowState + +if TYPE_CHECKING: + from src.utils.adobe import PhotoshopHandler """ * Enums @@ -332,7 +339,7 @@ def error( User Prompt Signals """ - def await_choice(self, thr: Event, msg: Optional[str] = None, end: str = "\n") -> bool: + def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: PhotoshopHandler | None = None) -> bool: """ Prompt the user to either continue or cancel. @param thr: Event object representing the status of the render thread. @@ -343,6 +350,9 @@ def await_choice(self, thr: Event, msg: Optional[str] = None, end: str = "\n") - # Clear other await procedures, then begin awaiting a user signal self.end_await() self.update(msg=msg or self.message_waiting, end=end) + if app: + # Show Photoshop in case it is minimized + app.set_window_state(WindowState.SHOWDEFAULT) response = input("[Y / Enter] Continue — [N] Cancel") # Signal the choice @@ -352,6 +362,10 @@ def await_choice(self, thr: Event, msg: Optional[str] = None, end: str = "\n") - # Cancel the current thread or continue based on user signal if thr: self.cancel_thread(thr) if not choice else self.start_await_cancel(thr) + + if self.running and app and CFG.minimize_photoshop: + app.set_window_state(WindowState.MINIMIZE) + return choice def signal(self, choice: bool): diff --git a/src/data/config/base.toml b/src/data/config/base.toml index cf742f28..602e845d 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -116,6 +116,12 @@ desc = """Pause the render process before saving to allow for manual edits.""" type = "bool" default = 0 +[TEMPLATES."Minimize.Photoshop"] +title = "Minimize Photoshop" +desc = """Minimize Photoshop when rendering starts. The rendering might be faster when Photoshop is minimized.""" +type = "bool" +default = 0 + [TEMPLATES."Import.Scryfall.Scan"] title = "Import Scryfall Scan" desc = """Will import the scryfall scan of each card into the document for reference. Useful for ensuring card accuracy when making manual changes.""" diff --git a/src/gui/console.py b/src/gui/console.py index 54136453..07ec20f4 100644 --- a/src/gui/console.py +++ b/src/gui/console.py @@ -1,13 +1,15 @@ """ CONSOLE MODULES """ +from __future__ import annotations + # Standard Library Imports import os import time import traceback from functools import cached_property from threading import Thread, Event, Lock -from typing import Optional, Any +from typing import Optional, Any, TYPE_CHECKING from datetime import datetime as dt # Third Party Imports @@ -17,10 +19,15 @@ from kivy.logger import Logger # Local Imports +from src import CFG from src._config import AppConfig from src._state import AppEnvironment, PATH from src.gui._state import get_root_app from src.gui.utils import HoverButton +from src.utils.windows import WindowState + +if TYPE_CHECKING: + from src.utils.adobe import PhotoshopHandler class GUIConsole(BoxLayout): @@ -248,7 +255,7 @@ def error( * User Prompt Signals """ - def await_choice(self, thr: Event, msg: Optional[str] = None, end: str = "\n") -> bool: + def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: PhotoshopHandler | None = None) -> bool: """Prompt the user to either continue or cancel. Args: @@ -263,11 +270,19 @@ def await_choice(self, thr: Event, msg: Optional[str] = None, end: str = "\n") - self.end_await() self.update(msg=msg or self.message_waiting, end=end) self.enable_buttons() + if app: + # Show Photoshop in case it is minimized + app.set_window_state(WindowState.SHOWDEFAULT) self.start_await() # Cancel the current thread or continue based on user signal if thr: self.cancel_thread(thr) if not self.running else self.start_await_cancel(thr) + + # Minimize Photoshop if the setting for that is active + if self.running and app and CFG.minimize_photoshop: + app.set_window_state(WindowState.MINIMIZE) + return self.running def signal(self, choice: bool) -> None: diff --git a/src/templates/_core.py b/src/templates/_core.py index 7720fd44..d5b316af 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -56,6 +56,7 @@ PS_EXCEPTIONS, ReferenceLayer, try_photoshop) +from src.utils.windows import WindowState """ * Template Classes @@ -1546,6 +1547,9 @@ def execute(self) -> bool: ): return False + if CFG.minimize_photoshop: + APP.set_window_state(WindowState.MINIMIZE) + # Pre-process layout data if not self.run_tasks( funcs=self.pre_render_methods, @@ -1624,7 +1628,7 @@ def execute(self) -> bool: # Manual edit step? if CFG.exit_early and not ENV.TEST_MODE: - self.console.await_choice(self.event) + self.console.await_choice(self.event, app=APP) # Save the document if not self.run_tasks( diff --git a/src/utils/adobe.py b/src/utils/adobe.py index ba16340c..cd05f39e 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -26,6 +26,7 @@ # Local Imports from src._state import AppEnvironment +from src.utils.windows import WindowState, get_window_handle_by_process_file_path_suffix, set_window_state """ * Types & Definitions @@ -52,7 +53,7 @@ OSError ) -PS_ERROR_CODES: dict[int: str] = { +PS_ERROR_CODES: dict[int, str] = { # --> COMError Messages that contain a message string # Response: "The message filter indicated that the application is busy." -2147417846: "Photoshop is currently busy, close any dialog boxes and stop any pending actions.", @@ -143,6 +144,15 @@ class PhotoshopHandler(ApplicationHandler): DIMS_800 = (2176, 2960) DIMS_600 = (1632, 2220) _instance = None + _window_handle: int | None = None + + @property + def window_handle(self) -> int | None: + if self._window_handle is None: + self._window_handle = get_window_handle_by_process_file_path_suffix( + "Photoshop.exe" + ) + return self._window_handle def __new__(cls, env: Optional[Any] = None) -> 'PhotoshopHandler': """Always return the same Photoshop Application instance on successive calls. @@ -178,8 +188,15 @@ def refresh_app(self): except Exception as e: # Photoshop is either busy or unresponsive return OSError(get_photoshop_error_message(e)) + + # Clear window handle as it might have changed + self._window_handle = None return + def set_window_state(self, state: WindowState) -> None: + if (handle := self.window_handle) is not None: + set_window_state(handle, state) + """ * Class Methods """ @@ -471,7 +488,7 @@ def bounds_no_effects(self) -> LayerBounds: """ @cached_property - def dims(self) -> type[LayerDimensions]: + def dims(self) -> LayerDimensions: """LayerDimensions: Returns dimensions of the layer (cached), including: - bounds (left, right, top, bottom) - height @@ -482,7 +499,7 @@ def dims(self) -> type[LayerDimensions]: return self.get_dimensions_from_bounds(self.bounds) @cached_property - def dims_no_effects(self) -> type[LayerDimensions]: + def dims_no_effects(self) -> LayerDimensions: """LayerDimensions: Returns dimensions of the layer (cached) without layer effects applied, including: - bounds (left, right, top, bottom) - height @@ -497,7 +514,7 @@ def dims_no_effects(self) -> type[LayerDimensions]: """ @staticmethod - def get_dimensions_from_bounds(bounds) -> type[LayerDimensions]: + def get_dimensions_from_bounds(bounds) -> LayerDimensions: """Compute width and height based on a set of bounds given. Args: diff --git a/src/utils/windows.py b/src/utils/windows.py new file mode 100644 index 00000000..393efaf6 --- /dev/null +++ b/src/utils/windows.py @@ -0,0 +1,75 @@ +from enum import IntEnum +import pywintypes +import win32api +import win32con +import win32gui +import win32process + + +# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow +class WindowState(IntEnum): + HIDE = win32con.SW_HIDE + NORMAL = win32con.SW_NORMAL + SHOWMINIMIZED = win32con.SW_SHOWMINIMIZED + MAXIMIZE = win32con.SW_MAXIMIZE + SHOWNOACTIVATE = win32con.SW_SHOWNOACTIVATE + SHOW = win32con.SW_SHOW + MINIMIZE = win32con.SW_MINIMIZE + SHOWMINNOACTIVE = win32con.SW_SHOWMINNOACTIVE + SHOWNA = win32con.SW_SHOWNA + RESTORE = win32con.SW_RESTORE + SHOWDEFAULT = win32con.SW_SHOWDEFAULT + FORCEMINIMIZE = win32con.SW_FORCEMINIMIZE + + +def set_window_state(window_handle: int, state: WindowState) -> None: + win32gui.ShowWindow(window_handle, state) + + +def get_window_handle_by_process_file_path_suffix(suffix: str) -> int | None: + """ + Tries to look up a window handle that belongs to a prcess that has a module + whose file path ends with suffix. + + Returns: + Window handle of a matching window or None if no matching window was found. + """ + extra: list[int] = [] + window_handles: list[int] = [] + + def enum_callback(handle: int, extra: list[int]): + window_handles.append(handle) + + win32gui.EnumWindows(enum_callback, extra) + for handle in window_handles: + if ( + # Try to skip windows that aren't visible in taskbar + not ( + win32gui.GetWindowLong(handle, win32con.GWL_STYLE) + & win32con.WS_EX_APPWINDOW + ) + # win32gui.GetParent(handle) + # or not win32gui.GetWindowText(handle) + ): + continue + + try: + _, process_id = win32process.GetWindowThreadProcessId(handle) + process_handle = win32api.OpenProcess( + win32con.PROCESS_QUERY_INFORMATION + win32con.PROCESS_VM_READ, + False, + process_id, + ) + try: + for module_handle in win32process.EnumProcessModules(process_handle): + process_file_path: str = win32process.GetModuleFileNameEx( + process_handle, module_handle + ) + if process_file_path.endswith(suffix): + return handle + finally: + win32api.CloseHandle(process_handle) + except pywintypes.error: + pass + except Exception as exc: + print("An exception occurred while getting Photoshop's window handle:", exc) From 4941daa057ab76605700d25db84ee520574a9c39 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 15 Jun 2025 15:22:08 +0300 Subject: [PATCH 021/190] feat(Photoshop): Simplify await_choice usage in relation to Photoshop minimization --- src/__init__.py | 2 +- src/console.py | 15 ++++++++------- src/gui/console.py | 13 +++++++------ src/templates/_core.py | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 8284cc6e..bc5d3407 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -51,7 +51,7 @@ from src.gui.console import GUIConsole as Console else: Console = TerminalConsole -CONSOLE = Console(cfg=CFG, env=ENV) +CONSOLE = Console(cfg=CFG, env=ENV, app=APP) # Global plugins and templates PLUGINS = get_all_plugins(con=CON, env=ENV) diff --git a/src/console.py b/src/console.py index d19ca2f9..6e899a6f 100644 --- a/src/console.py +++ b/src/console.py @@ -17,7 +17,6 @@ from omnitils.metaclass import Singleton # Local Imports -from src import CFG from src._config import AppConfig from src._state import AppEnvironment, PATH from src.utils.windows import WindowState @@ -164,12 +163,14 @@ class TerminalConsole: def __init__( self, cfg: AppConfig, - env: AppEnvironment + env: AppEnvironment, + app: PhotoshopHandler, ): # Establish global objects self.cfg: AppConfig = cfg self.env: AppEnvironment = env + self.app = app """ Logger Object Properties @@ -339,7 +340,7 @@ def error( User Prompt Signals """ - def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: PhotoshopHandler | None = None) -> bool: + def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: """ Prompt the user to either continue or cancel. @param thr: Event object representing the status of the render thread. @@ -350,9 +351,9 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: # Clear other await procedures, then begin awaiting a user signal self.end_await() self.update(msg=msg or self.message_waiting, end=end) - if app: + if self.cfg.minimize_photoshop and show_photoshop: # Show Photoshop in case it is minimized - app.set_window_state(WindowState.SHOWDEFAULT) + self.app.set_window_state(WindowState.SHOWDEFAULT) response = input("[Y / Enter] Continue — [N] Cancel") # Signal the choice @@ -363,8 +364,8 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: if thr: self.cancel_thread(thr) if not choice else self.start_await_cancel(thr) - if self.running and app and CFG.minimize_photoshop: - app.set_window_state(WindowState.MINIMIZE) + if choice and self.cfg.minimize_photoshop: + self.app.set_window_state(WindowState.MINIMIZE) return choice diff --git a/src/gui/console.py b/src/gui/console.py index 07ec20f4..b730d3dc 100644 --- a/src/gui/console.py +++ b/src/gui/console.py @@ -19,7 +19,6 @@ from kivy.logger import Logger # Local Imports -from src import CFG from src._config import AppConfig from src._state import AppEnvironment, PATH from src.gui._state import get_root_app @@ -42,12 +41,14 @@ def __init__( self, cfg: AppConfig, env: AppEnvironment, + app: PhotoshopHandler, **kwargs ): # Establish global objects super().__init__(**kwargs) self.cfg = cfg self.env = env + self.app = app # Test mode uses larger console if not self.env.TEST_MODE: @@ -255,7 +256,7 @@ def error( * User Prompt Signals """ - def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: PhotoshopHandler | None = None) -> bool: + def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: """Prompt the user to either continue or cancel. Args: @@ -270,9 +271,9 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: self.end_await() self.update(msg=msg or self.message_waiting, end=end) self.enable_buttons() - if app: + if self.cfg.minimize_photoshop and show_photoshop: # Show Photoshop in case it is minimized - app.set_window_state(WindowState.SHOWDEFAULT) + self.app.set_window_state(WindowState.SHOWDEFAULT) self.start_await() # Cancel the current thread or continue based on user signal @@ -280,8 +281,8 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", app: self.cancel_thread(thr) if not self.running else self.start_await_cancel(thr) # Minimize Photoshop if the setting for that is active - if self.running and app and CFG.minimize_photoshop: - app.set_window_state(WindowState.MINIMIZE) + if self.running and self.cfg.minimize_photoshop: + self.app.set_window_state(WindowState.MINIMIZE) return self.running diff --git a/src/templates/_core.py b/src/templates/_core.py index d5b316af..df0fa284 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -1628,7 +1628,7 @@ def execute(self) -> bool: # Manual edit step? if CFG.exit_early and not ENV.TEST_MODE: - self.console.await_choice(self.event, app=APP) + self.console.await_choice(self.event) # Save the document if not self.run_tasks( From 5d6dbd4f87d666636a8420b2a7e27bb0cc9424e4 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 21 Jun 2025 13:41:34 +0300 Subject: [PATCH 022/190] feat(NormalLayout): Improve robustness of card face matching --- src/layouts.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index 19705915..6f6fac1c 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from datetime import date, datetime -from typing import Optional, Match, Union, Type, ForwardRef +from typing import Any, Optional, Match, Union, Type, ForwardRef from os import path as osp from pathlib import Path from functools import cached_property @@ -14,7 +14,7 @@ # Local Imports from src import CFG, CON, CONSOLE, ENV, PATH from src.cards import CardDetails, FrameDetails, get_card_data, parse_card_info, process_card_data -from src.console import msg_error, msg_success +from src.console import msg_error, msg_success, msg_warn from src.utils.hexapi import get_watermark_svg, get_watermark_svg_from_set from src.utils.scryfall import get_cards_oracle from src.enums.layers import LAYERS @@ -213,10 +213,27 @@ def date(self) -> date: @cached_property def card(self) -> dict: """Main card data object to pull most relevant data from.""" - for i, face in enumerate(self.scryfall.get('card_faces', [])): + if faces := self.scryfall.get('card_faces', []): # Card with multiple faces, first index is always front side - if normalize_str(face['name']) == normalize_str(self.input_name): - return face + matching_face: dict[str, Any] | None = None + + for face in faces: + if normalize_str(face['name']) == normalize_str(self.input_name): + matching_face = face + break + + if not matching_face: + CONSOLE.update( + msg_warn( + f"""None of the card faces +{'\n'.join((f" - {face['name']}" for face in faces))} +matches the input file's name '{self.input_name}'. +Defaulting to first face.""" + ) + ) + matching_face = faces[0] + + return matching_face # Treat single face cards as front return self.scryfall From 0086a178b4d9f1efac93f62d9be378cd43335f81 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 12 Jul 2025 19:05:15 +0300 Subject: [PATCH 023/190] feat(Station): Add support for Station layout --- src/cards.py | 4 + src/enums/layers.py | 5 + src/enums/mtg.py | 6 +- src/helpers/bounds.py | 31 +++-- src/layouts.py | 62 +++++++++- src/templates/__init__.py | 1 + src/templates/station.py | 245 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 src/templates/station.py diff --git a/src/cards.py b/src/cards.py index 8cd9db0a..351fd4ab 100644 --- a/src/cards.py +++ b/src/cards.py @@ -214,6 +214,10 @@ def process_card_data(data: dict, card: CardDetails) -> dict: data['layout'] = 'planeswalker' return data + # Check for Station layout + if 'STATION ' in data.get('oracle_text', ''): + data['layout'] = 'station' + # Return updated data return data diff --git a/src/enums/layers.py b/src/enums/layers.py index 816e9922..425a3dd0 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -76,6 +76,7 @@ class LAYERS (StrConstant): FULL = 'Full' NORMAL = 'Normal' SNOW = 'Snow' + LEVEL = 'Level' # Borders BORDER = 'Border' @@ -249,3 +250,7 @@ class LAYERS (StrConstant): # Battles DEFENSE = 'Defense' DEFENSE_REFERENCE = 'Defense Reference' + + # Station + STATION = 'Station' + REQUIREMENT = 'Requirement' diff --git a/src/enums/mtg.py b/src/enums/mtg.py index a65c8955..1e34d861 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -29,6 +29,7 @@ class LayoutCategory(StrConstant): Prototype = 'Prototype' Saga = 'Saga' Split = 'Split' + Station = 'Station' Token = 'Token' Transform = 'Transform' @@ -52,6 +53,7 @@ class LayoutType(StrConstant): Prototype = 'prototype' Saga = 'saga' Split = 'split' + Station = 'station' TransformBack = 'transform_back' TransformFront = 'transform_front' @@ -86,6 +88,7 @@ class LayoutScryfall(StrConstant): Planeswalker = 'planeswalker' PlaneswalkerMDFC = 'planeswalker_mdfc' PlaneswalkerTransform = 'planeswalker_tf' + Station = 'station' """Maps Layout categories to a list of equivalent Layout types.""" @@ -104,7 +107,8 @@ class LayoutScryfall(StrConstant): LayoutCategory.Leveler: [LayoutType.Leveler], LayoutCategory.Split: [LayoutType.Split], LayoutCategory.Battle: [LayoutType.Battle], - LayoutCategory.Planar: [LayoutType.Planar] + LayoutCategory.Planar: [LayoutType.Planar], + LayoutCategory.Station: [LayoutType.Station], } """Maps Layout types to their equivalent Layout category.""" diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index a95d2d07..b053932a 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -14,6 +14,7 @@ from src import APP from src.helpers.descriptors import get_layer_action_ref from src.helpers.document import undo_action +from src.helpers.layers import duplicate_group, select_layer from src.utils.adobe import PS_EXCEPTIONS # QOL Definitions @@ -24,8 +25,8 @@ * Types """ -# Layer bounds: left, top, right, bottom LayerBounds = tuple[int, int, int, int] +"""left, top, right, bottom""" class LayerDimensions(TypedDict): @@ -51,7 +52,7 @@ class TextboxDimensions(TypedDict): """ -def get_dimensions_from_bounds(bounds: LayerBounds) -> type[LayerDimensions]: +def get_dimensions_from_bounds(bounds: LayerBounds) -> LayerDimensions: """Compute width and height based on a set of bounds given. Args: @@ -71,7 +72,7 @@ def get_dimensions_from_bounds(bounds: LayerBounds) -> type[LayerDimensions]: top=int(bounds[1]), bottom=int(bounds[3])) -def get_layer_dimensions(layer: Union[ArtLayer, LayerSet]) -> type[LayerDimensions]: +def get_layer_dimensions(layer: Union[ArtLayer, LayerSet]) -> LayerDimensions: """Compute the width and height dimensions of a layer. Args: @@ -83,6 +84,22 @@ def get_layer_dimensions(layer: Union[ArtLayer, LayerSet]) -> type[LayerDimensio return get_dimensions_from_bounds(layer.bounds) +def get_group_dimensions(group: LayerSet) -> LayerDimensions: + """ + Compute the dimensions of a group. + + Uses a workaround to avoid erroneous dimensions, which might occur + when the group contains shapes. + """ + select_layer(group) + group_copy = duplicate_group(group.name) + group_copy.visible = True + merged = group_copy.merge() + dims = get_layer_dimensions(merged) + merged.remove() + return dims + + def get_layer_width(layer: Union[ArtLayer, LayerSet]) -> Union[float, int]: """Returns the width of a given layer. @@ -140,7 +157,7 @@ def get_bounds_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerBounds: return layer.bounds -def get_dimensions_no_effects(layer: Union[ArtLayer, LayerSet]) -> type[LayerDimensions]: +def get_dimensions_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerDimensions: """Compute the dimensions of a layer without its effects applied. Args: @@ -153,7 +170,7 @@ def get_dimensions_no_effects(layer: Union[ArtLayer, LayerSet]) -> type[LayerDim return get_dimensions_from_bounds(bounds) -def get_width_no_effects(layer: Union[ArtLayer, LayerSet]) -> int: +def get_width_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: """Returns the width of a given layer without its effects applied. Args: @@ -170,7 +187,7 @@ def get_width_no_effects(layer: Union[ArtLayer, LayerSet]) -> int: return get_layer_width(layer) -def get_height_no_effects(layer: Union[ArtLayer, LayerSet]) -> int: +def get_height_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: """Returns the height of a given layer without its effects applied. Args: @@ -230,7 +247,7 @@ def get_textbox_bounds(layer: ArtLayer) -> LayerBounds: ) -def get_textbox_dimensions(layer: ArtLayer) -> type[TextboxDimensions]: +def get_textbox_dimensions(layer: ArtLayer) -> TextboxDimensions: """Get the dimensions of a TextLayer's bounding box. Args: diff --git a/src/layouts.py b/src/layouts.py index 19705915..2312fe19 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -3,7 +3,8 @@ """ # Standard Library Imports from datetime import date, datetime -from typing import Optional, Match, Union, Type, ForwardRef +import re +from typing import NotRequired, Optional, Match, TypedDict, Union, Type, ForwardRef from os import path as osp from pathlib import Path from functools import cached_property @@ -35,6 +36,12 @@ check_hybrid_mana_cost, get_mana_cost_colors) + +class PowerToughness(TypedDict): + power: str + toughness: str + + """ * Layout Processing """ @@ -1524,6 +1531,58 @@ def card_count(self) -> Optional[int]: return self.set_data.get('count_tokens', None) +class StationDetails(TypedDict): + requirement: str + ability: str + pt: NotRequired[PowerToughness] + + +class StationLayout(NormalLayout): + _pt_pattern = re.compile(r"([0-9]+)/([0-9]+)") + + card_class: str = LayoutType.Station + + @cached_property + def oracle_text_unprocessed(self) -> str: + """Unaltered oracle text.""" + return ( + self.card.get("printed_text", self.oracle_text_raw) + if self.is_alt_lang + else self.oracle_text_raw + ) + + @cached_property + def oracle_text(self) -> str: + """Oracle text with station levels stripped.""" + stations_start = self.oracle_text_unprocessed.index("\nSTATION ") + return self.oracle_text_unprocessed[0:stations_start] + + @cached_property + def stations(self) -> list[StationDetails]: + stations_start = self.oracle_text_unprocessed.index("\nSTATION ") + station_splits = self.oracle_text_unprocessed[stations_start:].split("STATION ") + out: list[StationDetails] = [] + for split in station_splits: + if stripped := split.strip(): + lines = stripped.split("\n") + + details: StationDetails = {"requirement": lines.pop(0), "ability": ""} + + for line in lines: + if match := self._pt_pattern.match(line): + details["pt"] = { + "power": match[1], + "toughness": match[2] + } + else: + details["ability"] += line + "\n" + + details["ability"] = details["ability"].strip() + + out.append(details) + return out + + """ * Types & Enums """ @@ -1575,6 +1634,7 @@ def card_count(self) -> Optional[int]: LayoutScryfall.Planeswalker: PlaneswalkerLayout, LayoutScryfall.PlaneswalkerMDFC: PlaneswalkerMDFCLayout, LayoutScryfall.PlaneswalkerTransform: PlaneswalkerTransformLayout, + LayoutScryfall.Station: StationLayout, # TODO: Supported by Scryfall, not implemented LayoutScryfall.Flip: TransformLayout, diff --git a/src/templates/__init__.py b/src/templates/__init__.py index 42fc6b49..55cb150a 100644 --- a/src/templates/__init__.py +++ b/src/templates/__init__.py @@ -18,3 +18,4 @@ from src.templates.prototype import * from src.templates.planar import * from src.templates.split import * +from src.templates.station import * diff --git a/src/templates/station.py b/src/templates/station.py new file mode 100644 index 00000000..e65fac11 --- /dev/null +++ b/src/templates/station.py @@ -0,0 +1,245 @@ +from functools import cached_property +from typing import Callable + +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet + +from src.enums.layers import LAYERS +from src.helpers.bounds import ( + get_group_dimensions, + get_layer_dimensions, + get_layer_height, +) +from src.helpers.layers import duplicate_group, getLayer, getLayerSet, select_layer +from src.helpers.position import spread_layers_over_reference +from src.helpers.text import scale_text_layers_to_height +from src.layouts import StationLayout +from src.templates._core import NormalTemplate +from src.text_layers import FormattedTextField + + +class StationMod(NormalTemplate): + """ + A template modifier for Station cards introduced in Edge of Eternities. + + Adds: + * Station requirement, ability and P/T texts and shapes. + """ + + # region Checks + + @cached_property + def is_station(self) -> bool: + """Checks if this card uses Station layout""" + return isinstance(self.layout, StationLayout) + + @cached_property + def is_centered(self) -> bool: + if self.is_station: + return False + return super().is_centered + + # endregion Checks + + # region Options + + @cached_property + def rules_text_gap(self) -> int | float: + return 64 + + # endregion Options + + # region Groups + + @cached_property + def station_group(self) -> LayerSet | None: + return getLayerSet(LAYERS.STATION) + + @cached_property + def station_level_base_group(self) -> LayerSet | None: + return getLayerSet(LAYERS.LEVEL, self.station_group) + + @cached_property + def station_level_groups(self) -> list[LayerSet]: + groups: list[LayerSet] = [] + if self.station_level_base_group: + if isinstance(self.layout, StationLayout): + for i in range(len(self.layout.stations) - 1): + select_layer(self.station_level_base_group) + groups.append( + duplicate_group(f"{self.station_level_base_group.name} {i}") + ) + groups.append(self.station_level_base_group) + return groups + + @cached_property + def station_requirement_groups(self) -> list[LayerSet]: + groups: list[LayerSet] = [] + for level_group in self.station_level_groups: + if group := getLayerSet(LAYERS.REQUIREMENT, level_group): + groups.append(group) + return groups + + @cached_property + def station_pt_groups(self) -> list[LayerSet]: + groups: list[LayerSet] = [] + for level_group in self.station_level_groups: + if group := getLayerSet(LAYERS.PT_BOX, level_group): + groups.append(group) + return groups + + # endregion Groups + + # region Text layers + + @cached_property + def station_requirement_text_layers(self) -> list[ArtLayer]: + layers: list[ArtLayer] = [] + for requirement_group in self.station_requirement_groups: + if layer := getLayer(LAYERS.TEXT, requirement_group): + layers.append(layer) + return layers + + @cached_property + def station_level_text_layers(self) -> list[ArtLayer]: + layers: list[ArtLayer] = [] + if isinstance(self.layout, StationLayout): + for details, level_group in zip( + self.layout.stations, self.station_level_groups + ): + if layer := getLayer( + LAYERS.RULES_TEXT_CREATURE + if "pt" in details + else LAYERS.RULES_TEXT, + level_group, + ): + layers.append(layer) + return layers + + @cached_property + def station_pt_text_layers(self) -> list[ArtLayer]: + layers: list[ArtLayer] = [] + for pt_group in self.station_pt_groups: + if layer := getLayer(LAYERS.TEXT, pt_group): + layers.append(layer) + return layers + + # endregion Text layers + + # region Mixin methods + + @cached_property + def text_layer_methods(self) -> list[Callable[[], None]]: + """Add Station text layers.""" + funcs = super().text_layer_methods + if self.is_station: + funcs.append(self.text_layers_station) + return funcs + + @cached_property + def frame_layer_methods(self) -> list[Callable[[], None]]: + """Add Station frame layers.""" + funcs = super().frame_layer_methods + if self.is_station: + funcs.append(self.frame_layers_station) + return funcs + + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: + """Position Station abilities.""" + funcs = super().post_text_methods + if self.is_station: + funcs.append(self.layer_positioning_station) + return funcs + + # endregion Mixin methods + + # region Text layer methods + + def text_layers_station(self) -> None: + """Add and modify text layers relating to Station cards.""" + if isinstance(self.layout, StationLayout): + for details, ability, requirement, pt in zip( + self.layout.stations, + self.station_level_text_layers, + self.station_requirement_text_layers, + self.station_pt_text_layers, + ): + self.text.append( + FormattedTextField(layer=ability, contents=details["ability"]) + ) + requirement.textItem.contents = details["requirement"] + if "pt" in details: + pt.textItem.contents = ( + f"{details['pt']['power']}/{details['pt']['toughness']}" + ) + + # endregion Text layer methods + + # region Frame layer methods + + def frame_layers_station(self) -> None: + """Enable frame layers required by Station cards.""" + if self.station_group: + self.station_group.visible = True + + if isinstance(self.layout, StationLayout): + for details, group in zip(self.layout.stations, self.station_pt_groups): + if "pt" in details: + group.visible = True + + # endregion Frame layer methods + + # region Positioning methods + + def align_center_ys(self, group: LayerSet, ref: ArtLayer) -> None: + dims = get_group_dimensions(group) + dims_ref = get_layer_dimensions(ref) + group.translate(0, dims_ref["center_y"] - dims["center_y"]) + + def layer_positioning_station(self) -> None: + """Positions and sizes Station ability layers.""" + if ( + isinstance(self.layout, StationLayout) + and self.textbox_reference + and self.text_layer_rules + ): + spacing = self.app.scale_by_dpi(self.rules_text_gap) + spaces = len(self.layout.stations) + 1 + ref_height = self.textbox_reference.dims["height"] + spacing_total = spaces * spacing + spacing + total_height = ref_height - spacing_total + + ability_layers = [self.text_layer_rules, *self.station_level_text_layers] + + # Resize text items till they fit in the available space + scale_text_layers_to_height( + text_layers=ability_layers, + ref_height=total_height, + ) + + # Get the exact gap between each layer left over + layer_heights = sum([get_layer_height(lyr) for lyr in ability_layers]) + gap = (ref_height - layer_heights) * (spacing / spacing_total) + inside_gap = (ref_height - layer_heights) * (spacing / spacing_total) + + # Space lines evenly apart + spread_layers_over_reference( + layers=ability_layers, + ref=self.textbox_reference, + gap=gap, + inside_gap=inside_gap, + ) + + # Shift requirement and pt elements + for details, level_text, requirement, pt in zip( + self.layout.stations, + self.station_level_text_layers, + self.station_requirement_groups, + self.station_pt_groups, + ): + self.align_center_ys(requirement, level_text) + if "pt" in details: + self.align_center_ys(pt, level_text) + + # endregion Positioning methods From 759a92510321f9db811da9be08de876aed20dce1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 21 Jul 2025 18:39:37 +0300 Subject: [PATCH 024/190] feat(TemplateModule): Show all category tabs irrespective of the amount of templates --- src/gui/tabs/main.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/gui/tabs/main.py b/src/gui/tabs/main.py index b0ae14cf..b06cf234 100644 --- a/src/gui/tabs/main.py +++ b/src/gui/tabs/main.py @@ -58,10 +58,6 @@ def __init__(self, **kwargs): # Get the template map for this category templates: TemplateCategoryMap = self.main.template_map[cat] - # If less than 2 templates exist for this category, skip it - if len(templates['names']) < 2: - continue - # Add a tab for this category tab = DynamicTabItem(text=cat) tab.content = self.get_template_container( From e9dc46a8c9bb0731f495dbca8d1c732b1581940e Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 11:32:54 +0300 Subject: [PATCH 025/190] feat: Adds an option to open Scryfall data in a text editor for manual modification --- src/_config.py | 12 +++++++----- src/data/config/app.toml | 12 ++++++++++++ src/layouts.py | 13 +++++++++++-- src/utils/manual_actions.py | 24 ++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/utils/manual_actions.py diff --git a/src/_config.py b/src/_config.py index 7de5bccd..1105c1cc 100644 --- a/src/_config.py +++ b/src/_config.py @@ -48,6 +48,8 @@ def update_definitions(self): self.scry_ascending = self.file.getboolean('APP.DATA', 'Scryfall.Ascending', fallback=False) self.scry_extras = self.file.getboolean('APP.DATA', 'Scryfall.Extras', fallback=False) self.scry_unique = self.get_option('APP.DATA', 'Scryfall.Unique', ScryfallUnique) + self.manually_edit_card_data = self.file.getboolean('APP.DATA', 'Manually.Edit.Card.Data', fallback=False) + self.manual_text_editor = self.file.get('APP.DATA', 'Manual.Text.Editor', fallback='notepad "{}"') # APP - TEXT self.force_english_formatting = self.file.getboolean('APP.TEXT', "Force.English.Formatting", fallback=False) @@ -87,7 +89,7 @@ def update_definitions(self): * Setting Utils """ - def get_option(self, section: str, key: str, enum_class: type[StrConstant], default: str = None) -> str: + def get_option(self, section: str, key: str, enum_class: type[StrConstant], default: str | None = None) -> str: """Returns the current value of an "options" setting if that option exists in its StrEnum class. Otherwise, returns the default value of that StrEnum class. @@ -100,14 +102,14 @@ def get_option(self, section: str, key: str, enum_class: type[StrConstant], defa Returns: Validated current value, or default value. """ - default = default or enum_class.Default + defa = default or enum_class.Default if self.file.has_section(section): - option = self.file[section].get(key, fallback=default) + option = self.file[section].get(key, fallback=defa) if option in enum_class: return option - return default + return defa - def get_setting(self, section: str, key: str, default: Optional[str] = None, is_bool: bool = True): + def get_setting(self, section: str, key: str, default: str | bool | None = None, is_bool: bool = True): """Check if the setting exists and return it. Default will be returned if missing. Args: diff --git a/src/data/config/app.toml b/src/data/config/app.toml index d5223e21..a6893ee1 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -70,6 +70,18 @@ type = "options" default = "arts" options = ["arts", "prints"] +[DATA."Manually.Edit.Card.Data"] +title = "Manually Edit Card Data" +desc = """At the start of the render process, will pass the Scryfall data to a text editor for manual modification. Save the file and close the editor to proceed. This can be used, e.g., to fix incorrect Scryfall data or for creating custom cards.""" +type = "bool" +default = 0 + +[DATA."Manual.Text.Editor"] +title = "Manual Text Editor" +desc = """Text editor to use for the manual card data editing step. Add curly brackets {} where you wish for the file path to be slotted in the command, e.g. Visual Studio Code can be used with [b]C:\\full\\path\\to\\Code.exe -w "{}"[/b].""" +type = "string" +default = 'notepad "{}"' + ### # * Text Settings ### diff --git a/src/layouts.py b/src/layouts.py index 19705915..e4ed6b5c 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from datetime import date, datetime -from typing import Optional, Match, Union, Type, ForwardRef +from typing import Optional, Match, Union, Type from os import path as osp from pathlib import Path from functools import cached_property @@ -16,6 +16,7 @@ from src.cards import CardDetails, FrameDetails, get_card_data, parse_card_info, process_card_data from src.console import msg_error, msg_success from src.utils.hexapi import get_watermark_svg, get_watermark_svg_from_set +from src.utils.manual_actions import manually_modify_dict from src.utils.scryfall import get_cards_oracle from src.enums.layers import LAYERS from src.enums.mtg import ( @@ -40,7 +41,7 @@ """ -def assign_layout(filename: Path) -> str | ForwardRef('CardLayout'): +def assign_layout(filename: Path) -> "str | CardLayout": """Assign layout object to a card. Args: @@ -65,6 +66,14 @@ def assign_layout(filename: Path) -> str | ForwardRef('CardLayout'): scryfall = get_card_data(card, cfg=CFG, logger=CONSOLE) if not scryfall: return msg_error(name_failed, reason="Scryfall search failed") + + if CFG.get_setting('BASE.TEMPLATES', 'Manually.Edit.Card.Data', default=False): + try: + scryfall = manually_modify_dict(scryfall, CFG.manual_text_editor) + except Exception as e: + CONSOLE.log_exception(e) + return msg_error(name_failed, reason="Manual card data modification failed") + scryfall = process_card_data(scryfall, card) # Instantiate layout object diff --git a/src/utils/manual_actions.py b/src/utils/manual_actions.py new file mode 100644 index 00000000..cba8583b --- /dev/null +++ b/src/utils/manual_actions.py @@ -0,0 +1,24 @@ +from json import dump, load +from os import unlink +import subprocess +from tempfile import NamedTemporaryFile +from typing import Any + + +def manually_modify_dict( + data: dict[str, Any], text_editing_program: str = 'notepad "{}"' +) -> dict[str, Any]: + tmp = NamedTemporaryFile( + "w", + prefix="Proxyshop_manual_dict_modification_", + encoding="UTF-8", + delete=False, + ) + try: + dump(data, tmp, ensure_ascii=False, indent=2) + tmp.close() + subprocess.run((text_editing_program.format(tmp.name)), check=True) + with open(tmp.name, "r", encoding="UTF-8") as f: + return load(f) + finally: + unlink(tmp.name) From 5d965c107bf1893bcb77ed48ca6b3a26b83db3e7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 16 Aug 2025 19:15:59 +0300 Subject: [PATCH 026/190] fix(assign_layout): Fix manual card data edit being always activated --- src/layouts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index e4ed6b5c..0fc4b8e6 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -67,7 +67,7 @@ def assign_layout(filename: Path) -> "str | CardLayout": if not scryfall: return msg_error(name_failed, reason="Scryfall search failed") - if CFG.get_setting('BASE.TEMPLATES', 'Manually.Edit.Card.Data', default=False): + if CFG.manually_edit_card_data: try: scryfall = manually_modify_dict(scryfall, CFG.manual_text_editor) except Exception as e: From e3269a6959b860d70fd2867879b3eca9a3e1db5b Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 24 Aug 2025 12:14:47 +0300 Subject: [PATCH 027/190] feat: Add stricter type annotations to most of the codebase excluding GUI code In order to make the code compliant with stricter typing many smallish functional changes also had to be done. BREAKING CHANGE: --- main.py | 55 +- .../Investigamer/py/actions/pencilsketch.py | 20 +- plugins/Investigamer/py/templates.py | 164 +- plugins/SilvanMTG/py/templates.py | 26 +- src/__init__.py | 12 +- src/_config.py | 230 +- src/_loader.py | 679 +++--- src/_state.py | 83 +- src/cards.py | 61 +- src/commands/build.py | 8 +- src/commands/files.py | 3 +- src/commands/test/compression.py | 3 +- src/commands/test/frame_logic.py | 4 +- src/commands/test/text_logic.py | 4 +- src/commands/test/utility.py | 43 +- src/console.py | 43 +- src/enums/adobe.py | 65 +- src/enums/mtg.py | 44 +- src/frame_logic.py | 30 +- src/gui/app.py | 111 +- src/gui/console.py | 14 +- src/gui/popup/settings.py | 16 +- src/gui/tabs/main.py | 5 +- src/gui/tabs/tools.py | 2 +- src/gui/utils/behaviors.py | 4 +- src/gui/utils/layouts.py | 4 +- src/helpers/actions.py | 2 +- src/helpers/adjustments.py | 46 +- src/helpers/bounds.py | 58 +- src/helpers/colors.py | 118 +- src/helpers/descriptors.py | 5 +- src/helpers/design.py | 107 +- src/helpers/document.py | 106 +- src/helpers/effects.py | 71 +- src/helpers/layers.py | 96 +- src/helpers/masks.py | 70 +- src/helpers/position.py | 133 +- src/helpers/selection.py | 44 +- src/helpers/text.py | 99 +- src/layouts.py | 325 +-- src/schema/adobe.py | 42 +- src/schema/colors.py | 298 +-- src/templates/_core.py | 955 +++++---- src/templates/_cosmetic.py | 122 +- src/templates/_vector.py | 301 ++- src/templates/adventure.py | 297 +-- src/templates/basic_land.py | 70 - src/templates/battle.py | 131 +- src/templates/case.py | 136 +- src/templates/classes.py | 360 ++-- src/templates/leveler.py | 149 +- src/templates/mdfc.py | 35 +- src/templates/mutate.py | 23 +- src/templates/normal.py | 1869 +++++++++++------ src/templates/planar.py | 85 +- src/templates/planeswalker.py | 352 ++-- src/templates/prototype.py | 112 +- src/templates/saga.py | 477 +++-- src/templates/split.py | 39 +- src/templates/station.py | 10 +- src/templates/token.py | 151 +- src/templates/transform.py | 101 +- src/text_layers.py | 405 ++-- src/utils/adobe.py | 106 +- src/utils/build.py | 19 +- src/utils/download.py | 4 +- src/utils/fonts.py | 45 +- src/utils/hexapi.py | 60 +- src/utils/scryfall.py | 247 ++- 69 files changed, 5830 insertions(+), 4184 deletions(-) delete mode 100644 src/templates/basic_land.py diff --git a/main.py b/main.py index 2c7f01fc..2f959dda 100644 --- a/main.py +++ b/main.py @@ -13,8 +13,9 @@ def launch_cli(): """Launch the app in CLI mode.""" - # Enable headless mode, remove cli marker + # Enable headless mode os.environ['PROXYSHOP_HEADLESS'] = '1' + # Remove cli marker if 'cli' in sys.argv: sys.argv.remove('cli') @@ -28,37 +29,41 @@ def launch_cli(): def launch_gui(): """Launch the app in GUI mode.""" - # Local Imports + # Disable headless mode os.environ['PROXYSHOP_HEADLESS'] = '0' + + # Local Imports from src import ( APP, CFG, CON, CONSOLE, ENV, PLUGINS, TEMPLATES, TEMPLATE_MAP, TEMPLATE_DEFAULTS) from src.gui._state import register_kv_classes, load_kv_config from src.gui.app import ProxyshopGUIApp + from src.gui.console import GUIConsole + + if isinstance(CONSOLE, GUIConsole): + # Kivy Imported Last + from kivy.resources import resource_add_path + + # Kivy packaging for PyInstaller + if hasattr(sys, '_MEIPASS'): + resource_add_path(os.path.join(sys._MEIPASS)) + + # Load KV files and utilities + load_kv_config() + register_kv_classes() - # Kivy Imported Last - from kivy.resources import resource_add_path - - # Kivy packaging for PyInstaller - if hasattr(sys, '_MEIPASS'): - resource_add_path(os.path.join(sys._MEIPASS)) - - # Load KV files and utilities - load_kv_config() - register_kv_classes() - - # Run the GUI application - ProxyshopGUIApp( - app=APP, - con=CON, - cfg=CFG, - env=ENV, - console=CONSOLE, - plugins=PLUGINS, - templates=TEMPLATES, - template_map=TEMPLATE_MAP, - templates_default=TEMPLATE_DEFAULTS, - ).run() + # Run the GUI application + ProxyshopGUIApp( + app=APP, + con=CON, + cfg=CFG, + env=ENV, + console=CONSOLE, + plugins=PLUGINS, + templates=TEMPLATES, + template_map=TEMPLATE_MAP, + templates_default=TEMPLATE_DEFAULTS, + ).run() if __name__ == '__main__': diff --git a/plugins/Investigamer/py/actions/pencilsketch.py b/plugins/Investigamer/py/actions/pencilsketch.py index e2ad5e03..000dddd8 100644 --- a/plugins/Investigamer/py/actions/pencilsketch.py +++ b/plugins/Investigamer/py/actions/pencilsketch.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from threading import Event -from typing import Union +from collections.abc import Iterable # Third Party Imports import photoshop.api as ps @@ -49,7 +49,7 @@ def reset_colors(): APP.executeAction(cID('Rset'), desc1, dialog_mode) -def move_layer(pos, index: Union[int, list[int]]): +def move_layer(pos: int, index: int | list[int]): if isinstance(index, int): index = [index] desc1 = ps.ActionDescriptor() @@ -118,7 +118,7 @@ def auto_contrast(): APP.executeAction(cID('Lvls'), desc1, dialog_mode) -def hide_layer(name=None): +def hide_layer(name: str | None = None): desc1 = ps.ActionDescriptor() list1 = ps.ActionList() ref1 = ps.ActionReference() @@ -131,7 +131,7 @@ def hide_layer(name=None): APP.executeAction(cID('Hd '), desc1, dialog_mode) -def show_layer(name=None): +def show_layer(name: str | None = None): desc1 = ps.ActionDescriptor() list1 = ps.ActionList() ref1 = ps.ActionReference() @@ -144,7 +144,7 @@ def show_layer(name=None): APP.executeAction(cID('Shw '), desc1, dialog_mode) -def delete_layers(layers): +def delete_layers(layers: Iterable[int]): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) @@ -161,7 +161,7 @@ def delete_layers(layers): """ -def blend(key): +def blend(key: str): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) @@ -201,7 +201,13 @@ def blend_overlay(): blend('overlay') def blend_color(): blend('color') -def run(thr: Event, draft_sketch=False, rough_sketch=False, black_and_white=True, manual_editing=False): +def run( + thr: Event, + draft_sketch: bool = False, + rough_sketch: bool = False, + black_and_white: bool = True, + manual_editing: bool = False +): """ Pencil Sketchify Steps """ diff --git a/plugins/Investigamer/py/templates.py b/plugins/Investigamer/py/templates.py index 9fb2bd52..a0677768 100644 --- a/plugins/Investigamer/py/templates.py +++ b/plugins/Investigamer/py/templates.py @@ -1,40 +1,42 @@ """ * Plugin: Investigamer """ + # Standard Library +from collections.abc import Callable from functools import cached_property -from typing import Optional, Callable # Third Party from photoshop.api._artlayer import ArtLayer # Local Imports +import src.helpers as psd from src import CFG, ENV from src.enums.layers import LAYERS -import src.helpers as psd -from src.templates import TransformMod, NormalTemplate, ExtendedMod +from src.templates import ExtendedMod, NormalTemplate, TransformMod # Plugin Imports -from .actions import sketch, pencilsketch +from .actions import pencilsketch, sketch """ * Template Classes """ -class SketchTemplate (NormalTemplate): +class SketchTemplate(NormalTemplate): """ * Sketch showcase from MH2 * Original PSD by Nelynes """ + template_suffix = "Sketch" """ * Sketch Action """ - @property - def art_action(self) -> Optional[Callable]: + @cached_property + def art_action(self) -> Callable[[], None] | None: # Skip action if in test mode if ENV.TEST_MODE: return @@ -42,69 +44,63 @@ def art_action(self) -> Optional[Callable]: section="ACTION", key="Sketch.Action", default="Advanced Sketch", - is_bool=False + is_bool=False, ) if action == "Advanced Sketch": - return pencilsketch.run + return lambda: pencilsketch.run( + self.event, + draft_sketch=CFG.get_bool_setting( + section="ACTION", key="Draft.Sketch.Lines", default=False + ), + rough_sketch=CFG.get_bool_setting( + section="ACTION", key="Rough.Sketch.Lines", default=False + ), + black_and_white=CFG.get_bool_setting( + section="ACTION", key="Black.And.White", default=False + ), + manual_editing=CFG.get_bool_setting( + section="ACTION", key="Sketch.Manual.Editing", default=False + ), + ) if action == "Quick Sketch": return sketch.run return - @property - def art_action_args(self) -> Optional[dict]: - # Skip if in test mode or using quick sketch - if ENV.TEST_MODE or not self.art_action == pencilsketch.run: - return - return { - 'thr': self.event, - 'rough_sketch': CFG.get_setting( - section="ACTION", - key="Rough.Sketch.Lines", - default=False - ), - 'draft_sketch': CFG.get_setting( - section="ACTION", - key="Draft.Sketch.Lines", - default=False - ), - 'black_and_white': CFG.get_setting( - section="ACTION", - key="Black.And.White", - default=False - ), - 'manual_editing': CFG.get_setting( - section="ACTION", - key="Sketch.Manual.Editing", - default=False - ) - } - -class KaldheimTemplate (NormalTemplate): +class KaldheimTemplate(NormalTemplate): """ Kaldheim viking legendary showcase. Original Template by FeuerAmeise """ + template_suffix = "Kaldheim" # Static Properties - is_legendary = False - background_layer = None - twins_layer = None + @cached_property + def is_legendary(self) -> bool: + return False + + @cached_property + def background_layer(self) -> ArtLayer | None: + return None + + @cached_property + def twins_layer(self) -> ArtLayer | None: + return None """ * Layer Properties """ @cached_property - def pt_layer(self) -> Optional[ArtLayer]: + def pt_layer(self) -> ArtLayer | None: # Enable vehicle support if "Vehicle" in self.layout.type_line: return psd.getLayer("Vehicle", LAYERS.PT_BOX) return psd.getLayer(self.twins, LAYERS.PT_BOX) @cached_property - def pinlines_layer(self) -> Optional[ArtLayer]: + def pinlines_layer(self) -> ArtLayer | None: # Enable vehicle support if self.is_land: return psd.getLayer(self.pinlines, LAYERS.LAND_PINLINES_TEXTBOX) @@ -113,23 +109,26 @@ def pinlines_layer(self) -> Optional[ArtLayer]: return psd.getLayer(self.pinlines, LAYERS.PINLINES_TEXTBOX) -class CrimsonFangTemplate (TransformMod, NormalTemplate): +class CrimsonFangTemplate(TransformMod, NormalTemplate): """The crimson vow showcase template. Original template by michayggdrasil. Notes: Transform features are kind of unfinished. """ + template_suffix = "Fang" # Static Properties - is_flipside_creature = False + @cached_property + def is_flipside_creature(self) -> bool: + return False """ * Details """ - @property - def background(self): + @cached_property + def background(self) -> str: # Use pinlines colors for background return self.pinlines @@ -138,7 +137,7 @@ def background(self): """ @cached_property - def pinlines_layer(self) -> Optional[ArtLayer]: + def pinlines_layer(self) -> ArtLayer | None: # Support backside colors if self.is_land: return psd.getLayer(self.pinlines, LAYERS.LAND_PINLINES_TEXTBOX) @@ -148,7 +147,8 @@ def pinlines_layer(self) -> Optional[ArtLayer]: def enable_transform_layers(self): # Enable circle backing - psd.getLayerSet(LAYERS.TRANSFORM, self.text_group).visible = True + if layer := psd.getLayerSet(LAYERS.TRANSFORM, self.text_group): + layer.visible = True super().enable_transform_layers() def text_layers_transform(self) -> None: @@ -156,33 +156,45 @@ def text_layers_transform(self) -> None: pass -class PhyrexianTemplate (NormalTemplate): +class PhyrexianTemplate(NormalTemplate): """From the Phyrexian secret lair promo.""" + template_suffix = "Phyrexian" # Static Properties - background_layer = None - twins_layer = None + @cached_property + def background_layer(self) -> ArtLayer | None: + return None + + @cached_property + def twins_layer(self) -> ArtLayer | None: + return None -class DoubleFeatureTemplate (NormalTemplate): +class DoubleFeatureTemplate(NormalTemplate): """ Midnight Hunt / Vow Double Feature Showcase Original assets from Warpdandy's Proximity Template Doesn't support companion, nyx, or twins layers. """ + template_suffix = "Double Feature" # Static Properties - pinlines_layer = None - twins_layer = None + @cached_property + def pinlines_layer(self) -> ArtLayer | None: + return None + + @cached_property + def twins_layer(self) -> ArtLayer | None: + return None """ * Layer Properties """ - @property - def background_layer(self) -> Optional[ArtLayer]: + @cached_property + def background_layer(self) -> ArtLayer | None: return psd.getLayer(self.pinlines, LAYERS.BACKGROUND) @@ -191,24 +203,30 @@ def background_layer(self) -> Optional[ArtLayer]: """ -class ColorshiftedTemplate (NormalTemplate): +class ColorshiftedTemplate(NormalTemplate): """ Planar Chaos era colorshifted template Rendered from CC and MSE assets. Most title boxes are built into pinlines. Doesn't support special layers for nyx, companion, land, or colorless. """ + template_suffix = "Colorshifted" # Static Properties - is_land = False - is_colorless = False + @cached_property + def is_land(self) -> bool: + return False + + @cached_property + def is_colorless(self) -> bool: + return False """ * Layer Properties """ @cached_property - def twins_layer(self) -> Optional[ArtLayer]: + def twins_layer(self) -> ArtLayer | None: if "Artifact" in self.layout.type_line and self.pinlines != "Artifact": if self.is_legendary: return psd.getLayer("Legendary Artifact", "Twins") @@ -220,29 +238,31 @@ def twins_layer(self) -> Optional[ArtLayer]: return @cached_property - def pt_layer(self) -> Optional[ArtLayer]: + def pt_layer(self) -> ArtLayer | None: if self.is_creature: # Check if vehicle if "Vehicle" in self.layout.type_line: return psd.getLayer("Vehicle", LAYERS.PT_BOX) return psd.getLayer(self.twins, LAYERS.PT_BOX) - return psd.getLayerSet(LAYERS.PT_BOX) """ * Methods """ def collector_info(self): - # Artist and set layer artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group) - psd.replace_text(artist_layer, "Artist", self.layout.artist) + if artist_layer: + psd.replace_text(artist_layer, "Artist", self.layout.artist) # Switch to white brush and artist name if self.layout.pinlines[0:1] == "B" and len(self.pinlines) < 3: - artist_layer.textItem.color = psd.rgb_white() - psd.getLayer("Brush B", self.legal_group).visible = False - psd.getLayer("Brush W", self.legal_group).visible = True + if artist_layer: + artist_layer.textItem.color = psd.rgb_white() + if layer := psd.getLayer("Brush B", self.legal_group): + layer.visible = False + if layer := psd.getLayer("Brush W", self.legal_group): + layer.visible = True """ @@ -250,14 +270,16 @@ def collector_info(self): """ -class BasicLandDarkMode (ExtendedMod, NormalTemplate): +class BasicLandDarkMode(ExtendedMod, NormalTemplate): """Basic land Dark Mode. Credit to Vittorio Masia (Sid) Todo: Transition to 'Normal' type. """ + template_suffix = "Dark Mode" def collector_info(self): # Collector info only has artist - psd.getLayer(LAYERS.ARTIST, self.legal_group).textItem.contents = self.layout.artist + if layer := psd.getLayer(LAYERS.ARTIST, self.legal_group): + layer.textItem.contents = self.layout.artist diff --git a/plugins/SilvanMTG/py/templates.py b/plugins/SilvanMTG/py/templates.py index c90b6e0b..ddd1ab0c 100644 --- a/plugins/SilvanMTG/py/templates.py +++ b/plugins/SilvanMTG/py/templates.py @@ -1,12 +1,13 @@ """ * SilvanMTG Templates """ + # Standard Library Imports from functools import cached_property -from typing import Optional # Third Party Imports from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet # Local Imports from src.enums.layers import LAYERS @@ -18,12 +19,13 @@ """ -class SilvanExtendedTemplate (ExtendedMod, M15Template): +class SilvanExtendedTemplate(ExtendedMod, M15Template): """Silvan's legendary extended template used for WillieTanner proxies.""" + template_suffix = "Extended" @cached_property - def background_layer(self) -> Optional[ArtLayer]: + def background_layer(self) -> ArtLayer | None: """Optional[ArtLayer]: No background for colorless cards.""" if self.background == LAYERS.COLORLESS: return @@ -32,16 +34,24 @@ def background_layer(self) -> Optional[ArtLayer]: def enable_crown(self) -> None: """Add a mask to the background layer.""" super().enable_crown() - if self.background_layer: - psd.enable_mask(self.background_layer.parent) + if self.background_layer and isinstance( + (parent := self.background_layer.parent), LayerSet + ): + psd.enable_mask(parent) -class SilvanMDFCTemplate (MDFCMod, ExtendedMod, M15Template): +class SilvanMDFCTemplate(MDFCMod, ExtendedMod, M15Template): """Silvan extended template modified for MDFC cards.""" def enable_crown(self) -> None: super().enable_crown() # Enable pinlines and background mask - psd.enable_vector_mask(self.pinlines_layer.parent) - psd.enable_vector_mask(self.background_layer.parent) + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) + if self.background_layer and isinstance( + (parent := self.background_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) diff --git a/src/__init__.py b/src/__init__.py index bc5d3407..eb2d714d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,6 +2,7 @@ * Load Global Application State """ # Standard Library Imports +from pathlib import Path import sys # Third Party Imports @@ -9,7 +10,6 @@ from omnitils.files import get_project_version # Local Imports -from .console import TerminalConsole from ._config import AppConfig from ._loader import get_all_plugins, get_all_templates, get_template_map, get_template_map_defaults from ._state import AppConstants, AppEnvironment, PATH @@ -19,6 +19,12 @@ * Globally Loaded Objects """ +def _get_proj_version(path: Path) -> str: + try: + return get_project_version(path) + except Exception: + return "0.0.0" + # Global environment object (dynaconf) ENV = AppEnvironment( envvar_prefix='PROXYSHOP', @@ -31,7 +37,7 @@ Validator('HEADLESS', cast=bool, default=False), Validator('DEV_MODE', cast=bool, default=bool(not hasattr(sys, '_MEIPASS'))), Validator('TEST_MODE', cast=bool, default=False), - Validator('VERSION', cast=str, default=get_project_version(PATH.PROJECT_FILE)), + Validator('VERSION', cast=str, default=_get_proj_version(PATH.PROJECT_FILE)), Validator('FORCE_RELOAD', cast=bool, default=False) ], apply_default_on_none=True @@ -50,7 +56,7 @@ if not ENV.HEADLESS: from src.gui.console import GUIConsole as Console else: - Console = TerminalConsole + from .console import TerminalConsole as Console CONSOLE = Console(cfg=CFG, env=ENV, app=APP) # Global plugins and templates diff --git a/src/_config.py b/src/_config.py index 604b6c49..2fe0a6a7 100644 --- a/src/_config.py +++ b/src/_config.py @@ -1,8 +1,9 @@ """ * Global Settings Module """ + # Standard Library Imports -from typing import Optional +from typing import Literal, overload # Third Party Imports from omnitils.metaclass import Singleton @@ -17,7 +18,7 @@ ScryfallSorting, ScryfallUnique, CollectorPromo, - WatermarkMode + WatermarkMode, ) from src._loader import ConfigManager @@ -25,6 +26,7 @@ class AppConfig: """Stores the current state of app and template settings. Can be changed within a template class to affect rendering behavior.""" + __metaclass__ = Singleton def __init__(self, env: AppEnvironment): @@ -36,61 +38,134 @@ def update_definitions(self): """Updates the defined settings values using the currently loaded ConfigParser object.""" # APP - FILES - self.overwrite_duplicate = self.file.getboolean('APP.FILES', 'Overwrite.Duplicate', fallback=True) - self.output_file_type = self.get_option('APP.FILES', 'Output.File.Type', OutputFileType) + self.overwrite_duplicate = self.file.getboolean( + "APP.FILES", "Overwrite.Duplicate", fallback=True + ) + self.output_file_type = self.get_option( + "APP.FILES", "Output.File.Type", OutputFileType + ) self.output_file_name = self.file.get( - section='APP.FILES', option='Output.File.Name', - fallback='#name (#frame<, #suffix>) [#set] {#num}') + section="APP.FILES", + option="Output.File.Name", + fallback="#name (#frame<, #suffix>) [#set] {#num}", + ) # APP - DATA - self.lang = self.file.get('APP.DATA', 'Scryfall.Language', fallback='en') - self.scry_sorting = self.get_option('APP.DATA', 'Scryfall.Sorting', ScryfallSorting) - self.scry_ascending = self.file.getboolean('APP.DATA', 'Scryfall.Ascending', fallback=False) - self.scry_extras = self.file.getboolean('APP.DATA', 'Scryfall.Extras', fallback=False) - self.scry_unique = self.get_option('APP.DATA', 'Scryfall.Unique', ScryfallUnique) - self.manually_edit_card_data = self.file.getboolean('APP.DATA', 'Manually.Edit.Card.Data', fallback=False) - self.manual_text_editor = self.file.get('APP.DATA', 'Manual.Text.Editor', fallback='notepad "{}"') + self.lang = self.file.get("APP.DATA", "Scryfall.Language", fallback="en") + self.scry_sorting = self.get_option( + "APP.DATA", "Scryfall.Sorting", ScryfallSorting + ) + self.scry_ascending = self.file.getboolean( + "APP.DATA", "Scryfall.Ascending", fallback=False + ) + self.scry_extras = self.file.getboolean( + "APP.DATA", "Scryfall.Extras", fallback=False + ) + self.scry_unique = self.get_option( + "APP.DATA", "Scryfall.Unique", ScryfallUnique + ) + self.manually_edit_card_data = self.file.getboolean( + "APP.DATA", "Manually.Edit.Card.Data", fallback=False + ) + self.manual_text_editor = self.file.get( + "APP.DATA", "Manual.Text.Editor", fallback='notepad "{}"' + ) # APP - TEXT - self.force_english_formatting = self.file.getboolean('APP.TEXT', "Force.English.Formatting", fallback=False) + self.force_english_formatting = self.file.getboolean( + "APP.TEXT", "Force.English.Formatting", fallback=False + ) # APP - RENDER - self.skip_failed = self.file.getboolean('APP.RENDER', 'Skip.Failed', fallback=False) - self.generative_fill = False if self.ENV.TEST_MODE else self.file.getboolean( - 'APP.RENDER', 'Generative.Fill', fallback=False) - self.select_variation = self.file.getboolean('APP.RENDER', 'Select.Variation', fallback=False) - self.feathered_fill = self.file.getboolean('APP.RENDER', 'Feathered.Fill', fallback=False) - self.vertical_fullart = self.file.getboolean('APP.RENDER', 'Vertical.Fullart', fallback=False) + self.skip_failed = self.file.getboolean( + "APP.RENDER", "Skip.Failed", fallback=False + ) + self.generative_fill = ( + False + if self.ENV.TEST_MODE + else self.file.getboolean("APP.RENDER", "Generative.Fill", fallback=False) + ) + self.select_variation = self.file.getboolean( + "APP.RENDER", "Select.Variation", fallback=False + ) + self.feathered_fill = self.file.getboolean( + "APP.RENDER", "Feathered.Fill", fallback=False + ) + self.vertical_fullart = self.file.getboolean( + "APP.RENDER", "Vertical.Fullart", fallback=False + ) # BASE - TEXT - self.flavor_divider = self.file.getboolean('BASE.TEXT', 'Flavor.Divider', fallback=True) - self.remove_flavor = self.file.getboolean('BASE.TEXT', 'No.Flavor.Text', fallback=False) - self.remove_reminder = self.file.getboolean('BASE.TEXT', 'No.Reminder.Text', fallback=False) - self.collector_mode = self.get_option('BASE.TEXT', 'Collector.Mode', CollectorMode) - self.collector_promo = self.get_option('BASE.TEXT', "Collector.Promo", CollectorPromo) + self.flavor_divider = self.file.getboolean( + "BASE.TEXT", "Flavor.Divider", fallback=True + ) + self.remove_flavor = self.file.getboolean( + "BASE.TEXT", "No.Flavor.Text", fallback=False + ) + self.remove_reminder = self.file.getboolean( + "BASE.TEXT", "No.Reminder.Text", fallback=False + ) + self.collector_mode = self.get_option( + "BASE.TEXT", "Collector.Mode", CollectorMode + ) + self.collector_promo = self.get_option( + "BASE.TEXT", "Collector.Promo", CollectorPromo + ) # BASE - SYMBOLS - self.symbol_enabled = self.file.getboolean('BASE.SYMBOLS', 'Enable.Expansion.Symbol', fallback=True) - self.symbol_default = self.file.get('BASE.SYMBOLS', 'Default.Symbol', fallback='MTG') - self.symbol_force_default = self.file.getboolean('BASE.SYMBOLS', 'Force.Default.Symbol', fallback=False) + self.symbol_enabled = self.file.getboolean( + "BASE.SYMBOLS", "Enable.Expansion.Symbol", fallback=True + ) + self.symbol_default = self.file.get( + "BASE.SYMBOLS", "Default.Symbol", fallback="MTG" + ) + self.symbol_force_default = self.file.getboolean( + "BASE.SYMBOLS", "Force.Default.Symbol", fallback=False + ) # BASE - WATERMARKS - self.watermark_mode = self.get_option('BASE.WATERMARKS', 'Watermark.Mode', WatermarkMode) - self.watermark_default = self.file.get('BASE.WATERMARKS', 'Default.Watermark', fallback='WOTC') - self.watermark_opacity = self.file.getint('BASE.WATERMARKS', 'Watermark.Opacity', fallback=30) - self.enable_basic_watermark = self.file.getboolean('BASE.WATERMARKS', 'Enable.Basic.Watermark', fallback=True) + self.watermark_mode = self.get_option( + "BASE.WATERMARKS", "Watermark.Mode", WatermarkMode + ) + self.watermark_default = self.file.get( + "BASE.WATERMARKS", "Default.Watermark", fallback="WOTC" + ) + self.watermark_opacity = self.file.getint( + "BASE.WATERMARKS", "Watermark.Opacity", fallback=30 + ) + self.enable_basic_watermark = self.file.getboolean( + "BASE.WATERMARKS", "Enable.Basic.Watermark", fallback=True + ) # BASE - TEMPLATES - self.exit_early = self.file.getboolean('BASE.TEMPLATES', 'Manual.Edit', fallback=False) - self.minimize_photoshop = self.file.getboolean('BASE.TEMPLATES', 'Minimize.Photoshop', fallback=False) - self.import_scryfall_scan = self.file.getboolean('BASE.TEMPLATES', 'Import.Scryfall.Scan', fallback=False) - self.border_color = self.get_option('BASE.TEMPLATES', 'Border.Color', BorderColor) + self.exit_early = self.file.getboolean( + "BASE.TEMPLATES", "Manual.Edit", fallback=False + ) + self.minimize_photoshop = self.file.getboolean( + "BASE.TEMPLATES", "Minimize.Photoshop", fallback=False + ) + self.import_scryfall_scan = self.file.getboolean( + "BASE.TEMPLATES", "Import.Scryfall.Scan", fallback=False + ) + self.border_color = self.get_option( + "BASE.TEMPLATES", "Border.Color", BorderColor + ) + + self.backup_template = self.file.getboolean( + "BASE.TEMPLATES", "Backup.Template", fallback=False + ) """ * Setting Utils """ - def get_option(self, section: str, key: str, enum_class: type[StrConstant], default: str | None = None) -> str: + def get_option( + self, + section: str, + key: str, + enum_class: type[StrConstant], + default: str | None = None, + ) -> str: """Returns the current value of an "options" setting if that option exists in its StrEnum class. Otherwise, returns the default value of that StrEnum class. @@ -110,7 +185,37 @@ def get_option(self, section: str, key: str, enum_class: type[StrConstant], defa return option return defa - def get_setting(self, section: str, key: str, default: str | bool | None = None, is_bool: bool = True): + @overload + def get_setting( + self, section: str, key: str, default: bool, is_bool: Literal[True] + ) -> bool: ... + + @overload + def get_setting( + self, section: str, key: str, default: bool | None, is_bool: Literal[True] + ) -> bool | None: ... + + @overload + def get_setting( + self, section: str, key: str, default: str, is_bool: Literal[False] = False + ) -> str: ... + + @overload + def get_setting( + self, + section: str, + key: str, + default: str | None = None, + is_bool: Literal[False] = False, + ) -> str | None: ... + + def get_setting( + self, + section: str, + key: str, + default: str | bool | None = None, + is_bool: bool = False, + ) -> str | bool | None: """Check if the setting exists and return it. Default will be returned if missing. Args: @@ -129,18 +234,63 @@ def get_setting(self, section: str, key: str, default: str | bool | None = None, return self.file[section].get(key, fallback=default) return default + @overload + def get_bool_setting(self, section: str, key: str, default: bool) -> bool: ... + + @overload + def get_bool_setting( + self, section: str, key: str, default: bool | None = None + ) -> bool | None: ... + + def get_bool_setting( + self, section: str, key: str, default: bool | None = None + ) -> bool | None: + return self.get_setting(section, key, default, is_bool=True) + + @overload + def get_int_setting(self, section: str, key: str, default: int) -> int: ... + + @overload + def get_int_setting( + self, section: str, key: str, default: int | None = None + ) -> int | None: ... + + def get_int_setting( + self, section: str, key: str, default: int | None = None + ) -> int | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return int(setting) + return default + + @overload + def get_float_setting(self, section: str, key: str, default: float) -> float: ... + + @overload + def get_float_setting( + self, section: str, key: str, default: float | None = None + ) -> float | None: ... + + def get_float_setting( + self, section: str, key: str, default: float | None = None + ) -> float | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return float(setting) + return default + """ * Load ConfigParser Object """ - def load(self, config: Optional[ConfigManager] = None) -> None: + def load(self, config: ConfigManager | None = None) -> None: """Reload the config file and define new values Args: config: ConfigManager to load from if provided, otherwise use app-wide configuration. """ # Invalidate file cache - if hasattr(self, 'file'): + if hasattr(self, "file"): del self.file # Load provided or load fresh diff --git a/src/_loader.py b/src/_loader.py index 687fa7f8..42228211 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -2,6 +2,7 @@ * Plugin and Template Loader * Only import enums and utils """ + # Standard Library Imports import json import shutil @@ -13,7 +14,8 @@ from pathlib import Path from traceback import format_exc, print_tb from types import ModuleType -from typing import Optional, TypedDict, NotRequired, Any, Callable, Union +from typing import Optional, TypedDict, NotRequired, Any, overload +from collections.abc import Callable # Third Party Imports from py7zr import SevenZipFile @@ -30,7 +32,8 @@ layout_map_category, layout_map_types, layout_map_display_condition_dual, - layout_map_display_condition) + layout_map_display_condition, +) from src.utils.download import download_cloudfront """ @@ -40,6 +43,7 @@ class TemplateUpdate(TypedDict): """Details about the latest update for a given AppTemplate.""" + name: NotRequired[str] version: NotRequired[str] size: NotRequired[int] @@ -47,10 +51,11 @@ class TemplateUpdate(TypedDict): class TemplateDetails(TypedDict): """Details about a specific template within the TemplateCategoryMap.""" + name: str class_name: str - object: 'AppTemplate' - config: 'ConfigManager' + object: "AppTemplate" + config: "ConfigManager" """Dictionary which maps a template's displayed names to classes, and classes to template types.""" @@ -60,17 +65,19 @@ class TemplateDetails(TypedDict): TemplateTypeMap = dict[str, dict[str, TemplateDetails]] """Map of templates selected for each template type.""" -TemplateSelectedMap = dict[str, Optional[TemplateDetails]] +TemplateSelectedMap = dict[str, TemplateDetails | None] class TemplateRequirements(TypedDict): """Requirements that must be met for a template to be supported.""" + templates: NotRequired[list[str]] version: NotRequired[str] class ManifestTemplateDetails(TypedDict): """Template details pulled from a plugin's `manifest.yml` file or Proxyshop's `templates.yml` file.""" + file: str name: NotRequired[str] id: NotRequired[str] @@ -82,6 +89,7 @@ class ManifestTemplateDetails(TypedDict): class TemplateMetadata(TypedDict): """TemplateDetails, minus the template class map.""" + file: str name: NotRequired[str] id: NotRequired[str] @@ -92,6 +100,7 @@ class TemplateMetadata(TypedDict): class PluginMetadata(TypedDict): """Metadata contained in the `PLUGIN` table of a plugin's `manifest.yml` file.""" + name: NotRequired[str] author: NotRequired[str] desc: NotRequired[str] @@ -104,15 +113,41 @@ class PluginMetadata(TypedDict): class TemplateCategoryMap(TypedDict): """Data mapped to a displayed template category, e.g. 'Normal'.""" + names: list[str] map: TemplateTypeMap +class KivyConfigTitle(TypedDict): + type: str + title: str + + +class KivyConfigSection(KivyConfigTitle): + type: str + title: str + desc: str + section: str + key: str + options: NotRequired[list[str]] + default: str | int | float + + +class IniConfig(TypedDict): + key: str + value: str | int | float + + """ * Config File Utils """ +class CustomConfigParser(ConfigParser): + def optionxform(self, optionstr: str) -> str: + return optionstr + + def verify_config_fields(ini_file: Path, data_file: Path) -> None: """Validate that all settings fields present in a given json data are present in config file. If any are missing, add them and return. @@ -122,15 +157,16 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: data_file: Data file containing config fields to check for, JSON or TOML. """ # Track data and changes - data, changed = {}, False + data: dict[str, list[IniConfig]] = {} + changed = False # Data file doesn't exist or is unsupported data type - if not data_file.is_file() or data_file.suffix not in ['.toml', '.json']: + if not data_file.is_file() or data_file.suffix not in [".toml", ".json"]: return # Load data from JSON or TOML file raw = load_data_file(data_file) - raw = parse_kivy_config_toml(raw) if data_file.suffix == '.toml' else raw + raw = parse_kivy_config_toml(raw) if data_file.suffix == ".toml" else raw # Ensure INI file exists and load ConfigParser ensure_file(ini_file) @@ -139,14 +175,11 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: # Build a dictionary of the necessary values for row in raw: # Add row if it's not a title - if row.get('type', 'title') == 'title': + if row.get("type", "title") == "title": continue - data.setdefault( - row.get('section', 'BROKEN'), [] - ).append({ - 'key': row.get('key', ''), - 'value': row.get('default', 0) - }) + data.setdefault(row.get("section", "BROKEN"), []).append( + {"key": row.get("key", ""), "value": row.get("default", 0)} + ) # Add the data to ini where missing for section, settings in data.items(): @@ -156,8 +189,8 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: changed = True # Check if each setting exists for setting in settings: - if not config.has_option(section, setting['key']): - config.set(section, setting['key'], str(setting['value'])) + if not config.has_option(section, setting["key"]): + config.set(section, setting["key"], str(setting["value"])) changed = True # If ini has changed, write changes @@ -166,7 +199,21 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: config.write(f) # noqa -def parse_kivy_config_json(raw: list[dict]) -> list[dict]: +@overload +def parse_kivy_config_json( + raw: list[KivyConfigTitle | KivyConfigSection], +) -> list[KivyConfigTitle | KivyConfigSection]: ... + + +@overload +def parse_kivy_config_json( + raw: list[dict[str, str | list[str]]], +) -> list[dict[str, str | list[str]]]: ... + + +def parse_kivy_config_json( + raw: list[dict[str, str | list[str]]] | list[KivyConfigTitle | KivyConfigSection], +) -> list[dict[str, str | list[str]]] | list[KivyConfigTitle | KivyConfigSection]: """Parse config JSON data for use with Kivy settings panel. Args: @@ -177,55 +224,67 @@ def parse_kivy_config_json(raw: list[dict]) -> list[dict]: """ # Remove unsupported keys for row in raw: - if 'default' in row: - row.pop('default') + if "default" in row: + row.pop("default") return raw -def parse_kivy_config_toml(raw: dict) -> list[dict]: +def parse_kivy_config_toml( + raw: dict[str, Any], +) -> list[KivyConfigTitle | KivyConfigSection]: """Parse config TOML data for use with Kivy settings panel. - Args: - raw: Raw loaded TOML data. + Args: + raw: Raw loaded TOML data. + Something along the lines of (not yet possible to express in Python): - Returns: - Properly parsed data safe for use with Kivy. + ``` + class Config(TypedDict): + prefix: str + + class Section(TypedDict): + title: str + [str]: dict[str,str|list[str]] + + class RawConf(TypedDict): + __CONFIG__: Config + [str]: Section + ``` + + Returns: + Properly parsed data safe for use with Kivy. """ # Process __CONFIG__ header if present - cfg_header = raw.pop('__CONFIG__', {}) - prefix = cfg_header.get('prefix', '') + cfg_header = raw.pop("__CONFIG__", {}) + prefix = cfg_header.get("prefix", "") # Process data - data: list[dict] = [] + data: list[KivyConfigTitle | KivyConfigSection] = [] for section, settings in raw.items(): - # Add section title if it exists - if title := settings.pop('title', None): - data.append({ - 'type': 'title', - 'title': title - }) + if title := settings.pop("title", None): + data.append({"type": "title", "title": title}) # Add each setting within this section for key, field in settings.items(): - # Establish data type and default value - data_type = field.get('type', 'bool') - display_default = default = field.get('default', 0) - if data_type == 'bool': - display_default = 'True' if default else 'False' - elif data_type in ['string', 'options', 'path']: + data_type = field.get("type", "bool") + display_default = default = field.get("default", 0) + if data_type == "bool": + display_default = "True" if default else "False" + elif data_type in ["string", "options", "path"]: display_default = f"'{default}'" - setting = { - 'type': data_type, - 'title': f"[b]{field.get('title', 'Broken Setting')}[/b]", - 'desc': f"{field.get('desc', '')}\n" - f"[b](Default: {display_default})[/b]", - 'section': f'{prefix}.{section}' if prefix else section, - 'key': key, 'default': default} - if options := field.get('options'): - setting['options'] = options + setting: KivyConfigSection = { + "type": data_type, + "title": f"[b]{field.get('title', 'Broken Setting')}[/b]", + "desc": f"{field.get('desc', '')}\n[b](Default: {display_default})[/b]", + "section": f"{prefix}.{section}" if prefix else section, + "key": key, + "default": default, + } + if options := field.get("options"): + setting["options"] = options data.append(setting) # Return parsed data @@ -245,7 +304,7 @@ def get_kivy_config_from_schema(config: Path) -> str: raw = load_data_file(config) # Use correct parser - if config.suffix == '.toml': + if config.suffix == ".toml": raw = parse_kivy_config_toml(raw) return json.dumps(parse_kivy_config_json(raw)) @@ -263,7 +322,9 @@ def copy_config_or_verify(path_from: Path, path_to: Path, data_file: Path) -> No shutil.copy(path_from, path_to) -def get_config_object(path: Union[str, os.PathLike, list[Union[str, os.PathLike]]]) -> ConfigParser: +def get_config_object( + path: str | os.PathLike[str] | list[str | os.PathLike[str]], +) -> ConfigParser: """Returns a ConfigParser object using a valid ini path. Args: @@ -275,9 +336,8 @@ def get_config_object(path: Union[str, os.PathLike, list[Union[str, os.PathLike] Raises: ValueError: If valid ini file wasn't received. """ - config = ConfigParser(allow_no_value=True) - config.optionxform = str - config.read(path, encoding='utf-8') + config = CustomConfigParser(allow_no_value=True) + config.read(path, encoding="utf-8") return config @@ -293,22 +353,28 @@ class ConfigManager: template_class: Name of the template class, if provided. template: AppTemplate object corresponding to the template class, if provided. """ + gui_elements = [] def __init__( - self, template_class: Optional[str] = None, - template: Optional['AppTemplate'] = None + self, + template_class: str | None = None, + template: Optional["AppTemplate"] = None, ) -> None: - # Establish template and plugin details - self._template_class: Optional[str] = template_class.replace( - 'Back', 'Front') if template_class else None - self._template: Optional[AppTemplate] = template or None - self._plugin: Optional[AppPlugin] = template.plugin if template else None + self._template_class: str | None = ( + template_class.replace("Back", "Front") if template_class else None + ) + self._template: AppTemplate | None = template or None + self._plugin: AppPlugin | None = template.plugin if template else None # Core path where config folders exist - self._path_schema = self._plugin.path_config if self._plugin else PATH.SRC_DATA_CONFIG - self._path_ini = self._plugin.path_ini if self._plugin else PATH.SRC_DATA_CONFIG_INI + self._path_schema = ( + self._plugin.path_config if self._plugin else PATH.SRC_DATA_CONFIG + ) + self._path_ini = ( + self._plugin.path_ini if self._plugin else PATH.SRC_DATA_CONFIG_INI + ) """ * Path: App settings only modified in Global @@ -363,29 +429,29 @@ def base_cfg(self) -> ConfigParser: """ @property - def template_path_schema(self) -> Optional[Path]: + def template_path_schema(self) -> Path | None: """Template specific config schema, in JSON or TOML.""" - if self._template: + if self._template and self._template_class: path = self._template.get_path_config(self._template_class) return path if path.is_file() else None return @property - def template_path_ini(self) -> Optional[Path]: + def template_path_ini(self) -> Path | None: """Template specific config INI.""" - if self._template: + if self._template and self._template_class: return self._template.get_path_ini(self._template_class) return @property - def template_json(self) -> Optional[str]: + def template_json(self) -> str | None: """Template specific config as Kivy readable JSON string dump.""" if self.template_path_schema: return get_kivy_config_from_schema(self.template_path_schema) return @property - def template_cfg(self) -> Optional[ConfigParser]: + def template_cfg(self) -> ConfigParser | None: """Template specific ConfigParser instance.""" if self.template_path_ini: return get_config_object(self.template_path_ini) @@ -407,16 +473,12 @@ def has_template_ini(self) -> bool: def get_config(self) -> ConfigParser: """Return a ConfigParser instance with all relevant config data loaded.""" self.validate_configs() - if self.has_template_ini: + if self.template_path_ini and self.template_path_ini.is_file(): # Load template INI file instead of base self.validate_template_configs() - return get_config_object([ - self.app_path_ini, - self.template_path_ini]) + return get_config_object([self.app_path_ini, self.template_path_ini]) # Load app and base only - return get_config_object([ - self.app_path_ini, - self.base_path_ini]) + return get_config_object([self.app_path_ini, self.base_path_ini]) """ * Validate Methods @@ -424,12 +486,10 @@ def get_config(self) -> ConfigParser: def validate_configs(self) -> None: """Validate app and base configs against data schemas.""" + verify_config_fields(ini_file=self.app_path_ini, data_file=self.app_path_schema) verify_config_fields( - ini_file=self.app_path_ini, - data_file=self.app_path_schema) - verify_config_fields( - ini_file=self.base_path_ini, - data_file=self.base_path_schema) + ini_file=self.base_path_ini, data_file=self.base_path_schema + ) def validate_template_configs(self) -> None: """Validate template configs against data schemas.""" @@ -441,13 +501,14 @@ def validate_template_configs(self) -> None: copy_config_or_verify( path_from=self.base_path_ini, path_to=self.template_path_ini, - data_file=self.base_path_schema) + data_file=self.base_path_schema, + ) # Validate template specific configs if self.template_path_schema: verify_config_fields( - ini_file=self.template_path_ini, - data_file=self.template_path_schema) + ini_file=self.template_path_ini, data_file=self.template_path_schema + ) class AppPlugin: @@ -468,8 +529,8 @@ class AppPlugin: FileNotFoundError: If the plugin path or manifest file couldn't be found. ModuleNotFoundError: If the plugin's python module couldn't be found. """ - def __init__(self, con: AppConstants, env: AppEnvironment, path: Path): + def __init__(self, con: AppConstants, env: AppEnvironment, path: Path): # Save a reference to the global application constants self.con: AppConstants = con self.env: AppEnvironment = env @@ -480,16 +541,18 @@ def __init__(self, con: AppConstants, env: AppEnvironment, path: Path): self._root = path # Find a valid manifest - self._manifest = Path(path, 'manifest.yml') + self._manifest = Path(path, "manifest.yml") if not self._manifest.is_file(): - self._manifest = Path(path, 'manifest.yaml') + self._manifest = Path(path, "manifest.yaml") if not self._manifest.is_file(): - self._manifest = Path(path, 'manifest.json') + self._manifest = Path(path, "manifest.json") if not self._manifest.is_file(): - raise FileNotFoundError(f"Couldn't locate manifest file for plugin at path: {str(path)}") + raise FileNotFoundError( + f"Couldn't locate manifest file for plugin at path: {str(path)}" + ) # Load the manifest data - self.load_manifest() if self._manifest.suffix.lower() != '.json' else self.load_manifest_json() + self.load_manifest() if self._manifest.suffix.lower() != ".json" else self.load_manifest_json() # Attempt to load this plugin's module self.load_module() @@ -502,7 +565,7 @@ def __init__(self, con: AppConstants, env: AppEnvironment, path: Path): """ @cached_property - def template_map(self) -> dict[str, 'AppTemplate']: + def template_map(self) -> dict[str, "AppTemplate"]: """dict[str, AppTemplate]: A dictionary mapping of AppTemplate's by file name pulled from this plugin.""" return {} @@ -510,25 +573,29 @@ def template_map(self) -> dict[str, 'AppTemplate']: * Pathing """ + @property + def root(self) -> Path: + return self._root + @cached_property def path_config(self) -> Path: """Path: Path to this plugin's config directory.""" - return Path(self._root, 'config') + return Path(self._root, "config") @cached_property def path_ini(self) -> Path: """Path: Path to this plugin's INI config directory.""" - return Path(self._root, 'config_ini') + return Path(self._root, "config_ini") @cached_property def path_img(self) -> Path: """Path: Path to this plugin's preview image directory.""" - return Path(self._root, 'img') + return Path(self._root, "img") @cached_property def path_templates(self) -> Path: """Path: Path to this plugin's templates directory.""" - return self._root / 'templates' + return self._root / "templates" """ * Plugin Metadata @@ -537,31 +604,31 @@ def path_templates(self) -> Path: @cached_property def name(self) -> str: """str: Displayed name of the plugin. Fallback on root directory name.""" - return self._info.get('name', self._root.stem) + return self._info.get("name", self._root.stem) @cached_property def author(self) -> str: """str: Displayed name of the plugin's author. Fallback on name.""" - return self._info.get('author', self.name) + return self._info.get("author", self.name) @cached_property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Optional[str]: Displayed description of the plugin. Fallback on None.""" - return self._info.get('desc', None) + return self._info.get("desc", None) @cached_property - def source(self) -> Optional[yarl.URL]: + def source(self) -> yarl.URL | None: """Optional[URL]: Link to the hosted files of this plugin.""" - if url := self._info.get('source'): + if url := self._info.get("source"): with suppress(Exception): url = yarl.URL(url) return url return @cached_property - def docs(self) -> Optional[yarl.URL]: + def docs(self) -> yarl.URL | None: """Optional[URL]: Link to the hosted documentation for this plugin.""" - if url := self._info.get('docs'): + if url := self._info.get("docs"): with suppress(Exception): url = yarl.URL(url) return url @@ -570,31 +637,35 @@ def docs(self) -> Optional[yarl.URL]: @cached_property def license(self) -> str: """str: Name of the open source license carried by this plugin. Fallback on MPL-2.0.""" - return self._info.get('license', 'MPL-2.0') + return self._info.get("license", "MPL-2.0") @cached_property - def required_version(self) -> Optional[str]: + def required_version(self) -> str | None: """Optional[str]: Proxyshop version this plugin requires to function as intended.""" - return self._info.get('requires', None) + return self._info.get("requires", None) @cached_property def version(self) -> str: """str: Current version of the plugin.""" - return self._info.get('version', '0.1.0') + return self._info.get("version", "0.1.0") """ * Module Details """ + @property + def module(self) -> ModuleType | None: + return self._module + @cached_property def module_path(self) -> Path: """Path: The path to this plugin's Python module.""" - return Path(self._root, 'py') + return Path(self._root, "py") @cached_property def module_name(self) -> str: """str: The name of this plugin's Python module, e.g. 'plugins.MyPlugin.py'.""" - return f'{self._root.name}.py' + return f"{self._root.name}.py" """ * Plugin Methods @@ -609,10 +680,12 @@ def load_manifest(self) -> None: try: # Load plugin metadata and template details data = load_data_file(self._manifest) - self._info: PluginMetadata = data.pop('PLUGIN', {}) + self._info: PluginMetadata = data.pop("PLUGIN", {}) self._templates: dict[str, ManifestTemplateDetails] = data.copy() except Exception as e: - raise ValueError(f'Manifest file contains invalid data: {str(self._manifest)}') from e + raise ValueError( + f"Manifest file contains invalid data: {str(self._manifest)}" + ) from e def load_manifest_json(self) -> None: """Load the legacy `manifest.json` data for this plugin. @@ -623,10 +696,12 @@ def load_manifest_json(self) -> None: try: # Load Plugin metadata data = load_data_file(self._manifest) - self._info: PluginMetadata = data.pop('PLUGIN', {}) + self._info: PluginMetadata = data.pop("PLUGIN", {}) templates: dict[str, dict[str, dict[str, str]]] = data.copy() except Exception as e: - raise ValueError(f'Manifest file contains invalid data: {str(self._manifest)}') from e + raise ValueError( + f"Manifest file contains invalid data: {str(self._manifest)}" + ) from e # Re-format templates dict for TemplateDetails self._templates: dict[str, ManifestTemplateDetails] = {} @@ -635,26 +710,26 @@ def load_manifest_json(self) -> None: continue for name, details in temps.items(): # Add new file - file_name = details.get('file', '') - class_name = details.get('class', '') + file_name = details.get("file", "") + class_name = details.get("class", "") self._templates.setdefault( - file_name, { - 'file': file_name, - 'templates': {} - }) + file_name, {"file": file_name, "templates": {}} + ) # Add Google Drive ID - if details.get('id'): - self._templates[file_name]['id'] = details['id'] + if details.get("id"): + self._templates[file_name]["id"] = details["id"] # Existing name - if name in self._templates[file_name]['templates']: + if name in self._templates[file_name]["templates"]: # Existing class name - if class_name in self._templates[file_name]['templates'][name]: - self._templates[file_name]['templates'][name][class_name].append(t) + if class_name in self._templates[file_name]["templates"][name]: + self._templates[file_name]["templates"][name][ + class_name + ].append(t) continue # Add new class - self._templates[file_name]['templates'][name][class_name] = [t] + self._templates[file_name]["templates"][name][class_name] = [t] # Add new template - self._templates[file_name]['templates'][name] = {class_name: [t]} + self._templates[file_name]["templates"][name] = {class_name: [t]} def load_templates(self) -> None: """Load the dictionary of AppTemplate's pulled from this plugin's manifest file. @@ -663,12 +738,10 @@ def load_templates(self) -> None: A dictionary where keys are PSD/PSB filenames and values are AppTemplate objects. """ for file_name, data in self._templates.items(): - data['file'] = file_name + data["file"] = file_name self.template_map[file_name] = AppTemplate( - con=self.con, - env=self.env, - data=data, - plugin=self) + con=self.con, env=self.env, data=data, plugin=self + ) def load_module(self, hotswap: bool = False) -> None: """Load the plugin's Python module.""" @@ -678,30 +751,28 @@ def load_module(self, hotswap: bool = False) -> None: self.module_path.mkdir(mode=777, parents=True, exist_ok=True) # Check if root has init - root_init = self._root / '__init__.py' + root_init = self._root / "__init__.py" has_root_init = bool(root_init.is_file()) if not has_root_init: - with open(root_init, 'w', encoding='utf-8') as f: - f.write('') - import_module_from_path( - name=self._root.stem, - path=root_init, - hotswap=hotswap) + with open(root_init, "w", encoding="utf-8") as f: + f.write("") + import_module_from_path(name=self._root.stem, path=root_init, hotswap=hotswap) if not has_root_init: os.remove(root_init) # Ensure init file in py module - if not Path(self.module_path, '__init__.py').is_file(): - with open(Path(self.module_path, '__init__.py'), 'w', encoding='utf-8') as f: - f.write('from .templates import *') + if not Path(self.module_path, "__init__.py").is_file(): + with open( + Path(self.module_path, "__init__.py"), "w", encoding="utf-8" + ) as f: + f.write("from .templates import *") # Attempt to load this module self._module = import_package( - name=self.module_name, - path=self.module_path, - hotswap=hotswap) + name=self.module_name, path=self.module_path, hotswap=hotswap + ) - def get_template_list(self) -> list['AppTemplate']: + def get_template_list(self) -> list["AppTemplate"]: """list[AppTemplate]: Returns a list of AppTemplate's pulled from this plugin.""" return list(self.template_map.values()) @@ -722,27 +793,31 @@ class AppTemplate: """ def __init__( - self, - con: AppConstants, - env: AppEnvironment, - data: ManifestTemplateDetails, - plugin: Optional[AppPlugin] = None): - + self, + con: AppConstants, + env: AppEnvironment, + data: ManifestTemplateDetails, + plugin: AppPlugin | None = None, + ): # Save a reference to the global application constants self.con: AppConstants = con self.env: AppEnvironment = env # Save the template's plugin and class map - self.plugin: Optional[AppPlugin] = plugin + self.plugin: AppPlugin | None = plugin self.manifest_map: ManifestTemplateMap = { - name: ({mapped: ['normal']} if isinstance(mapped, str) else { - k: ([v] if isinstance(v, str) else v) for k, v in mapped.items() - }) for name, mapped in data['templates'].items()} + name: ( + {str(mapped): ["normal"]} + if isinstance(mapped, str) + else {k: ([v] if isinstance(v, str) else v) for k, v in mapped.items()} + ) + for name, mapped in data["templates"].items() + } self.generate_template_map(self.manifest_map) # Save the template's metadata self._info: TemplateMetadata = data.copy() - self._info.pop('templates') + self._info.pop("templates") """ * Template Mappings @@ -750,20 +825,20 @@ def __init__( def generate_template_map(self, _map: ManifestTemplateMap) -> None: """Generates a TemplateTypeMap, the configuration of types mapped to names mapped to details: - - 'class_name': python class name - - 'config': ConfigManager object - - 'object': AppTemplate object + - 'class_name': python class name + - 'config': ConfigManager object + - 'object': AppTemplate object """ mapped: TemplateTypeMap = {} - configs = {} + configs: dict[str, ConfigManager] = {} for name, classes in _map.items(): for class_name, types in classes.items(): for t in types: - # Reuse ConfigManager object for identical class names if class_name not in configs: configs[class_name] = ConfigManager( - template_class=class_name, template=self) + template_class=class_name, template=self + ) # Add to the map mapped.setdefault(t, {}) @@ -771,7 +846,8 @@ def generate_template_map(self, _map: ManifestTemplateMap) -> None: name=name, class_name=class_name, config=configs[class_name], - object=self) + object=self, + ) # Establish the complete map self.map = mapped @@ -783,28 +859,28 @@ def generate_template_map(self, _map: ManifestTemplateMap) -> None: @cached_property def name(self) -> str: """str: Name of the template displayed in download manager menus.""" - return self._info.get('name', self.generate_template_name()) + return self._info.get("name", self.generate_template_name()) @cached_property def file_name(self) -> str: """str: File name of the template PSD/PSB file.""" - file_name = self._info.get('file') + file_name = self._info.get("file") if not file_name: raise ValueError(f"Template '{self.name}' did not provide a file name!") return file_name @cached_property - def google_drive_id(self) -> Optional[str]: + def google_drive_id(self) -> str | None: """Optional[str]: The template's Google Drive file ID, fallback to None.""" - return self._info.get('id', None) + return self._info.get("id", None) @cached_property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Optional[str]: The template's displayed description, fallback to None.""" - return self._info.get('desc', None) + return self._info.get("desc", None) @property - def version(self) -> Optional[str]: + def version(self) -> str | None: """Optional[str]: The template's currently logged version.""" if not self.google_drive_id: return @@ -821,19 +897,19 @@ def _update(self) -> TemplateUpdate: return {} @property - def update_file(self) -> Optional[str]: + def update_file(self) -> str | None: """Optional[str]: Returns the filename of the fetched updated version of this template.""" - return self._update.get('name') + return self._update.get("name") @property - def update_size(self) -> Optional[int]: + def update_size(self) -> int | None: """Optional[int]: Returns the size in bytes of the fetched updated version of this template.""" - return self._update.get('size') + return self._update.get("size") @property - def update_version(self) -> Optional[str]: + def update_version(self) -> str | None: """Optional[str]: Returns the version number of the fetched updated version of this template.""" - return self._update.get('version') + return self._update.get("version") """ * Boolean Properties @@ -845,7 +921,7 @@ def is_installed(self) -> bool: # Todo: Add version checking if not self.path_psd.is_file(): return False - for required_template in self.requirements.get('templates', []): + for required_template in self.requirements.get("templates", []): if not Path(self.path_psd.parent, required_template).is_file(): return False return True @@ -863,10 +939,10 @@ def path_psd(self) -> Path: @cached_property def path_7z(self) -> Path: """Path: Path to the 7z archive downloaded for this plugin.""" - return self.path_psd.with_suffix('.7z') + return self.path_psd.with_suffix(".7z") @property - def path_download(self) -> Optional[Path]: + def path_download(self) -> Path | None: """Optional[Path]: Path the file should be saved to when downloaded, if update ready.""" if self.update_file: root = self.plugin.path_templates if self.plugin else PATH.TEMPLATES @@ -880,33 +956,33 @@ def path_download(self) -> Optional[Path]: @property def requirements(self) -> TemplateRequirements: """TemplateRequirements: Requirements that must be met for this template to be supported.""" - return self._info.get('requires', {}) + return self._info.get("requires", {}) """ * Template URLs """ @property - def url_amazon(self) -> Optional[yarl.URL]: + def url_amazon(self) -> yarl.URL | None: """yarl.URL: Amazon download URL for this template.""" if self.env.API_AMAZON and self.update_file: with suppress(Exception): base = yarl.URL(self.env.API_AMAZON) # Add plugin name to URL if self.plugin: - base = base / self.plugin._root.name + base = base / self.plugin.root.name return base / self.update_file return @property - def url_google_drive(self) -> Optional[yarl.URL]: + def url_google_drive(self) -> yarl.URL | None: """yarl.URL: Google Drive download URL for this template.""" if self.env.API_GOOGLE and self.update_file and self.google_drive_id: with suppress(Exception): # Return Google Drive URL with file ID - return yarl.URL( - 'https://drive.google.com/uc' - ).with_query({'id': self.google_drive_id}) + return yarl.URL("https://drive.google.com/uc").with_query( + {"id": self.google_drive_id} + ) return """ @@ -916,11 +992,14 @@ def url_google_drive(self) -> Optional[yarl.URL]: @cached_property def types_supported(self) -> list[str]: """set[str]: A set of all types supported by this template.""" - return list({ - t for class_map in self.manifest_map.values() - for types in class_map.values() - for t in types - }) + return list( + { + t + for class_map in self.manifest_map.values() + for types in class_map.values() + for t in types + } + ) @cached_property def all_names(self) -> list[str]: @@ -930,7 +1009,13 @@ def all_names(self) -> list[str]: @cached_property def all_classes(self) -> list[str]: """set[str]: A set of all python classes used by this template.""" - return list({cls_name for class_map in self.manifest_map.values() for cls_name in class_map.keys()}) + return list( + { + cls_name + for class_map in self.manifest_map.values() + for cls_name in class_map.keys() + } + ) """ * Template Utils @@ -950,8 +1035,8 @@ def get_template_class(self, class_name: str) -> Any: if not self.plugin: try: module = get_local_module( - module_path='src.templates', - hotswap=self.env.FORCE_RELOAD) + module_path="src.templates", hotswap=self.env.FORCE_RELOAD + ) return getattr(module, class_name) except Exception as e: # Failed to load module @@ -962,7 +1047,7 @@ def get_template_class(self, class_name: str) -> Any: try: if self.env.FORCE_RELOAD: self.plugin.load_module(hotswap=True) - return getattr(self.plugin._module, class_name) + return getattr(self.plugin.module, class_name) except Exception as e: # Failed to load module print_tb(e.__traceback__) @@ -973,17 +1058,20 @@ def generate_template_name(self) -> str: # Use first name on the list and look for types supported for that name name = self.all_names[0] - supported = self.types_supported.copy() if len(self.all_names) == 1 else { - t for types in self.manifest_map[name].values() for t in types} - if 'normal' in supported: - supported.remove('normal') + supported = ( + self.types_supported.copy() + if len(self.all_names) == 1 + else {t for types in self.manifest_map[name].values() for t in types} + ) + if "normal" in supported: + supported.remove("normal") # When 'normal' type is present, just use the name if not supported: return self.all_names[0] # Format types into display names - cats = set() + cats: set[str] = set() for cat, types in layout_map_display_condition_dual.items(): # Add both face types if all([n in supported for n in types]): @@ -1011,24 +1099,27 @@ def check_for_update(self) -> bool: True if Template needs to be updated, otherwise False. """ # Get our metadata - data = gdrive_get_metadata(self.google_drive_id, self.env.API_GOOGLE) - if not data: + if not self.google_drive_id or not ( + data := gdrive_get_metadata(self.google_drive_id, self.env.API_GOOGLE) + ): # File couldn't be located on Google Drive print(f"{self.name} ({self.file_name}) not found on Google Drive!") return False # Cache update data - self._update: TemplateUpdate = { - 'version': data.get('description', 'v1.0.0'), - 'name': data.get('name', self.file_name), - 'size': data['size'] + self._update = { + "version": data.get("description", "v1.0.0"), + "name": data.get("name", self.file_name), + "size": data["size"], } # Compare the versions self.validate_version() if not self.version: return True - if normalize_ver(self.version) == normalize_ver(self.update_version): + if self.update_version and normalize_ver(self.version) == normalize_ver( + self.update_version + ): return False # Template needs an update @@ -1037,19 +1128,19 @@ def check_for_update(self) -> bool: def validate_version(self) -> None: """Checks the current on-file version of this template and if the template is installed, updates the version tracker accordingly.""" - - # If installed but version is not logged, log default version - if self.is_installed and not self.version: - self.con.versions[self.google_drive_id] = '1.0.0' - self.con.update_version_tracker() - return - - # If installed but version mistakenly logged, reset - if not self.is_installed and self.version: - del self.con.versions[self.google_drive_id] - self.con.update_version_tracker() - - def update_template(self, callback: Callable) -> bool: + if self.google_drive_id: + # If installed but version is not logged, log default version + if self.is_installed and not self.version: + self.con.versions[self.google_drive_id] = "1.0.0" + self.con.update_version_tracker() + return + + # If installed but version mistakenly logged, reset + if not self.is_installed and self.version: + del self.con.versions[self.google_drive_id] + self.con.update_version_tracker() + + def update_template(self, callback: Callable[[int, int], None]) -> bool: """Update a given template to the latest version. Args: @@ -1058,36 +1149,49 @@ def update_template(self, callback: Callable) -> bool: Returns: True if succeeded, False if failed. """ - try: - - # Download using Google Drive - result = gdrive_download_file( - url=self.url_google_drive, - path=self.path_download, - path_cookies=PATH.LOGS_COOKIES, - callback=callback - ) if self.google_drive_id else False - - # Google Drive failed or isn't an option, download from Amazon S3 - if not result: - result = download_cloudfront( - url=self.url_amazon, - path=self.path_download, - callback=callback) - - # If downloaded file is a 7z archive, extract the template from it - if self.path_download.suffix == ".7z": - with SevenZipFile(self.path_7z, "r") as archive: - root = self.plugin.path_templates if self.plugin else PATH.TEMPLATES - archive.extractall(root) - self.path_7z.unlink() - - # Return result status - return result - - # Exception caught while downloading / unpacking - except Exception as e: - print(f"Failed to update template: {self.name}", e, format_exc()) + if self.path_download: + try: + result: bool = False + + if self.google_drive_id and self.url_google_drive: + # Download using Google Drive + result = bool( + gdrive_download_file( + url=self.url_google_drive, + path=self.path_download, + path_cookies=PATH.LOGS_COOKIES, + callback=callback, + ) + ) + + if self.url_amazon: + # Google Drive failed or isn't an option, download from Amazon S3 + if not result: + result = download_cloudfront( + url=self.url_amazon, + path=self.path_download, + callback=callback, + ) + + # If downloaded file is a 7z archive, extract the template from it + if result and self.path_download.suffix == ".7z": + with SevenZipFile(self.path_7z, "r") as archive: + root = ( + self.plugin.path_templates + if self.plugin + else PATH.TEMPLATES + ) + archive.extractall(root) + self.path_7z.unlink() + + # Return result status + return result + + # Exception caught while downloading / unpacking + except Exception as e: + print(f"Failed to update template: {self.name}", e, format_exc()) + else: + print("Template update failed. Download path isn't specified.") return False def mark_updated(self) -> None: @@ -1115,8 +1219,8 @@ def get_path_preview(self, class_name: str, class_type: str) -> Path: root = self.plugin.path_img if self.plugin else PATH.SRC_IMG_PREVIEWS # Try with type provided, then with just the class name, fallback to "Not Found" image - path = (root / f'{class_name}[{class_type}]').with_suffix('.jpg') - path = path if path.is_file() else (root / f'{class_name}').with_suffix('.jpg') + path = (root / f"{class_name}[{class_type}]").with_suffix(".jpg") + path = path if path.is_file() else (root / f"{class_name}").with_suffix(".jpg") return path if path.is_file() else PATH.SRC_IMG_NOTFOUND def get_path_config(self, class_name: str) -> Path: @@ -1130,13 +1234,13 @@ def get_path_config(self, class_name: str) -> Path: """ # Get plugin template config if self.plugin: - json_path = (self.plugin.path_config / class_name).with_suffix('.json') + json_path = (self.plugin.path_config / class_name).with_suffix(".json") if json_path.is_file(): return json_path - return json_path.with_suffix('.toml') + return json_path.with_suffix(".toml") # Get app template config - return (PATH.SRC_DATA_CONFIG / class_name).with_suffix('.toml') + return (PATH.SRC_DATA_CONFIG / class_name).with_suffix(".toml") def get_path_ini(self, class_name: str) -> Path: """Gets the path to an INI config file for a given template class. @@ -1149,8 +1253,8 @@ def get_path_ini(self, class_name: str) -> Path: """ # Is this a plugin template? if self.plugin: - return (self.plugin.path_ini / class_name).with_suffix('.ini') - return (PATH.SRC_DATA_CONFIG_INI / class_name).with_suffix('.ini') + return (self.plugin.path_ini / class_name).with_suffix(".ini") + return (PATH.SRC_DATA_CONFIG_INI / class_name).with_suffix(".ini") """ @@ -1172,7 +1276,7 @@ def get_all_plugins(con: AppConstants, env: AppEnvironment) -> dict[str, AppPlug # Load all plugins and plugin templates for folder in [p for p in PATH.PLUGINS.iterdir() if p.is_dir()]: - if folder.stem.startswith('__') or folder.stem.startswith('!'): + if folder.stem.startswith("__") or folder.stem.startswith("!"): continue try: plugin = AppPlugin(con=con, env=env, path=folder) @@ -1188,7 +1292,9 @@ def get_all_plugins(con: AppConstants, env: AppEnvironment) -> dict[str, AppPlug """ -def get_all_templates(con: AppConstants, env: AppEnvironment, plugins: dict[str, AppPlugin]) -> list[AppTemplate]: +def get_all_templates( + con: AppConstants, env: AppEnvironment, plugins: dict[str, AppPlugin] +) -> list[AppTemplate]: """Gets a list of all 'AppTemplate' objects. Args: @@ -1203,11 +1309,13 @@ def get_all_templates(con: AppConstants, env: AppEnvironment, plugins: dict[str, templates: list[AppTemplate] = [] # Load the built-in templates - manifest: dict[str, ManifestTemplateDetails] = load_data_file(PATH.SRC_DATA_MANIFEST) + manifest: dict[str, ManifestTemplateDetails] = load_data_file( + PATH.SRC_DATA_MANIFEST + ) # Build a TemplateDetails for each template for file_name, data in manifest.items(): - data['file'] = file_name + data["file"] = file_name templates.append(AppTemplate(con=con, env=env, data=data)) # Load all plugins and plugin templates @@ -1227,8 +1335,9 @@ def get_template_map(templates: list[AppTemplate]) -> dict[str, TemplateCategory """ # Establish our core category map d: dict[str, TemplateCategoryMap] = { - type_named: {'names': [], 'map': {}} - for type_named in layout_map_category.keys()} + type_named: {"names": [], "map": {}} + for type_named in layout_map_category.keys() + } # Track names for uniqueness names: dict[str, list[str]] = {} @@ -1244,7 +1353,7 @@ def get_template_map(templates: list[AppTemplate]) -> dict[str, TemplateCategory # Ensure name is unique if name in names.get(t, []): # Add plugin to name if plugin provided - if details['object'].plugin: + if details["object"].plugin: # Swap this objects name name = f"{name} ({details['object'].plugin.name})" @@ -1254,18 +1363,18 @@ def get_template_map(templates: list[AppTemplate]) -> dict[str, TemplateCategory name, i = f"{n} ({i})", i + 1 # Add template to map and tracked names - d[cat]['map'].setdefault(t, {})[name] = details - if name not in d[cat]['names']: - d[cat]['names'].append(name) + d[cat]["map"].setdefault(t, {})[name] = details + if name not in d[cat]["names"]: + d[cat]["names"].append(name) names.setdefault(t, []).append(name) # Sort names for t, tcm in d.items(): - sorted_names = [] - if 'Normal' in tcm['names']: - sorted_names.append('Normal') - tcm['names'].remove('Normal') - d[t]['names'] = [*sorted_names, *sorted(tcm['names'])] + sorted_names: list[str] = [] + if "Normal" in tcm["names"]: + sorted_names.append("Normal") + tcm["names"].remove("Normal") + d[t]["names"] = [*sorted_names, *sorted(tcm["names"])] return d @@ -1280,26 +1389,26 @@ def get_template_map_defaults(d: dict[str, TemplateCategoryMap]) -> TemplateSele """ sel: TemplateSelectedMap = {t: None for t in layout_map_types.keys()} first_items: dict[str, TemplateDetails] = {} - for cat, tcm in d.items(): - for t, name_map in tcm['map'].items(): + for template_category_map in d.values(): + for template, name_map in template_category_map["map"].items(): for name, details in name_map.items(): - if t not in first_items: - first_items[t] = details - if sel.get(t): + if template not in first_items: + first_items[template] = details + if sel.get(template): continue - if name == 'Normal': - sel[t] = details.copy() + if name == "Normal": + sel[template] = details.copy() # Ensure each type has a value, fallback on 'first' appearing items - for t, details in sel.items(): + for template, details in sel.items(): if not details: - sel[t] = first_items.get(t, {}).copy() + if first_item := first_items.get(template): + sel[template] = first_item.copy() return sel def get_template_map_selected( - selected: TemplateSelectedMap, - defaults: TemplateSelectedMap + selected: TemplateSelectedMap, defaults: TemplateSelectedMap ) -> TemplateSelectedMap: """Merges the template's selected by the users with a provided mapping of default templates. @@ -1312,7 +1421,7 @@ def get_template_map_selected( """ combined = defaults.copy() for layout, details in selected.items(): - combined[layout] = details.copy() + combined[layout] = details.copy() if details else details return combined @@ -1335,7 +1444,7 @@ def check_for_updates(templates: list[AppTemplate]) -> list[AppTemplate]: # Perform threaded version check requests with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: - results: list[tuple[Future, AppTemplate]] = [] + results: list[tuple[Future[bool], AppTemplate]] = [] # Check each template for updates for t in templates: diff --git a/src/_state.py b/src/_state.py index d0b9f717..d54bc1b6 100644 --- a/src/_state.py +++ b/src/_state.py @@ -10,10 +10,10 @@ from pathlib import Path import sys from threading import Lock -from typing import Optional, Union, Any +from typing import TYPE_CHECKING, Any # Third Party Imports -from hexproof.hexapi import schema as Hexproof +from hexproof.hexapi.schema.meta import Meta from omnitils.exceptions import return_on_exception from omnitils.files import load_data_file, dump_data_file from omnitils.metaclass import Singleton @@ -35,21 +35,24 @@ # Establish global root, based on frozen executable or Python __PATH_CWD__: Path = Path(os.getcwd()) -__PATH_ROOT__: Optional[Path] = Path(sys.executable).parent if ( - getattr(sys, 'frozen', False) -) else (Path(__file__).parent.parent if __file__ else __PATH_CWD__) +__PATH_ROOT__: Path | None = ( + # Path handling for PyInstalller build + Path(sys.executable).parent + if (getattr(sys, "frozen", False)) + # Path handling for regular Python + else (Path(__file__).parent.parent if __file__ else __PATH_CWD__) +) # Switch to root directory if current directory differs if str(__PATH_CWD__) != str(__PATH_ROOT__): os.chdir(__PATH_ROOT__) - class DefinedPaths: """Class for defining reusable named Path objects.""" @classmethod - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) + def __init_subclass__(cls): + super().__init_subclass__() cls.ensure_paths() @classmethod @@ -135,11 +138,33 @@ class PATH(DefinedPaths): environ.setdefault('KIVY_NO_FILELOG', '1') -class AppEnvironment(Dynaconf): +class CustomDynaconf(Dynaconf): + if TYPE_CHECKING: + @cached_property + def API_GOOGLE(self) -> str: ... + @cached_property + def API_AMAZON(self) -> str: ... + @cached_property + def PS_ERROR_DIALOG(self) -> bool: ... + @cached_property + def PS_VERSION(self) -> str | None: ... + @cached_property + def HEADLESS(self) -> bool: ... + @cached_property + def DEV_MODE(self) -> bool: ... + @cached_property + def TEST_MODE(self) -> bool: ... + @cached_property + def FORCE_RELOAD(self) -> bool: ... + @cached_property + def VERSION(self) -> str: ... + + +class AppEnvironment(CustomDynaconf): """Tracking and modifying global environment behavior.""" @staticmethod - def string_or_none(value: Any) -> Optional[str]: + def string_or_none(value: Any) -> str | None: """Casts a value as either a string OR None. Args: @@ -176,10 +201,10 @@ def PS_ERROR_DIALOG(self) -> bool: return super().PS_ERROR_DIALOG @cached_property - def PS_VERSION(self) -> Optional[str]: + def PS_VERSION(self) -> str | None: """str: Photoshop version to try and load, for use when multiple Photoshop versions are installed.""" - if super().PS_VERSION not in ['', None]: - return super().PS_VERSION + if (ver := (super().PS_VERSION)) not in ['', None]: + return ver return None """ @@ -228,7 +253,7 @@ def VERSION(self) -> str: class AppConstants: """Stores global constants that control app behavior.""" __metaclass__ = Singleton - _changes = set() + _changes: set[str] = set() # Thread locking handlers lock_decompress = Lock() @@ -292,7 +317,7 @@ def mana_symbols(self) -> dict[str, str]: """ @tracked_prop - def watermarks(self) -> dict[str, dict]: + def watermarks(self) -> dict[str, dict[str,Any]]: """dict[str, dict]: Maps watermark names to defined formatting rules.""" with suppress(): # Ensure file exists @@ -306,13 +331,13 @@ def watermarks(self) -> dict[str, dict]: """ @tracked_prop - def colors(self) -> dict[str, list[int]]: + def colors(self) -> dict[str, tuple[float,float,float]]: """dict[str, list[int]]: Named reusable colors.""" return { - 'black': [0, 0, 0], - 'white': [255, 255, 255], - 'silver': [167, 177, 186], - 'gold': [166, 135, 75] + 'black': (0, 0, 0), + 'white': (255, 255, 255), + 'silver': (167, 177, 186), + 'gold': (166, 135, 75) } @tracked_prop @@ -327,8 +352,8 @@ def symbol_map(self) -> dict[str, tuple[str, list[ColorObject]]]: return {sym: (n, get_symbol_colors(sym, n, self.mana_colors)) for sym, n in self.mana_symbols.items()} def build_symbol_map( - self, colors: Optional[SymbolColorMap] = None, - symbols: Optional[dict[str, str]] = None + self, colors: SymbolColorMap | None = None, + symbols: dict[str, str] | None = None ) -> None: """Establishes a new `symbol_color_map` using a provided color map and symbol map. @@ -360,7 +385,7 @@ def masks(self) -> dict[int, list[str]]: } @tracked_prop - def gradient_locations(self) -> dict[int, list[Union[int, float]]]: + def gradient_locations(self) -> dict[int, list[int | float]]: """dict[int, list[Union[int, float]]: Maps gradient locations to a number representing the number of color splits.""" return { @@ -375,7 +400,7 @@ def gradient_locations(self) -> dict[int, list[Union[int, float]]]: """ @tracked_prop - def versions(self) -> dict: + def versions(self) -> dict[str,str]: """dict[str, str]: Maps version numbers to template file identifiers.""" with suppress(): # Ensure file exists @@ -396,12 +421,12 @@ def update_version_tracker(self): """ @tracked_prop - def set_data(self) -> dict[str, dict]: + def set_data(self) -> dict[str, dict[str,dict[str,Any]]]: """dict[str, dict]: Returns set data pulled from Hexproof.io mapped to set codes.""" return self.get_set_data() @tracked_prop - def metadata(self) -> dict[str, Hexproof.Meta]: + def metadata(self) -> dict[str, Meta]: """dict[str, dict]: Returns data pulled from Hexproof.io mapped to resources.""" return self.get_meta_data() @@ -442,7 +467,7 @@ def get_user_data(self): """ @return_on_exception({}) - def get_set_data(self) -> dict[str, dict]: + def get_set_data(self) -> dict[str, dict[str,dict[str,Any]]]: """dict: Loaded data from the 'set' data file.""" if not PATH.SRC_DATA_HEXPROOF_SET.is_file(): dump_data_file({}, PATH.SRC_DATA_HEXPROOF_SET) @@ -451,12 +476,12 @@ def get_set_data(self) -> dict[str, dict]: return load_data_file(PATH.SRC_DATA_HEXPROOF_SET) @return_on_exception({}) - def get_meta_data(self) -> dict[str, Hexproof.Meta]: + def get_meta_data(self) -> dict[str, Meta]: """dict: Loaded data from the 'meta' data file.""" if not PATH.SRC_DATA_HEXPROOF_META.is_file(): dump_data_file({}, PATH.SRC_DATA_HEXPROOF_META) # Import watermark library return { - k: Hexproof.Meta(**v) for k, v in + k: Meta(**v) for k, v in load_data_file(PATH.SRC_DATA_HEXPROOF_META)} diff --git a/src/cards.py b/src/cards.py index aa793d75..29cf360a 100644 --- a/src/cards.py +++ b/src/cards.py @@ -5,7 +5,7 @@ # Standard Library Imports from contextlib import suppress from pathlib import Path -from typing import Optional, Union, TypedDict, Any +from typing import TypedDict, Any, cast # Third Party Imports from omnitils.strings import normalize_str @@ -13,8 +13,9 @@ # Local Imports from src._config import AppConfig -from src.console import msg_warn +from src.console import TerminalConsole, msg_warn from src.enums.mtg import TransformIcons, non_italics_abilities, CardTextPatterns +from src.gui.console import GUIConsole from src.schema.colors import ColorObject from src.utils import scryfall @@ -32,19 +33,19 @@ class CardDetails(TypedDict): """Card details obtained from processing a card's art file name.""" name: str - set: Optional[str] - number: Optional[str] - artist: Optional[str] - creator: Optional[str] - file: Union[str, Path] + set: str + number: str + artist: str + creator: str + file: str | Path class FrameDetails(TypedDict): """Frame details obtained from processing frame logic.""" - background: Optional[str] - pinlines: Optional[str] - twins: Optional[str] - identity: Optional[str] + background: str + pinlines: str + twins: str + identity: str is_colorless: bool is_hybrid: bool @@ -54,7 +55,7 @@ class FrameDetails(TypedDict): """ -def get_card_data(card: CardDetails, cfg: AppConfig, logger: Optional[Any] = None) -> Optional[dict]: +def get_card_data(card: CardDetails, cfg: AppConfig, logger: GUIConsole | TerminalConsole | None = None) -> dict[str,Any] | None: """Fetch card data from the Scryfall API. Args: @@ -145,7 +146,7 @@ def parse_card_info(file_path: Path) -> CardDetails: """ -def process_card_data(data: dict, card: CardDetails) -> dict: +def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: """Process any additional required data before sending it to the layout object. Args: @@ -161,7 +162,8 @@ def process_card_data(data: dict, card: CardDetails) -> dict: # Modify meld card data to fit transform layout if data['layout'] == 'meld': # Ignore tokens and other objects - front, back = [], None + front: list[dict[str,Any]] = [] + back: dict[str,Any] | None = None for part in data.get('all_parts', []): if part.get('component') == 'meld_part': front.append(part) @@ -169,16 +171,16 @@ def process_card_data(data: dict, card: CardDetails) -> dict: back = part # Figure out if card is a front or a back - faces = [front[0], back] if ( + faces: list[dict[str,Any] | None] = [front[0], back] if back and ( name_normalized == normalize_str(back.get('name', ''), True) or name_normalized == normalize_str(front[0].get('name', ''), True) ) else [front[1], back] # Pull JSON data for each face and set object to card_face data['card_faces'] = [{ - **scryfall.get_uri_object(yarl.URL(n['uri'])), + **scryfall.get_uri_object(yarl.URL(face['uri'])), 'object': 'card_face' - } for n in faces] + } for face in faces if face] # Add meld transform icon if none provided if not any([bool(n in TransformIcons) for n in data.get('frame_effects', [])]): @@ -188,19 +190,20 @@ def process_card_data(data: dict, card: CardDetails) -> dict: # Check for alternate MDFC / Transform layouts if 'card_faces' in data: # Select the corresponding face - card, i = (data['card_faces'][0], 0) if ( - normalize_str(data['card_faces'][0].get('name', ''), True) == name_normalized - ) else (data['card_faces'][1], 1) + card_faces = cast(list[dict[str,Any]], data['card_faces']) + card_face, i = (card_faces[0], 0) if ( + normalize_str(card_faces[0].get('name', ''), True) == name_normalized + ) else (card_faces[1], 1) # Decide if this is a front face data['front'] = True if i == 0 else False # Transform / MDFC Planeswalker layout - if 'Planeswalker' in card.get('type_line', ''): + if 'Planeswalker' in card_face.get('type_line', ''): data['layout'] = 'planeswalker_tf' if data.get('layout') == 'transform' else 'planeswalker_mdfc' # Transform Saga layout - if 'Saga' in card['type_line']: + if 'Saga' in card_face['type_line']: data['layout'] = 'saga' # Battle layout - if 'Battle' in card['type_line']: + if 'Battle' in card_face['type_line']: data['layout'] = 'battle' return data @@ -236,14 +239,14 @@ def process_card_data(data: dict, card: CardDetails) -> dict: def locate_symbols( text: str, symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Optional[Any] = None + logger: Any | None = None ) -> tuple[str, list[CardSymbolString]]: """Locate symbols in the input string, replace them with the proper characters from the mana font, and determine the colors those characters need to be. Args: text: String to analyze for symbols. - symbol_map: Maps a characters and colors to a scryfall symbol string. + symbol_map: Maps characters and colors to a scryfall symbol string. logger: Console or other logger object used to relay warning messages. Returns: @@ -277,9 +280,9 @@ def locate_symbols( def locate_italics( st: str, - italics_strings: list, + italics_strings: list[str], symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Optional[Any] = None + logger: Any | None = None ) -> list[CardItalicString]: """Locate all instances of italic strings in the input string and record their start and end indices. @@ -292,7 +295,7 @@ def locate_italics( Returns: List of italic string indices (start and end). """ - indexes = [] + indexes: list[tuple[int,int]] = [] for italic in italics_strings: # Look for symbols present in italicized text @@ -333,7 +336,7 @@ def generate_italics(card_text: str) -> list[str]: Returns: List of italics strings. """ - italic_text = [] + italic_text: list[str] = [] # Find each reminder text block end_index = 0 diff --git a/src/commands/build.py b/src/commands/build.py index c297e456..2ea8dc37 100644 --- a/src/commands/build.py +++ b/src/commands/build.py @@ -1,8 +1,6 @@ """ * CLI Commands: Build """ -# Standard Library -from typing import Optional # Third party imports import click @@ -21,7 +19,7 @@ @click.option('-B', '--beta', is_flag=True, default=False, help="Build app as a Beta release.") @click.option('-C', '--console', is_flag=True, default=False, help="Build app with console enabled.") @click.option('-R', '--raw', is_flag=True, default=False, help="Build app without creating zip release archive.") -def build_app(version: Optional[str] = None, beta: bool = False, console: bool = False, raw: bool = False) -> None: +def build_app(version: str | None = None, beta: bool = False, console: bool = False, raw: bool = False) -> None: """Build Proxyshop as an executable release. Args: @@ -41,9 +39,7 @@ def build_app(version: Optional[str] = None, beta: bool = False, console: bool = @click.group( name='build', help='Command utilities for building and managing release files.', - commands=dict( - app=build_release - ) + commands={"app": build_app}, ) def build_cli() -> None: """App build tools CLI.""" diff --git a/src/commands/files.py b/src/commands/files.py index 490ebd62..b897c7e2 100644 --- a/src/commands/files.py +++ b/src/commands/files.py @@ -3,7 +3,6 @@ """ # Standard Library from pathlib import Path -from typing import Optional # Third Party Imports import click @@ -32,7 +31,7 @@ def compress_cli(): ) @click.argument('template') @click.argument('plugin', required=False) -def compress_template(template: str, plugin: Optional[str] = None) -> None: +def compress_template(template: str, plugin: str | None = None) -> None: """Compress a template by name and optionally plugin name. Args: diff --git a/src/commands/test/compression.py b/src/commands/test/compression.py index 28e6addf..aa6f9d64 100644 --- a/src/commands/test/compression.py +++ b/src/commands/test/compression.py @@ -3,7 +3,6 @@ """ # Standard Library Imports from pathlib import Path -from typing import Optional from time import perf_counter # Third Party Imports @@ -99,7 +98,7 @@ def test_jpeg_compression( test_dpi=True, test_resample=True, test_optimize=True, - test_quality: Optional[list[int]] = None + test_quality: list[int] | None = None ) -> None: """ Test a battery of JPEG compression settings. diff --git a/src/commands/test/frame_logic.py b/src/commands/test/frame_logic.py index 252de7f8..6e9d1f17 100644 --- a/src/commands/test/frame_logic.py +++ b/src/commands/test/frame_logic.py @@ -3,14 +3,12 @@ * Credit to Chilli: https://tinyurl.com/chilli-frame-logic-tests """ # Standard Library Imports -import os from multiprocessing import cpu_count from concurrent.futures import ( ThreadPoolExecutor as Pool, as_completed, Future) from pathlib import Path -from typing import Optional # Third Part Imports from tqdm import tqdm @@ -63,7 +61,7 @@ def format_result(layout: CardLayout) -> FrameData: """ -def test_case(card_name: str, card_data: FrameData) -> Optional[tuple[str, FrameData, FrameData]]: +def test_case(card_name: str, card_data: FrameData) -> tuple[str, FrameData, FrameData] | None: """Test frame logic for a target test case. Args: diff --git a/src/commands/test/text_logic.py b/src/commands/test/text_logic.py index 25213ad6..154e7d4d 100644 --- a/src/commands/test/text_logic.py +++ b/src/commands/test/text_logic.py @@ -54,13 +54,13 @@ def test_all_cases() -> bool: # Log what we expect if not result_expected: - logr.warning(f"This card doesn't have italic text!") + logr.warning("This card doesn't have italic text!") else: logr.warning(f"Italic strings expected: {msg_expected}") # Log what we found if not result_actual: - logr.warning(f"No italic strings were found!") + logr.warning("No italic strings were found!") else: logr.warning(f"Italic strings found: {msg_actual}") diff --git a/src/commands/test/utility.py b/src/commands/test/utility.py index dfcafb0c..ca8a818b 100644 --- a/src/commands/test/utility.py +++ b/src/commands/test/utility.py @@ -4,14 +4,12 @@ """ # Standard Library Imports from contextlib import suppress -from typing import Optional, Union from _ctypes import COMError from xml.dom import minidom import warnings import logging import json import csv -import os # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -30,10 +28,11 @@ # Local Imports from src import APP, TEMPLATES import src.helpers as psd +from src.schema.colors import ColorObject from src.utils.adobe import LayerContainer # Photoshop infrastructure -cID, sID = APP.charIDtoTypeID, APP.stringIDToTypeID +cID, sID = APP.charIDToTypeID, APP.stringIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs # Reference Box colors @@ -48,7 +47,7 @@ """ -def test_new_color(new: str, old: Optional[str] = None, ignore: Optional[list[str]] = None) -> None: +def test_new_color(new: str, old: str | None = None, ignore: list[str] | None = None) -> None: """Enables given color in all necessary groups. Optionally disable a color in those groups. Args: @@ -71,8 +70,8 @@ def test_new_color(new: str, old: Optional[str] = None, ignore: Optional[list[st def make_duals( name: str = "Pinlines & Textbox", - mask_top: Optional[ArtLayer] = None, - mask_bottom: Optional[ArtLayer] = None + mask_top: ArtLayer | None = None, + mask_bottom: ArtLayer | None = None ): """Creates dual color layers for a given group. @@ -106,9 +105,9 @@ def make_duals( def create_blended_layer( - colors: Union[str, list[str]], + colors: str | list[str], group: LayerSet, - masks: Union[None, ArtLayer, list[ArtLayer]] = None + masks: None | ArtLayer | list[ArtLayer] = None ): """Create a multicolor layer using a gradient mask. @@ -298,16 +297,16 @@ def apply_single_line_composer(layer: ArtLayer) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - ref1.PutProperty(sID("property"), sID("paragraphStyle")) + ref1.putProperty(sID("property"), sID("paragraphStyle")) ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.PutReference(sID("target"), ref1) - desc2.PutInteger(sID("textOverrideFeatureName"), 808464691) - desc2.PutBoolean(sID("textEveryLineComposer"), False) - desc1.PutObject(sID("to"), sID("paragraphStyle"), desc2) - APP.Executeaction(sID("set"), desc1, NO_DIALOG) + desc1.putReference(sID("target"), ref1) + desc2.putInteger(sID("textOverrideFeatureName"), 808464691) + desc2.putBoolean(sID("textEveryLineComposer"), False) + desc1.putObject(sID("to"), sID("paragraphStyle"), desc2) + APP.executeAction(sID("set"), desc1, NO_DIALOG) -def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: Optional[str] = " "): +def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str | None = " "): """Append the "from_layer" contents to the end of "to_layer" contents with optional separator. Preserves the complete style range formatting of both text contents. @@ -399,7 +398,7 @@ def xmp_remove_ancestors() -> None: """ -def create_color_shape(layer: ArtLayer, color: list) -> ArtLayer: +def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: layer_name = layer.name color = psd.get_color(color) docref = APP.activeDocument @@ -415,7 +414,7 @@ def create_color_shape(layer: ArtLayer, color: list) -> ArtLayer: ref2.putProperty(sID("selectionClass"), sID("selection")) desc1.putReference(sID("from"), ref2) desc1.putUnitDouble(sID("tolerance"), sID("pixelsUnit"), 2.000000) - APP.executeaction(sID("make"), desc1, NO_DIALOG) + APP.executeAction(sID("make"), desc1, NO_DIALOG) ref1 = ActionReference() desc1 = ActionDescriptor() @@ -430,7 +429,7 @@ def create_color_shape(layer: ArtLayer, color: list) -> ArtLayer: desc3.putObject(sID("color"), sID("RGBColor"), desc4) desc2.putObject(sID("type"), sID("solidColorLayer"), desc3) desc1.putObject(sID("using"), sID("contentLayer"), desc2) - APP.executeaction(sID("make"), desc1, NO_DIALOG) + APP.executeAction(sID("make"), desc1, NO_DIALOG) APP.activeDocument.activeLayer.name = layer_name # Check dims @@ -500,7 +499,7 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: # Log a sorted master list master: dict[str, list] = {k: v for k, v in sorted(master.items(), key=lambda item: len(item[1]))} - with open(f'logs/FONTS.json', 'w', encoding='utf-8') as f: + with open('logs/FONTS.json', 'w', encoding='utf-8') as f: json.dump(master, f, indent=2) return master @@ -512,7 +511,7 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: """ -def insert_data_set_variables(xml_data: str, from_path: str, to_path: Optional[str] = None) -> None: +def insert_data_set_variables(xml_data: str, from_path: str, to_path: str | None = None) -> None: """Inserts data set variable XML into a PSD document. Args: @@ -569,8 +568,8 @@ def format_data_set_variable_name(text: str) -> str: def get_data_set_variables( - group: Optional[Union[LayerContainer]] = None, - tree: Optional[str] = None + group: LayerContainer | None = None, + tree: str | None = None ) -> list[dict[str, str]]: """Get data set variables for all ArtLayer and LayerSet objects in document or LayerSet. diff --git a/src/console.py b/src/console.py index 6e899a6f..3e17e506 100644 --- a/src/console.py +++ b/src/console.py @@ -9,12 +9,13 @@ from threading import Lock, Event, Thread from datetime import datetime as dt from functools import cached_property -from typing import Optional, TYPE_CHECKING +from typing import Any, TYPE_CHECKING # Third Party Imports from omnitils.enums import StrConstant -from omnitils.logs import logger, Logger +from omnitils.logs import logger from omnitils.metaclass import Singleton +from loguru._logger import Logger # Local Imports from src._config import AppConfig @@ -77,7 +78,7 @@ def msg_italics(msg: str) -> str: return f"[i]{msg}[/i]" -def msg_error(msg: str, reason: Optional[str] = None) -> str: +def msg_error(msg: str, reason: str | None = None) -> str: """Adds a defined 'error' color tag to Proxyshop console message. Args: @@ -91,7 +92,7 @@ def msg_error(msg: str, reason: Optional[str] = None) -> str: return f"{msg_bold(msg)} - {msg_italics(reason)}" if reason else msg -def msg_warn(msg: str, reason: Optional[str] = None) -> str: +def msg_warn(msg: str, reason: str | None = None) -> str: """Adds a defined 'warning' color tag to Proxyshop console message. Args: @@ -185,20 +186,20 @@ def logger(self) -> Logger: Logger Object Methods """ - def debug(self, *args, **kwargs): - return self.logger.debug(*args, **kwargs) + def debug(self, msg: str, *args: Any, **kwargs: Any): + return self.logger.debug(msg, *args, **kwargs) - def info(self, *args, **kwargs): - return self.logger.info(*args, **kwargs) + def info(self, msg: str, *args: Any, **kwargs: Any): + return self.logger.info(msg, *args, **kwargs) - def warning(self, *args, **kwargs): - return self.logger.warning(*args, **kwargs) + def warning(self, msg: str, *args: Any, **kwargs: Any): + return self.logger.warning(msg, *args, **kwargs) - def failed(self, *args, **kwargs): - return self.logger.error(*args, **kwargs) + def failed(self, msg: str, *args: Any, **kwargs: Any): + return self.logger.error(msg, *args, **kwargs) - def critical(self, *args, **kwargs): - return self.logger.critical(*args, **kwargs) + def critical(self, msg: str, *args: Any, **kwargs: Any): + return self.logger.critical(msg, *args, **kwargs) """ Reusable Strings @@ -228,7 +229,7 @@ def time(self) -> str: Utility Methods """ - def log_exception(self, error: Exception, *_) -> None: + def log_exception(self, error: Exception, *args: Any) -> None: """Log python exception. Args: @@ -248,7 +249,7 @@ def clear() -> None: def update( self, msg: str = "", - exception: Optional[Exception] = None, + exception: Exception | None = None, end: str = "\n" ) -> None: """ @@ -277,9 +278,9 @@ def log_error( self, thr: Event, card: str, - template: Optional[str] = None, + template: str | None = None, msg: str = "Encountered a general error!", - e: Optional[Exception] = None + e: Exception | None = None ) -> bool: """ Log failed card and exception if provided, then prompt user to make a decision. @@ -295,9 +296,9 @@ def log_error( def error( self, - thr: Optional[Event] = None, + thr: Event | None = None, msg: str = 'Encountered a general error!', - exception: Optional[Exception] = None, + exception: Exception | None = None, end: str = '\nShould I continue?\n' ) -> bool: """Display error, wait for user to cancel or continue. @@ -340,7 +341,7 @@ def error( User Prompt Signals """ - def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: + def await_choice(self, thr: Event |None, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: """ Prompt the user to either continue or cancel. @param thr: Event object representing the status of the render thread. diff --git a/src/enums/adobe.py b/src/enums/adobe.py index 361a2036..6242bf5b 100644 --- a/src/enums/adobe.py +++ b/src/enums/adobe.py @@ -1,9 +1,10 @@ """ * Enums for Photoshop Actions """ + # Standard Library Imports from enum import Enum -from typing import Literal, Union +from typing import Literal # Third Party Imports from omnitils.enums import StrConstant @@ -17,7 +18,7 @@ class DescriptorEnum(Enum): - def __call__(self, *args, **kwargs) -> int: + def __call__(self) -> int: return self.value @property @@ -27,58 +28,56 @@ def value(self) -> int: class Dimensions(StrConstant): """Layer dimension descriptors.""" - Width = 'width' - Height = 'height' - CenterX = 'center_x' - CenterY = 'center_y' - Left = 'left' - Right = 'right' - Top = 'top' - Bottom = 'bottom' + + Width = "width" + Height = "height" + CenterX = "center_x" + CenterY = "center_y" + Left = "left" + Right = "right" + Top = "top" + Bottom = "bottom" class Alignment(DescriptorEnum): """Selection alignment descriptors.""" - Top: str = 'ADSTops' - Bottom: str = 'ADSBottoms' - Left: str = 'ADSLefts' - Right: str = 'ADSRights' - CenterHorizontal: str = 'ADSCentersH' - CenterVertical: str = 'ADSCentersV' + + Top = "ADSTops" + Bottom = "ADSBottoms" + Left = "ADSLefts" + Right = "ADSRights" + CenterHorizontal = "ADSCentersH" + CenterVertical = "ADSCentersV" class TextAlignment(DescriptorEnum): """Selection alignment descriptors.""" - Left: str = 'left' - Right: str = 'right' - Center: str = 'center' + + Left = "left" + Right = "right" + Center = "center" class Stroke(DescriptorEnum): """Stroke effect descriptors.""" - Inside: str = 'insetFrame' - Outside: str = 'outsetFrame' - Center: str = 'centeredFrame' + + Inside = "insetFrame" + Outside = "outsetFrame" + Center = "centeredFrame" @staticmethod def position( pos: Literal[ - 'in', 'insetFrame', - 'out', 'outsetFrame', - 'center', 'centeredFrame' - ] - ) -> Union[ - 'Stroke.Inside.value', - 'Stroke.Outside.value', - 'Stroke.Center.value' - ]: + "in", "insetFrame", "out", "outsetFrame", "center", "centeredFrame" + ], + ) -> int: """ Get the proper stroke action enum from canonical user input. @param pos: "in", "out", or "center" @return: Proper action descriptor ID. """ - if pos in ['in', 'insetFrame']: + if pos in ["in", "insetFrame"]: return Stroke.Inside.value - elif pos in ['center', 'centeredFrame']: + elif pos in ["center", "centeredFrame"]: return Stroke.Center.value return Stroke.Outside.value diff --git a/src/enums/mtg.py b/src/enums/mtg.py index 0e1087fa..f2f37811 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -116,10 +116,10 @@ class LayoutScryfall(StrConstant): } """Maps Layout types to their equivalent Layout category.""" -layout_map_types = { - raw: named for named, raw in sum([ - [(k, n) for n in names] for k, names in layout_map_category.items() - ], []) +layout_map_types: dict[str,LayoutCategory] = { + raw: named for named, raw in ( + (k, n) for k, names in layout_map_category.items() for n in names + ) } """Maps Layout types to a display formatted Layout category (with Back or Front).""" @@ -358,31 +358,31 @@ class CardTextPatterns: """Defined card data regex patterns.""" # Rules Text - Special Card Types - LEVELER: re.Pattern = re.compile(r"(.*?)\nLEVEL (\d*-\d*)\n(\d*/\d*)\n(.*?)\nLEVEL (\d*\+)\n(\d*/\d*)\n(.*?)$") - PROTOTYPE: re.Pattern = re.compile(r"Prototype (.+) [—\-] ([0-9]{0,2}/[0-9]{0,2}) \((.+)\)") - PLANESWALKER: re.Pattern = re.compile(r"(^[^:]*$|^.*:.*$)", re.MULTILINE) - CLASS: re.Pattern = re.compile(r"(.+?): Level (\d)\n(.+)") + LEVELER: re.Pattern[str] = re.compile(r"(.*?)\nLEVEL (\d*-\d*)\n(\d*/\d*)\n(.*?)\nLEVEL (\d*\+)\n(\d*/\d*)\n(.*?)$") + PROTOTYPE: re.Pattern[str] = re.compile(r"Prototype (.+) [—\-] ([0-9]{0,2}/[0-9]{0,2}) \((.+)\)") + PLANESWALKER: re.Pattern[str] = re.compile(r"(^[^:]*$|^.*:.*$)", re.MULTILINE) + CLASS: re.Pattern[str] = re.compile(r"(.+?): Level (\d)\n(.+)") # Filename - Card Art - PATH_ARTIST: re.Pattern = re.compile(r"\(+(.*?)\)") - PATH_SPLIT: re.Pattern = re.compile(r"[\[({$]") - PATH_SET: re.Pattern = re.compile(r"\[(.*)]") - PATH_NUM: re.Pattern = re.compile(r"\{(.*)}") - PATH_CONDITION: re.Pattern = re.compile(r'<([^>]*)>') + PATH_ARTIST: re.Pattern[str] = re.compile(r"\(+(.*?)\)") + PATH_SPLIT: re.Pattern[str] = re.compile(r"[\[({$]") + PATH_SET: re.Pattern[str] = re.compile(r"\[(.*)]") + PATH_NUM: re.Pattern[str] = re.compile(r"\{(.*)}") + PATH_CONDITION: re.Pattern[str] = re.compile(r'<([^>]*)>') # Mana - Symbols - SYMBOL: re.Pattern = re.compile(r"(\{.*?})") - MANA_NORMAL: re.Pattern = re.compile(r"{([WUBRG])}") - MANA_PHYREXIAN: re.Pattern = re.compile(r"{([WUBRG])/P}") - MANA_HYBRID: re.Pattern = re.compile(r"{([2WUBRG])/([WUBRG])}") - MANA_PHYREXIAN_HYBRID: re.Pattern = re.compile(r"{([WUBRG])/([WUBRG])/P}") + SYMBOL: re.Pattern[str] = re.compile(r"(\{.*?})") + MANA_NORMAL: re.Pattern[str] = re.compile(r"{([WUBRG])}") + MANA_PHYREXIAN: re.Pattern[str] = re.compile(r"{([WUBRG])/P}") + MANA_HYBRID: re.Pattern[str] = re.compile(r"{([2WUBRG])/([WUBRG])}") + MANA_PHYREXIAN_HYBRID: re.Pattern[str] = re.compile(r"{([WUBRG])/([WUBRG])/P}") # Text - Extra Spaces - EXTRA_SPACE: re.Pattern = re.compile(r" +") + EXTRA_SPACE: re.Pattern[str] = re.compile(r" +") # Text - Reminder - TEXT_REMINDER: re.Pattern = re.compile(r"\([^()]*\)") - TEXT_REMINDER_ENDING = re.compile(r"[\s\S]*(\([^()]*\))$", ) + TEXT_REMINDER: re.Pattern[str] = re.compile(r"\([^()]*\)") + TEXT_REMINDER_ENDING: re.Pattern[str] = re.compile(r"[\s\S]*(\([^()]*\))$", ) # Text - Italicised Ability - TEXT_ABILITY: re.Pattern = re.compile(r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE) + TEXT_ABILITY: re.Pattern[str] = re.compile(r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE) diff --git a/src/frame_logic.py b/src/frame_logic.py index 4eee1566..de2c74d1 100644 --- a/src/frame_logic.py +++ b/src/frame_logic.py @@ -2,13 +2,14 @@ * Frame Logic Module """ # Standard Library Imports -from functools import cached_property, cache -from typing import Union, Iterable +from collections.abc import Iterable, Iterator +from functools import cache, cached_property +from typing import Any # Local Imports from src.cards import FrameDetails -from src.enums.mtg import Rarity from src.enums.layers import LAYERS +from src.enums.mtg import Rarity """ * Planned Utility Classes @@ -35,9 +36,8 @@ def __init__(self, text: str): self._text = text self._lines = [RulesTextLine(n) for n in text.split('\n')] - def __iter__(self): - for line in self._lines: - yield line + def __iter__(self) -> Iterator[RulesTextLine]: + yield from self._lines """ @@ -148,7 +148,7 @@ def contains_frame_colors(text: str) -> bool: return False -def get_ordered_colors(text: Union[str, Iterable]) -> str: +def get_ordered_colors(text: str | Iterable[str]) -> str: """Takes in a string of letters representing color identity and puts them in the MTG accurate letter order. @@ -161,7 +161,7 @@ def get_ordered_colors(text: Union[str, Iterable]) -> str: # Validate the input if not text: return '' - if isinstance(text, Iterable): + if not isinstance(text, str): text = ''.join(text) # Match an ordered color @@ -227,7 +227,7 @@ def get_color_identity_nonland( return get_ordered_colors(get_mana_cost_colors(mana_cost)) -def check_hybrid_color_card(color_identity: Union[str, list[str]], mana_cost: str, is_dfc: bool) -> bool: +def check_hybrid_color_card(color_identity: str | list[str], mana_cost: str, is_dfc: bool) -> bool: """Check a number of inputs to see if this card is: - A card with only hybrid Mana symbols - Only 2 colors represented @@ -255,7 +255,7 @@ def check_hybrid_color_card(color_identity: Union[str, list[str]], mana_cost: st return False -def check_hybrid_mana_cost(color_identity: Union[str, list[str]], mana_cost: str) -> bool: +def check_hybrid_mana_cost(color_identity: str | list[str], mana_cost: str) -> bool: """More simplified hybrid mana test for isolated mana cases e.g. Adventure spells. Args: @@ -277,7 +277,7 @@ def check_hybrid_mana_cost(color_identity: Union[str, list[str]], mana_cost: str """ -def get_frame_details(card: dict) -> FrameDetails: +def get_frame_details(card: dict[str,Any]) -> FrameDetails: """Figure out which layers to use for pinlines, background, twins and define the color identity. Pass the card to an appropriate function based on card type. @@ -292,7 +292,7 @@ def get_frame_details(card: dict) -> FrameDetails: return get_frame_details_nonland(card) -def get_frame_details_land(card: dict) -> FrameDetails: +def get_frame_details_land(card: dict[str,Any]) -> FrameDetails: """Card is a Land card, must check a variety of cases to identify the appropriate color identity. Args: @@ -450,7 +450,7 @@ def get_frame_details_land(card: dict) -> FrameDetails: return result -def get_frame_details_nonland(card: dict) -> FrameDetails: +def get_frame_details_nonland(card: dict[str,Any]) -> FrameDetails: """Get frame details related to a Non-Land card. Must discern the frame color identity, for example Noble Hierarch's color identity is [W, U, G] on Scryfall, but the frame is Green. @@ -560,7 +560,7 @@ def get_frame_details_nonland(card: dict) -> FrameDetails: """ -def get_special_rarity(rarity: str, card: dict) -> str: +def get_special_rarity(rarity: str, card: dict[str,Any]) -> str: """Control for special rarities. Args: @@ -575,7 +575,7 @@ def get_special_rarity(rarity: str, card: dict) -> str: if card.get('frame') == '1997': return Rarity.T # Championship cards - if 'Champion' in card.get('set_name'): + if 'Champion' in card.get('set_name', ''): return Rarity.M # Masterpiece if card.get('set_type') == 'masterpiece': diff --git a/src/gui/app.py b/src/gui/app.py index d1d6a55e..573c35a6 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -14,7 +14,7 @@ from functools import cached_property from threading import Event, Thread, Lock from multiprocessing import cpu_count -from typing import Union, Optional, Callable +from collections.abc import Callable # Third-party Imports import requests @@ -137,7 +137,7 @@ def __init__( plugins: dict[str, AppPlugin], templates: list[AppTemplate], template_map: dict[str, TemplateCategoryMap], - templates_default: dict[str, TemplateDetails], + templates_default: TemplateSelectedMap, **kwargs ): # Call super @@ -250,14 +250,14 @@ def current_render(self) -> BaseTemplate: return self._current_render @property - def docref(self) -> Optional[Document]: + def docref(self) -> Document | None: """Optional[Document]: Tracks the currently open Photoshop document.""" if self.current_render and hasattr(self.current_render, 'docref'): return self.current_render.docref or None return None @property - def thread(self) -> Optional[Event]: + def thread(self) -> Event | None: """Optional[Event]: Tracks the current render threading Event.""" if self.current_render and hasattr(self.current_render, 'event'): return self.current_render.event or None @@ -403,7 +403,7 @@ def close_document(self) -> None: """ @render_process_wrapper - def render_all(self, target: bool = False, files: Optional[list[Path]] = None) -> None: + def render_all(self, target: bool = False, files: list[Path] | None = None) -> None: """Render cards using all images located in the art folder. Args: @@ -591,61 +591,62 @@ def test_all(self, deep: bool = False) -> None: cards = {k: v for k, v in [next(iter(cards.items()))]} if not deep else cards self.console.update(msg_success(f'\n— {card_type.upper()}')) + if card_type in layout_map_types: # Loop through templates to test - for name, template in self.template_map[layout_map_types[card_type]]['map'][card_type].items(): - self.console.update(f"{template['class_name']} ... ", end="") + for template in self.template_map[layout_map_types[card_type]]['map'][card_type].values(): + self.console.update(f"{template['class_name']} ... ", end="") - # Is this template installed? - if not template['object'].is_installed: - self.console.update(msg_warn('SKIPPED (Template not installed)')) - continue + # Is this template installed? + if not template['object'].is_installed: + self.console.update(msg_warn('SKIPPED (Template not installed)')) + continue - # Can this template's class be loaded? - loaded_class = template['object'].get_template_class(template['class_name']) - if not loaded_class: - self.console.update(msg_error('SKIPPED (Template class failed to load)')) - continue + # Can this template's class be loaded? + loaded_class = template['object'].get_template_class(template['class_name']) + if not loaded_class: + self.console.update(msg_error('SKIPPED (Template class failed to load)')) + continue - # Load constants and config for this template - self.cfg.load(config=template['config']) - self.con.reload() + # Load constants and config for this template + self.cfg.load(config=template['config']) + self.con.reload() - # Loop through cards to test - failures: list[tuple[str, str, str]] = [] - times: list[float] = [] - for card_name, card_case in cards.items(): + # Loop through cards to test + failures: list[tuple[str, str, str]] = [] + times: list[float] = [] + for card_name, card_case in cards.items(): - # Attempt to assign a layout - layout = assign_layout(Path(card_name)) - if isinstance(layout, str): - failures.append((card_name, card_case, 'Failed to assign layout')) - continue + # Attempt to assign a layout + layout = assign_layout(Path(card_name)) + if isinstance(layout, str): + failures.append((card_name, card_case, 'Failed to assign layout')) + continue - # Grab the template class and start the render thread - layout.art_file = PATH.SRC_IMG / 'test.jpg' - result = self.start_render(layout, template, loaded_class) - if result is None: - failures.append((card_name, card_case, 'Failed to render')) + # Grab the template class and start the render thread + layout.art_file = PATH.SRC_IMG / 'test.jpg' + result = self.start_render(layout, template, loaded_class) + if result is None: + failures.append((card_name, card_case, 'Failed to render')) + else: + times.append(result) + + # Was thread cancelled? + if self.thread_cancelled: + return + + # Create a summary message + if failures: + fail_log = get_bullet_points([ + f"{name} ({case}) — {reason}" + for name, case, reason in failures]) + summary = msg_error(f'FAILED{fail_log}') else: - times.append(result) - - # Was thread cancelled? - if self.thread_cancelled: - return - - # Create a summary message - if failures: - fail_log = get_bullet_points([ - f"{name} ({case}) — {reason}" - for name, case, reason in failures]) - summary = msg_error(f'FAILED{fail_log}') - else: - avg = round(sum(times) / len(times), 1) - summary = msg_success(f'{avg}s Avg.') + avg = round(sum(times) / len(times), 1) + summary = msg_success(f'{avg}s Avg.') - # Log summary and continue to next template - self.console.update(summary) - self.close_document() + # Log summary and continue to next template + self.console.update(summary) + self.close_document() @render_process_wrapper def test_target(self, card_type: str, template: TemplateDetails) -> None: @@ -715,7 +716,7 @@ def start_render( loaded_class: type[BaseTemplate], reload_config: bool = False, reload_constants: bool = False - ) -> Optional[float]: + ) -> float | None: """Execute a render job using a given card layout, template, and template class. Args: @@ -730,7 +731,7 @@ def start_render( """ # Notify the user if not self.env.TEST_MODE: - self.console.update(msg_success(f"---- {card.display_name} ----")) + self.console.update(msg_success(f"---- {card.display_name} ({card.artist}) [{card.set}] {card.collector_number_raw} ----")) # Reload config and/or constants if reload_config: @@ -892,7 +893,7 @@ async def open_app_settings() -> None: cfg_panel = SettingsPopup() cfg_panel.open() - def build(self) -> Union[TestApp, AppContainer]: + def build(self) -> TestApp | AppContainer: """Build the app for display. Returns: @@ -978,7 +979,7 @@ def on_start(self) -> None: return # Missing fonts - self.console.update(f"Fonts ... {msg_warn(f'Missing or outdated fonts:')}", end='') + self.console.update(f"Fonts ... {msg_warn('Missing or outdated fonts:')}", end='') if missing: self.console.update( get_bullet_points([f"{f['name']} — {msg_warn('Not Installed')}" for f in missing.values()]), end="") diff --git a/src/gui/console.py b/src/gui/console.py index b730d3dc..9743990c 100644 --- a/src/gui/console.py +++ b/src/gui/console.py @@ -9,7 +9,7 @@ import traceback from functools import cached_property from threading import Thread, Event, Lock -from typing import Optional, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from datetime import datetime as dt # Third Party Imports @@ -64,7 +64,7 @@ def main(self) -> Any: """ @cached_property - def output(self) -> 'ConsoleOutput': + def output(self) -> ConsoleOutput: """Label where console output is stored.""" return self.ids.console_output @@ -166,7 +166,7 @@ def clear(self) -> None: def update( self, msg: str = "", - exception: Optional[Exception] = None, + exception: Exception | None = None, end: str = "\n" ) -> None: """Add text to console output. @@ -186,9 +186,9 @@ def log_error( self, thr: Event, card: str, - template: Optional[str] = None, + template: str | None = None, msg: str = 'Encountered a general error!', - e: Optional[Exception] = None + e: Exception | None = None ) -> bool: """Log failed card and exception if provided, then prompt user to make a decision. @@ -208,9 +208,9 @@ def log_error( def error( self, - thr: Optional[Event] = None, + thr: Event | None = None, msg: str = 'Encountered a general error!', - exception: Optional[Exception] = None, + exception: Exception | None = None, end: str = '\nShould I continue?\n' ) -> bool: """Display error, wait for user to cancel or continue. diff --git a/src/gui/popup/settings.py b/src/gui/popup/settings.py index 9bb9b021..a071f093 100644 --- a/src/gui/popup/settings.py +++ b/src/gui/popup/settings.py @@ -4,7 +4,6 @@ # Standard Library Imports from functools import cached_property from os import PathLike -from typing import Union # Third Party Imports import kivy.utils as utils @@ -144,6 +143,19 @@ def _create_popup(self, instance): # all done, open the popup ! popup.open() + def _validate(self, instance): + # Check float status by using the current input text. + # This allows converting between int and float. + is_float = '.' in str(self.textinput.text) + self._dismiss() + try: + if is_float: + self.value = str(float(self.textinput.text)) + else: + self.value = str(int(self.textinput.text)) + except ValueError: + return + class FormattedSettingOptions(SettingOptions): """Create custom SettingOptions class to allow Markup in title.""" @@ -189,7 +201,7 @@ class SettingsPopup(ModalView, GlobalAccess): """ @staticmethod - def get_config(ini_file: Union[str, PathLike]) -> ConfigParser: + def get_config(ini_file: str | PathLike) -> ConfigParser: config = ConfigParser(allow_no_value=True) config.optionxform = str config.read(str(ini_file)) diff --git a/src/gui/tabs/main.py b/src/gui/tabs/main.py index b06cf234..fc438f36 100644 --- a/src/gui/tabs/main.py +++ b/src/gui/tabs/main.py @@ -4,7 +4,6 @@ # Standard Library Imports import os from pathlib import Path -from typing import Optional from functools import cached_property # Kivy Imports @@ -127,7 +126,7 @@ def add_template_rows(self) -> None: for name in self.templates['names']: # Get details for each type - templates: dict[str, Optional[type[TemplateDetails]]] = { + templates: dict[str, type[TemplateDetails] | None] = { t: self.templates['map'][t].get(name) for t in self.types} # Is template installed? @@ -271,7 +270,7 @@ class TemplateResetDefaultButton(HoverButton, GlobalAccess): async def reset_default(self) -> None: """Removes the INI config file containing customized settings for a specific template class.""" - _file, removed = self.parent.config.template_path_ini, False + _file = self.parent.config.template_path_ini if _file and _file.is_file(): try: os.remove(_file) diff --git a/src/gui/tabs/tools.py b/src/gui/tabs/tools.py index 85d53a30..11f66040 100644 --- a/src/gui/tabs/tools.py +++ b/src/gui/tabs/tools.py @@ -5,7 +5,7 @@ import os from functools import cached_property from pathlib import Path -from typing import Callable +from collections.abc import Callable from threading import Event from concurrent.futures import ThreadPoolExecutor as Pool, as_completed diff --git a/src/gui/utils/behaviors.py b/src/gui/utils/behaviors.py index 3cad4f12..eff24eb8 100644 --- a/src/gui/utils/behaviors.py +++ b/src/gui/utils/behaviors.py @@ -11,7 +11,7 @@ """ -class HoverBehavior(object): +class HoverBehavior: """Utility modifier class which adds hover behavior to layout elements. Events: @@ -25,7 +25,7 @@ def __init__(self, **kwargs): self.register_event_type('on_enter') self.register_event_type('on_leave') Window.bind(mouse_pos=self.on_mouse_pos) - super(HoverBehavior, self).__init__(**kwargs) + super().__init__(**kwargs) def on_mouse_pos(self, *args): if not self.get_root_window(): diff --git a/src/gui/utils/layouts.py b/src/gui/utils/layouts.py index 92390db0..e9a2337c 100644 --- a/src/gui/utils/layouts.py +++ b/src/gui/utils/layouts.py @@ -152,7 +152,7 @@ def add_widget(self, widget, *args, **kwargs): elif isinstance(widget, DynamicTabHeader): self_tabs = self._tab_strip self_tabs.add_widget(widget, *args, **kwargs) - widget.group = '__tab%r__' % self_tabs.uid + widget.group = f'__tab{self_tabs.uid!r}__' self.on_tab_width() else: widget.pos_hint = {'x': 0, 'top': 1} @@ -207,7 +207,7 @@ def _setup_default_tab(self): default_tab.text = self.default_tab_text default_tab.height = self.tab_height - default_tab.group = '__tab%r__' % _tabs.uid + default_tab.group = f'__tab{_tabs.uid!r}__' default_tab.state = 'down' default_tab.width = self.tab_width if self.tab_width else 100 default_tab.content = content diff --git a/src/helpers/actions.py b/src/helpers/actions.py index 4cadc80a..3ce63137 100644 --- a/src/helpers/actions.py +++ b/src/helpers/actions.py @@ -35,4 +35,4 @@ def run_action(action_set: str, action: str) -> None: ref7.putName(sID("action"), action) ref7.putName(sID("actionSet"), action_set) desc310.putReference(sID("target"), ref7) - APP.ExecuteAction(sID("play"), desc310, NO_DIALOG) + APP.executeAction(sID("play"), desc310, NO_DIALOG) diff --git a/src/helpers/adjustments.py b/src/helpers/adjustments.py index fa14144f..b9d997e8 100644 --- a/src/helpers/adjustments.py +++ b/src/helpers/adjustments.py @@ -2,10 +2,10 @@ * Helpers: Adjustment Layers """ # Standard Library -from typing import Union, Optional +from typing import NotRequired, TypedDict, Unpack # Third Party -from photoshop.api import DialogModes, ActionList, ActionDescriptor, ActionReference, SolidColor +from photoshop.api import BlendMode, DialogModes, ActionList, ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -13,6 +13,7 @@ # Local Imports from src import APP from src.helpers.colors import get_color, apply_color, add_color_to_gradient, rgb_black +from src.schema.colors import ColorObject, GradientConfig # QOL Definitions sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID @@ -37,11 +38,16 @@ def create_vibrant_saturation(vibrancy: int, saturation: int) -> None: APP.executeAction(sID("vibrance"), desc232, NO_DIALOG) +class CreateColorLayerKwargs(TypedDict): + clipped: NotRequired[bool] + blend_mode: NotRequired[BlendMode] + + def create_color_layer( - color: Union[list[int], SolidColor, str], - layer: Union[ArtLayer, LayerSet, None], - docref: Optional[Document] = None, - **kwargs + color: ColorObject, + layer: ArtLayer | LayerSet | None, + docref: Document | None = None, + **kwargs: Unpack[CreateColorLayerKwargs] ) -> ArtLayer: """Create a solid color adjustment layer. @@ -73,16 +79,27 @@ def create_color_layer( desc1.putObject(sID("using"), sID("contentLayer"), desc2) APP.executeAction(sID("make"), desc1, NO_DIALOG) layer = docref.activeLayer + if not isinstance(layer, ArtLayer): + raise ValueError( + "Failed to create a color layer. Active layer is unexpectedly not an ArtLayer." + ) if 'blend_mode' in kwargs: layer.blendMode = kwargs['blend_mode'] return layer +class CreateGradientLayerKwargs(TypedDict): + blend_mode: NotRequired[BlendMode] + clipped: NotRequired[bool] + dither: NotRequired[bool] + rotation: NotRequired[float] + scale: NotRequired[float] + def create_gradient_layer( - colors: list[dict], - layer: Union[ArtLayer, LayerSet, None], - docref: Optional[Document] = None, - **kwargs + colors: list[GradientConfig], + layer: ArtLayer | LayerSet | None, + docref: Document | None = None, + **kwargs: Unpack[CreateGradientLayerKwargs] ) -> ArtLayer: """Create a gradient adjustment layer. @@ -92,10 +109,11 @@ def create_gradient_layer( docref: Reference Document, use active if not provided. Keyword Args: + blend_mode (BlendMode): Optional blend mode to apply to the new layer. clipped (bool): Whether to apply as a clipping mask to the nearest layer, defaults to True. + dither (bool): Whether to enable dithering for the gradient. rotation (Union[int, float]): Rotation to apply to the gradient, defaults to 90. scale (Union[int, float]): Scale to apply to the gradient, defaults to 100. - blend_mode (BlendMode): Optional blend mode to apply to the new layer. Returns: The new gradient adjustment layer. @@ -118,7 +136,9 @@ def create_gradient_layer( desc3.putEnumerated( sID("gradientsInterpolationMethod"), sID("gradientInterpolationMethodType"), - sID("perceptual")) + sID("perceptual") + ) + desc3.putBoolean(sID("dither"), kwargs.get("dither", True)) desc3.putUnitDouble(sID("angle"), sID("angleUnit"), kwargs.get('rotation', 0)) desc3.putEnumerated(sID("type"), sID("gradientType"), sID("linear")) desc3.putUnitDouble(sID("scale"), sID("percentUnit"), kwargs.get('scale', 100)) @@ -146,6 +166,8 @@ def create_gradient_layer( desc1.putObject(sID("using"), sID("contentLayer"), desc2) APP.executeAction(sID("make"), desc1, NO_DIALOG) layer = docref.activeLayer + if not isinstance(layer, ArtLayer): + raise ValueError("Failed to create a gradient color layer") if 'blend_mode' in kwargs: layer.blendMode = kwargs['blend_mode'] return layer diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index b053932a..2f783961 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -3,7 +3,7 @@ """ # Standard Library Imports from contextlib import suppress -from typing import Union, TypedDict +from typing import TypedDict # Third Party Imports from photoshop.api import DialogModes @@ -12,10 +12,10 @@ # Local Imports from src import APP -from src.helpers.descriptors import get_layer_action_ref +from src.helpers.descriptors import get_layer_action_descriptor from src.helpers.document import undo_action -from src.helpers.layers import duplicate_group, select_layer -from src.utils.adobe import PS_EXCEPTIONS +from src.helpers.layers import duplicate_group +from src.utils.adobe import PS_EXCEPTIONS, LayerBounds # QOL Definitions sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID @@ -25,20 +25,17 @@ * Types """ -LayerBounds = tuple[int, int, int, int] -"""left, top, right, bottom""" - class LayerDimensions(TypedDict): """Calculated layer dimension info for a layer.""" - width: int - height: int - center_x: int - center_y: int - left: int - right: int - top: int - bottom: int + width: int | float + height: int | float + center_x: int | float + center_y: int | float + left: int | float + right: int | float + top: int | float + bottom: int | float class TextboxDimensions(TypedDict): @@ -72,7 +69,7 @@ def get_dimensions_from_bounds(bounds: LayerBounds) -> LayerDimensions: top=int(bounds[1]), bottom=int(bounds[3])) -def get_layer_dimensions(layer: Union[ArtLayer, LayerSet]) -> LayerDimensions: +def get_layer_dimensions(layer: ArtLayer | LayerSet) -> LayerDimensions: """Compute the width and height dimensions of a layer. Args: @@ -91,8 +88,7 @@ def get_group_dimensions(group: LayerSet) -> LayerDimensions: Uses a workaround to avoid erroneous dimensions, which might occur when the group contains shapes. """ - select_layer(group) - group_copy = duplicate_group(group.name) + group_copy = duplicate_group(group, group.name) group_copy.visible = True merged = group_copy.merge() dims = get_layer_dimensions(merged) @@ -100,7 +96,7 @@ def get_group_dimensions(group: LayerSet) -> LayerDimensions: return dims -def get_layer_width(layer: Union[ArtLayer, LayerSet]) -> Union[float, int]: +def get_layer_width(layer: ArtLayer | LayerSet) -> float | int: """Returns the width of a given layer. Args: @@ -113,7 +109,7 @@ def get_layer_width(layer: Union[ArtLayer, LayerSet]) -> Union[float, int]: return int(bounds[2]-bounds[0]) -def get_layer_height(layer: Union[ArtLayer, LayerSet]) -> Union[float, int]: +def get_layer_height(layer: ArtLayer | LayerSet) -> float | int: """Returns the height of a given layer. Args: @@ -131,7 +127,7 @@ def get_layer_height(layer: Union[ArtLayer, LayerSet]) -> Union[float, int]: """ -def get_bounds_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerBounds: +def get_bounds_no_effects(layer: ArtLayer | LayerSet) -> LayerBounds: """Returns the bounds of a given layer without its effects applied. Args: @@ -141,7 +137,7 @@ def get_bounds_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerBounds: list: Pixel location top left, top right, bottom left, bottom right. """ with suppress(Exception): - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) try: # Try getting bounds no effects bounds = d.getObjectValue(sID('boundsNoEffects')) @@ -157,7 +153,7 @@ def get_bounds_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerBounds: return layer.bounds -def get_dimensions_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerDimensions: +def get_dimensions_no_effects(layer: ArtLayer | LayerSet) -> LayerDimensions: """Compute the dimensions of a layer without its effects applied. Args: @@ -170,7 +166,7 @@ def get_dimensions_no_effects(layer: Union[ArtLayer, LayerSet]) -> LayerDimensio return get_dimensions_from_bounds(bounds) -def get_width_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: +def get_width_no_effects(layer: ArtLayer | LayerSet) -> float | int: """Returns the width of a given layer without its effects applied. Args: @@ -181,13 +177,13 @@ def get_width_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: """ with suppress(Exception): # Try getting bounds no effects - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundsNoEffects')) return bounds.getInteger(sID('right')) - bounds.getInteger(sID('left')) return get_layer_width(layer) -def get_height_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: +def get_height_no_effects(layer: ArtLayer | LayerSet) -> float | int: """Returns the height of a given layer without its effects applied. Args: @@ -198,7 +194,7 @@ def get_height_no_effects(layer: Union[ArtLayer, LayerSet]) -> float | int: """ with suppress(Exception): # Try getting bounds no effects - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundsNoEffects')) return bounds.getInteger(sID('bottom')) - bounds.getInteger(sID('top')) return get_layer_height(layer) @@ -237,7 +233,7 @@ def get_textbox_bounds(layer: ArtLayer) -> LayerBounds: Returns: List of bounds integers. """ - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundingBox')) return ( bounds.getInteger(sID('left')), @@ -256,7 +252,7 @@ def get_textbox_dimensions(layer: ArtLayer) -> TextboxDimensions: Returns: Dict containing width and height. """ - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundingBox')) return { 'width': bounds.getInteger(sID('width')), @@ -273,7 +269,7 @@ def get_textbox_width(layer: ArtLayer) -> int: Returns: Width of the textbox. """ - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundingBox')) return bounds.getInteger(sID('width')) @@ -287,6 +283,6 @@ def get_textbox_height(layer: ArtLayer) -> int: Returns: Height of the textbox. """ - d = get_layer_action_ref(layer) + d = get_layer_action_descriptor(layer) bounds = d.getObjectValue(sID('boundingBox')) return bounds.getInteger(sID('height')) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index f3564f1d..dd9ab92f 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -1,17 +1,26 @@ """ * Helpers: Colors """ + # Standard Library Imports -from typing import TypedDict, Union +from collections.abc import Mapping # Third Party Imports -from photoshop.api import SolidColor, DialogModes, ActionList, ActionDescriptor, ColorModel, LayerKind -from photoshop.api._artlayer import ArtLayer, TextItem +from photoshop.api import ( + SolidColor, + DialogModes, + ActionList, + ActionDescriptor, + ColorModel, + LayerKind, +) +from photoshop.api._artlayer import ArtLayer # Local Imports from src import APP, CON from src.enums.layers import LAYERS from src.schema.colors import pinlines_color_map, ColorObject +from src.schema.colors import GradientConfig # QOL Definitions sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID @@ -31,9 +40,9 @@ def hex_to_rgb(color: str) -> list[int]: Returns: Color in RGB list notation. """ - if '#' in color: + if "#" in color: color = color[1:] - return [int(color[i:i+2], 16) for i in (0, 2, 4)] + return [int(color[i : i + 2], 16) for i in (0, 2, 4)] """ @@ -68,7 +77,7 @@ def rgb_white() -> SolidColor: return get_rgb(255, 255, 255) -def get_rgb(r: int, g: int, b: int) -> SolidColor: +def get_rgb(r: float, g: float, b: float) -> SolidColor: """Creates a SolidColor object with the given RGB values. Args: @@ -96,7 +105,7 @@ def get_rgb_from_hex(hex_code: str) -> SolidColor: SolidColor object. """ # Remove hashtag - hex_code = hex_code.lstrip('#') + hex_code = hex_code.lstrip("#") # Hexadecimal abbreviated if len(hex_code) == 3: hex_code = "".join([n * 2 for n in hex_code]) @@ -140,7 +149,7 @@ def get_color(color: ColorObject) -> SolidColor: if isinstance(color, SolidColor): # Solid color given return color - if isinstance(color, list): + elif isinstance(color, tuple): # List notation if len(color) == 3: # RGB @@ -148,7 +157,7 @@ def get_color(color: ColorObject) -> SolidColor: elif len(color) == 4: # CMYK return get_cmyk(*color) - if isinstance(color, str): + else: # Named color if color in CON.colors: return get_color(CON.colors[color]) @@ -169,33 +178,16 @@ def get_text_layer_color(layer: ArtLayer) -> SolidColor: Returns: SolidColor object representing the color of the text item. """ - if isinstance(layer, ArtLayer) and layer.kind == LayerKind.TextLayer: - if hasattr(layer.textItem, 'color'): - return layer.textItem.color - print(f"Couldn't retrieve color of layer: {layer.name}") - return rgb_black() - - -def get_text_item_color(item: TextItem) -> SolidColor: - """Utility definition for `get_text_layer_color`, targeting a known TextItem.""" - if isinstance(item, TextItem): - if hasattr(item, 'color'): - return item.color - print(f"Couldn't retrieve color of TextItem!") + if layer.kind == LayerKind.TextLayer: + return layer.textItem.color return rgb_black() -class GradientConfig(TypedDict): - color: ColorObject - location: int | float - midpoint: int | float - - def get_pinline_gradient( - colors: str, - color_map: dict[str, ColorObject] | None = None, - location_map: dict[int, list[Union[int, float]]] | None = None -) -> ColorObject | list[ColorObject] | list[GradientConfig]: + colors: str, + color_map: Mapping[str, ColorObject] | None = None, + location_map: dict[int, list[int | float]] | None = None, +) -> ColorObject | list[GradientConfig]: """Return a gradient color list notation for some given pinline colors. Args: @@ -216,30 +208,31 @@ def get_pinline_gradient( # Return our colors if not colors: - return color_map.get(LAYERS.ARTIFACT, [0, 0, 0]) + return color_map.get(LAYERS.ARTIFACT, (0, 0, 0)) if colors in color_map: return color_map[colors] if (len_colors := len(colors)) > 1: - return [ - conf - for i in range(len_colors - 1) - for conf in [ + color_confs: list[GradientConfig] = [] + for i in range(len_colors - 1): + color_confs.append( { - "color": color_map.get(colors[i], [0, 0, 0]), + "color": color_map.get(colors[i], (0, 0, 0)), "location": location_map[len_colors][2 * i] * 4096, "midpoint": 50, - }, + } + ) + color_confs.append( { - "color": color_map.get(colors[i + 1], [0, 0, 0]), + "color": color_map.get(colors[i + 1], (0, 0, 0)), "location": location_map[len_colors][2 * i + 1] * 4096, "midpoint": 50, - }, - ] - ] + } + ) + return color_confs - return [0, 0, 0] + return (0, 0, 0) """ @@ -247,7 +240,11 @@ def get_pinline_gradient( """ -def apply_rgb_from_list(action: ActionDescriptor, color: list[int], color_type: str = 'color') -> None: +def apply_rgb_from_list( + action: ActionDescriptor, + color: tuple[float, float, float], + color_type: str = "color", +) -> None: """Applies RGB color to action descriptor from a list of values. Args: @@ -262,7 +259,11 @@ def apply_rgb_from_list(action: ActionDescriptor, color: list[int], color_type: action.putObject(sID(color_type), sID("RGBColor"), ad) -def apply_cmyk_from_list(action: ActionDescriptor, color: list[int], color_type: str = 'color') -> None: +def apply_cmyk_from_list( + action: ActionDescriptor, + color: tuple[float, float, float, float], + color_type: str = "color", +) -> None: """Applies CMYK color to action descriptor from a list of values. Args: @@ -278,7 +279,9 @@ def apply_cmyk_from_list(action: ActionDescriptor, color: list[int], color_type: action.putObject(sID(color_type), sID("CMYKColorClass"), ad) -def apply_rgb(action: ActionDescriptor, c: SolidColor, color_type: str = 'color') -> None: +def apply_rgb( + action: ActionDescriptor, c: SolidColor, color_type: str = "color" +) -> None: """Apply RGB SolidColor object to action descriptor. Args: @@ -286,10 +289,12 @@ def apply_rgb(action: ActionDescriptor, c: SolidColor, color_type: str = 'color' c: SolidColor object matching RGB model. color_type: Color action descriptor type, defaults to 'color'. """ - apply_rgb_from_list(action, [c.rgb.red, c.rgb.green, c.rgb.blue], color_type) + apply_rgb_from_list(action, (c.rgb.red, c.rgb.green, c.rgb.blue), color_type) -def apply_cmyk(action: ActionDescriptor, c: SolidColor, color_type: str = 'color') -> None: +def apply_cmyk( + action: ActionDescriptor, c: SolidColor, color_type: str = "color" +) -> None: """Apply CMYK SolidColor object to action descriptor. Args: @@ -297,13 +302,13 @@ def apply_cmyk(action: ActionDescriptor, c: SolidColor, color_type: str = 'color c: SolidColor object matching CMYK model. color_type: Color action descriptor type, defaults to 'color'. """ - apply_cmyk_from_list(action, [c.cmyk.cyan, c.cmyk.magenta, c.cmyk.yellow, c.cmyk.black], color_type) + apply_cmyk_from_list( + action, (c.cmyk.cyan, c.cmyk.magenta, c.cmyk.yellow, c.cmyk.black), color_type + ) def apply_color( - action: ActionDescriptor, - color: ColorObject, - color_type: str = 'color' + action: ActionDescriptor, color: ColorObject, color_type: str = "color" ) -> None: """Applies color to the specified action descriptor. @@ -312,7 +317,7 @@ def apply_color( color: RGB/CMYK SolidColor object, list of RGB/CMYK values, a hex string, or a named color. color_type: Color action descriptor type, defaults to 'color'. """ - if isinstance(color, list): + if isinstance(color, tuple): # RGB / CMYK list notation if len(color) < 4: return apply_rgb_from_list(action, color, color_type) @@ -331,10 +336,7 @@ def apply_color( def add_color_to_gradient( - action_list: ActionList, - color: ColorObject, - location: int, - midpoint: int + action_list: ActionList, color: ColorObject, location: int, midpoint: int ) -> None: """Adds a SolidColor to gradient at a given location, with a given midpoint. diff --git a/src/helpers/descriptors.py b/src/helpers/descriptors.py index ce882f8b..13cd234f 100644 --- a/src/helpers/descriptors.py +++ b/src/helpers/descriptors.py @@ -2,10 +2,9 @@ * Helpers: PS Object Descriptors """ # Standard Library Imports -from typing import Union # Third Party Imports -from photoshop.api import DialogModes, ActionReference +from photoshop.api import ActionDescriptor, DialogModes, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -21,7 +20,7 @@ """ -def get_layer_action_ref(layer: Union[ArtLayer, LayerSet]) -> ActionReference: +def get_layer_action_descriptor(layer: ArtLayer | LayerSet) -> ActionDescriptor: """Gets action descriptor info object using layer as a reference. Args: diff --git a/src/helpers/design.py b/src/helpers/design.py index f653e64a..84ee4b14 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -1,8 +1,8 @@ """ * Helpers: Design """ + # Standard Library Imports -from typing import Optional from contextlib import suppress # Third Party Imports @@ -14,7 +14,7 @@ BlendMode, SolidColor, ElementPlacement, - SaveOptions + SaveOptions, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document @@ -35,7 +35,9 @@ """ -def fill_empty_area(reference: ArtLayer, color: Optional[SolidColor] = None) -> ArtLayer: +def fill_empty_area( + reference: ArtLayer, color: SolidColor | None = None +) -> ArtLayer: """Fills empty gaps on an art layer, such as a symbol, with a solid color. Args: @@ -48,17 +50,17 @@ def fill_empty_area(reference: ArtLayer, color: Optional[SolidColor] = None) -> coords = ActionDescriptor() click1 = ActionDescriptor() ref1 = ActionReference() - idPaint = sID('paint') - idPixel = sID('pixelsUnit') - idTolerance = sID('tolerance') - coords.putUnitDouble(sID('horizontal'), idPixel, 5) - coords.putUnitDouble(sID('vertical'), idPixel, 5) - ref1.putProperty(sID('channel'), sID('selection')) - click1.putReference(sID('target'), ref1) - click1.putObject(sID('to'), idPaint, coords) + idPaint = sID("paint") + idPixel = sID("pixelsUnit") + idTolerance = sID("tolerance") + coords.putUnitDouble(sID("horizontal"), idPixel, 5) + coords.putUnitDouble(sID("vertical"), idPixel, 5) + ref1.putProperty(sID("channel"), sID("selection")) + click1.putReference(sID("target"), ref1) + click1.putObject(sID("to"), idPaint, coords) click1.putInteger(idTolerance, 12) - click1.putBoolean(sID('antiAlias'), True) - APP.executeAction(sID('set'), click1) + click1.putBoolean(sID("antiAlias"), True) + APP.executeAction(sID("set"), click1) # Invert selection docsel.invert() @@ -66,25 +68,27 @@ def fill_empty_area(reference: ArtLayer, color: Optional[SolidColor] = None) -> # Make a new layer layer = docref.artLayers.add() - layer.name = 'Expansion Mask' + layer.name = "Expansion Mask" layer.blendMode = BlendMode.NormalBlend - layer.moveAfter(reference) + layer.move(reference, ElementPlacement.PlaceAfter) # Fill selection with stroke color APP.foregroundColor = color or rgb_black() click3 = ActionDescriptor() - click3.putObject(sID('from'), idPaint, coords) + click3.putObject(sID("from"), idPaint, coords) click3.putInteger(idTolerance, 0) - click3.putEnumerated(sID('using'), sID('fillContents'), sID('foregroundColor')) - click3.putBoolean(sID('contiguous'), False) - APP.executeAction(sID('fill'), click3) + click3.putEnumerated(sID("using"), sID("fillContents"), sID("foregroundColor")) + click3.putBoolean(sID("contiguous"), False) + APP.executeAction(sID("fill"), click3) # Clear Selection docsel.deselect() return layer -def content_aware_fill_edges(layer: Optional[ArtLayer] = None, feather: bool = False) -> None: +def content_aware_fill_edges( + layer: ArtLayer | None = None, feather: bool = False +) -> None: """Fills pixels outside art layer using content-aware fill. Args: @@ -95,10 +99,14 @@ def content_aware_fill_edges(layer: Optional[ArtLayer] = None, feather: bool = F docref = APP.activeDocument if layer: docref.activeLayer = layer - docref.activeLayer.rasterize(RasterizeType.EntireLayer) + active_layer = layer + elif not isinstance((active_layer := docref.activeLayer), ArtLayer): + print("Skipping content aware fill as active layer is not an ArtLayer.") + return + active_layer.rasterize(RasterizeType.EntireLayer) # Select pixels of the active layer - select_layer_pixels(docref.activeLayer) + select_layer_pixels(active_layer) selection = docref.selection # Guard against no selection made @@ -108,15 +116,16 @@ def content_aware_fill_edges(layer: Optional[ArtLayer] = None, feather: bool = F selection.contract(22) selection.feather(8) else: - selection.contract(10) - selection.smooth(5) + selection.contract(6) + # selection.smooth(4) + selection.feather(2) + # selection.contract(10) + # selection.feather(3) selection.invert() content_aware_fill() except PS_EXCEPTIONS: # Unable to fill due to invalid selection - CONSOLE.update( - "Couldn't make a valid selection!\n" - "Skipping automated fill.") + CONSOLE.update("Couldn't make a valid selection!\nSkipping automated fill.") # Clear selection with suppress(Exception): @@ -124,11 +133,11 @@ def content_aware_fill_edges(layer: Optional[ArtLayer] = None, feather: bool = F def generative_fill_edges( - layer: Optional[ArtLayer] = None, + layer: ArtLayer | None = None, feather: bool = False, close_doc: bool = True, - docref: Optional[Document] = None -) -> Optional[Document]: + docref: Document | None = None, +) -> Document | None: """Fills pixels outside an art layer using AI powered generative fill. Args: @@ -142,24 +151,28 @@ def generative_fill_edges( """ # Set docref and use active layer if not provided docref = docref or APP.activeDocument - if not layer: - layer = docref.activeLayer - docref.activeLayer = layer + if layer: + docref.activeLayer = layer + active_layer = layer + elif not isinstance((active_layer := docref.activeLayer), ArtLayer): + print("Skipping content aware fill as active layer is not an ArtLayer.") + return + active_layer.rasterize(RasterizeType.EntireLayer) # Create a fill layer the size of the document fill_layer: ArtLayer = docref.artLayers.add() - fill_layer.move(layer, ElementPlacement.PlaceAfter) + fill_layer.move(active_layer, ElementPlacement.PlaceAfter) fill_layer_primary() fill_layer.opacity = 0 # Create a smart layer document and enter it - select_layers([layer, fill_layer]) + select_layers([active_layer, fill_layer]) smart_layer() edit_smart_layer() # Select pixels of active layer and invert docref = APP.activeDocument - select_layer_pixels(docref.activeLayer) + select_layer_pixels(active_layer) selection = docref.selection # Guard against no selection made @@ -176,15 +189,15 @@ def generative_fill_edges( generative_fill() except PS_EXCEPTIONS: # Generative fill call not responding - CONSOLE.update("Generative fill failed!\n" - "Falling back to Content Aware Fill.") - docref.activeLayer.rasterize(RasterizeType.EntireLayer) + CONSOLE.update( + "Generative fill failed!\nFalling back to Content Aware Fill." + ) + active_layer.rasterize(RasterizeType.EntireLayer) content_aware_fill() close_doc = True except PS_EXCEPTIONS: # Unable to fill due to invalid selection - CONSOLE.update("Couldn't make a valid selection!\n" - "Skipping automated fill.") + CONSOLE.update("Couldn't make a valid selection!\nSkipping automated fill.") close_doc = True # Deselect @@ -263,25 +276,21 @@ def repair_edges(edge: int = 6) -> None: desc_caf.putEnumerated( sID("cafSamplingRegion"), sID("cafSamplingRegion"), - sID("cafSamplingRegionRectangular") + sID("cafSamplingRegionRectangular"), ) desc_caf.putBoolean(sID("cafSampleAllLayers"), False) desc_caf.putEnumerated( sID("cafColorAdaptationLevel"), sID("cafColorAdaptationLevel"), - sID("cafColorAdaptationDefault") + sID("cafColorAdaptationDefault"), ) desc_caf.putEnumerated( - sID("cafRotationAmount"), - sID("cafRotationAmount"), - sID("cafRotationAmountNone") + sID("cafRotationAmount"), sID("cafRotationAmount"), sID("cafRotationAmountNone") ) desc_caf.putBoolean(sID("cafScale"), False) desc_caf.putBoolean(sID("cafMirror"), False) desc_caf.putEnumerated( - sID("cafOutput"), - sID("cafOutput"), - sID("cafOutputToNewLayer") + sID("cafOutput"), sID("cafOutput"), sID("cafOutputToNewLayer") ) APP.executeAction(sID("cafWorkspace"), desc_caf, NO_DIALOG) diff --git a/src/helpers/document.py b/src/helpers/document.py index 13dcf707..b02aeea7 100644 --- a/src/helpers/document.py +++ b/src/helpers/document.py @@ -3,7 +3,8 @@ """ # Standard Library Imports from pathlib import Path -from typing import Optional, Union +from typing import Any +from collections.abc import Callable # Third Party Imports from photoshop.api import ( @@ -36,7 +37,7 @@ """ -def get_leaf_layers(group: Optional[LayerSet] = None) -> list[ArtLayer]: +def get_leaf_layers(group: Document | LayerSet | None = None) -> list[ArtLayer]: """Utility function to generate a list of leaf layers in a LayerSet or document. Args: @@ -52,8 +53,9 @@ def get_leaf_layers(group: Optional[LayerSet] = None) -> list[ArtLayer]: layers.extend(get_leaf_layers(g)) return layers +ArtLayerTree = dict[str, "ArtLayer | ArtLayerTree"] -def get_layer_tree(group: Optional[LayerSet] = None) -> dict[str, Union[ArtLayer, dict[str, ArtLayer]]]: +def get_layer_tree(group: Document | LayerSet | None = None) -> ArtLayerTree: """Composes a dictionary tree of layers in the active document or a specific LayerSet. Args: @@ -64,7 +66,7 @@ def get_layer_tree(group: Optional[LayerSet] = None) -> dict[str, Union[ArtLayer """ if not group: group = APP.activeDocument - layers = {layer.name: layer for layer in group.artLayers} + layers: ArtLayerTree = {layer.name: layer for layer in group.artLayers} for g in group.layerSets: layers[g.name] = get_layer_tree(g) return layers @@ -77,9 +79,9 @@ def get_layer_tree(group: Optional[LayerSet] = None) -> dict[str, Union[ArtLayer def import_art( layer: ArtLayer, - path: Union[str, Path], + path: str | Path, name: str = 'Layer 1', - docref: Optional[Document] = None + docref: Document | None = None ) -> ArtLayer: """Imports an art file into the active layer. @@ -97,15 +99,20 @@ def import_art( docref.activeLayer = layer desc.putPath(sID('target'), str(path)) APP.executeAction(sID('placeEvent'), desc) - docref.activeLayer.name = name - return docref.activeLayer + + active_layer = docref.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError("Failed to import art. Active layer is unexpectedly not an ArtLayer.") + active_layer.name = name + + return active_layer def import_svg( - path: Union[str, Path], - ref: Union[ArtLayer, LayerSet] = None, - placement: Optional[ElementPlacement] = None, - docref: Optional[Document] = None + path: str | Path, + ref: ArtLayer | LayerSet | None = None, + placement: ElementPlacement | None = None, + docref: Document | None = None ) -> ArtLayer: """Imports an SVG image, then moves it if needed. @@ -124,18 +131,21 @@ def import_svg( desc.putPath(sID('target'), str(path)) APP.executeAction(sID('placeEvent'), desc) + active_layer = docref.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError("Failed to import SVG. Active layer is unexpectedly a LayerSet.") + # Position the layer if needed if ref and placement: - docref.activeLayer.move(ref, placement) - return docref.activeLayer + active_layer.move(ref, placement) + return active_layer def paste_file( layer: ArtLayer, - path: Union[str, Path], - action: any = None, - action_args: dict = None, - docref: Optional[Document] = None + path: str | Path, + action: Callable[[], Any] | None = None, + docref: Document | None = None ) -> ArtLayer: """Pastes the given file into the specified layer. @@ -156,7 +166,7 @@ def paste_file( # Optionally run action on art before importing it if action: - action(**action_args) if action_args else action() + action() # Select the entire image, copy it, and close the file newdoc = APP.activeDocument @@ -168,13 +178,18 @@ def paste_file( # Paste the image into the specific layer docref.paste() - return docref.activeLayer + + active_layer = docref.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError("Failed to paste file. Active layer is unexpectedly not an ArtLayer.") + + return active_layer def import_art_into_new_layer( - path: Union[str, Path], + path: str | Path, name: str = "New Layer", - docref: Optional[Document] = None + docref: Document | None = None ) -> ArtLayer: """Creates a new layer and imports a given art into that layer. @@ -207,8 +222,8 @@ def jump_to_history_state(position: int): """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.PutOffset(sID('historyState'), position) - desc1.PutReference(sID('target'), ref1) + ref1.putOffset(sID('historyState'), position) + desc1.putReference(sID('target'), ref1) APP.executeAction(sID('select'), desc1, NO_DIALOG) @@ -220,8 +235,8 @@ def toggle_history_state(direction: str = 'previous') -> None: """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.PutEnumerated(sID('historyState'), sID('ordinal'), sID(direction)) - desc1.PutReference(sID('target'), ref1) + ref1.putEnumerated(sID('historyState'), sID('ordinal'), sID(direction)) + desc1.putReference(sID('target'), ref1) APP.executeAction(sID('select'), desc1, NO_DIALOG) @@ -235,7 +250,7 @@ def redo_action() -> None: toggle_history_state('next') -def reset_document(docref: Optional[Document] = None) -> None: +def reset_document(docref: Document | None = None) -> None: """Reset to the history state to when document was first opened. Args: @@ -253,7 +268,7 @@ def reset_document(docref: Optional[Document] = None) -> None: """ -def points_to_pixels(number: Union[int, float], docref: Optional[Document] = None) -> float: +def points_to_pixels(number: int | float, docref: Document | None = None) -> float: """Converts a given number in point units to pixel units. Args: @@ -267,7 +282,7 @@ def points_to_pixels(number: Union[int, float], docref: Optional[Document] = Non return (docref.resolution / 72) * number -def pixels_to_points(number: Union[int, float], docref: Optional[Document] = None) -> float: +def pixels_to_points(number: int | float, docref: Document | None = None) -> float: """Converts a given number in pixel units to point units. Args: @@ -300,7 +315,7 @@ def check_active_document() -> bool: return False -def get_document(name: str) -> Optional[Document]: +def get_document(name: str) -> Document | None: """Check if a Photoshop Document has been loaded. Args: @@ -310,10 +325,7 @@ def get_document(name: str) -> Optional[Document]: The Document if located, None if missing. """ try: - docs = APP.documents - if docs.length < 1: - return - doc = docs.getByName(name) + doc = APP.documents[name] APP.activeDocument = doc return doc except PS_EXCEPTIONS: @@ -341,7 +353,7 @@ def trim_transparent_pixels() -> None: """ -def save_document_png(path: Path, docref: Optional[Document] = None) -> None: +def save_document_png(path: Path, docref: Document | None = None) -> None: """Save the current document as a PNG. Args: @@ -358,7 +370,7 @@ def save_document_png(path: Path, docref: Optional[Document] = None) -> None: asCopy=True) -def save_document_jpeg(path: Path, optimize: bool = True, docref: Optional[Document] = None) -> None: +def save_document_jpeg(path: Path, docref: Document | None = None, optimize: bool = True) -> None: """Save the current document as a JPEG. Args: @@ -393,7 +405,7 @@ def save_document_jpeg(path: Path, optimize: bool = True, docref: Optional[Docum raise OSError from e -def save_document_psd(path: Path, docref: Optional[Document] = None) -> None: +def save_document_psd(path: Path, docref: Document | None = None) -> None: """Save the current document as a PSD. Args: @@ -407,7 +419,7 @@ def save_document_psd(path: Path, docref: Optional[Document] = None) -> None: asCopy=True) -def save_document_psb(path: Path, *_args, **_kwargs) -> None: +def save_document_psb(path: Path) -> None: """Save the current document as a PSB. Args: @@ -424,7 +436,7 @@ def save_document_psb(path: Path, *_args, **_kwargs) -> None: APP.executeAction(sID('save'), d1, NO_DIALOG) -def close_document(save: bool = False, docref: Optional[Document] = None, purge: bool = True) -> None: +def close_document(save: bool = False, docref: Document | None = None, purge: bool = True) -> None: """Close the active document. Args: @@ -452,10 +464,10 @@ def rotate_document(angle: int) -> None: """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.PutEnumerated(sID('document'), sID('ordinal'), sID('first')) - desc1.PutReference(sID('target'), ref1) - desc1.PutUnitDouble(sID('angle'), sID('angleUnit'), angle) - APP.executeaction(sID('rotateEventEnum'), desc1, NO_DIALOG) + ref1.putEnumerated(sID('document'), sID('ordinal'), sID('first')) + desc1.putReference(sID('target'), ref1) + desc1.putUnitDouble(sID('angle'), sID('angleUnit'), angle) + APP.executeAction(sID('rotateEventEnum'), desc1, NO_DIALOG) def rotate_counter_clockwise() -> None: @@ -478,7 +490,7 @@ def rotate_full() -> None: """ -def paste_to_document(layer: Union[ArtLayer, LayerSet, None] = None): +def paste_to_document(layer: ArtLayer | LayerSet | None = None): """Paste current clipboard to the current layer. Args: @@ -487,6 +499,6 @@ def paste_to_document(layer: Union[ArtLayer, LayerSet, None] = None): if layer: APP.activeDocument.activeLayer = layer desc1 = ActionDescriptor() - desc1.PutEnumerated(sID("antiAlias"), sID("antiAliasType"), sID("antiAliasNone")) - desc1.PutClass(sID("as"), sID("pixel")) - APP.Executeaction(sID("paste"), desc1, NO_DIALOG) + desc1.putEnumerated(sID("antiAlias"), sID("antiAliasType"), sID("antiAliasNone")) + desc1.putClass(sID("as"), sID("pixel")) + APP.executeAction(sID("paste"), desc1, NO_DIALOG) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index b55555ae..7aef4c6d 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -2,7 +2,6 @@ * Helpers: Layer Effects """ # Standard Library Imports -from typing import Union, Optional # Third Party Imports from photoshop.api import DialogModes, ActionDescriptor, ActionReference, ActionList @@ -25,7 +24,7 @@ """ -def set_fill_opacity(opacity: float, layer: Optional[Union[ArtLayer, LayerSet]]) -> None: +def set_fill_opacity(opacity: float, layer: ArtLayer | LayerSet | None) -> None: """Sets the fill opacity of a given layer. Args: @@ -40,10 +39,10 @@ def set_fill_opacity(opacity: float, layer: Optional[Union[ArtLayer, LayerSet]]) d = ActionDescriptor() ref = ActionReference() d1 = ActionDescriptor() - ref.PutEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - d.PutReference(sID("target"), ref) - d1.PutUnitDouble(sID("fillOpacity"), sID("percentUnit"), opacity) - d.PutObject(sID("to"), sID("layer"), d1) + ref.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) + d.putReference(sID("target"), ref) + d1.putUnitDouble(sID("fillOpacity"), sID("percentUnit"), opacity) + d.putObject(sID("to"), sID("layer"), d1) APP.executeAction(sID("set"), d, NO_DIALOG) @@ -52,7 +51,7 @@ def set_fill_opacity(opacity: float, layer: Optional[Union[ArtLayer, LayerSet]]) """ -def set_layer_fx_visibility(layer: Optional[Union[ArtLayer, LayerSet]] = None, visible: bool = True) -> None: +def set_layer_fx_visibility(layer: ArtLayer | LayerSet | None = None, visible: bool = True) -> None: """Shows or hides the layer effects on a given layer. Args: @@ -74,7 +73,7 @@ def set_layer_fx_visibility(layer: Optional[Union[ArtLayer, LayerSet]] = None, v APP.executeAction(sID("show" if visible else "hide"), desc, NO_DIALOG) -def enable_layer_fx(layer: Optional[Union[ArtLayer, LayerSet]] = None) -> None: +def enable_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: """Utility definition for `change_fx_visibility` to enable effects on layer. Args: @@ -83,7 +82,7 @@ def enable_layer_fx(layer: Optional[Union[ArtLayer, LayerSet]] = None) -> None: set_layer_fx_visibility(layer, True) -def disable_layer_fx(layer: Optional[Union[ArtLayer, LayerSet]] = None) -> None: +def disable_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: """Utility definition for `change_fx_visibility` to disable effects on layer. Args: @@ -92,7 +91,7 @@ def disable_layer_fx(layer: Optional[Union[ArtLayer, LayerSet]] = None) -> None: set_layer_fx_visibility(layer, False) -def clear_layer_fx(layer: Union[ArtLayer, LayerSet, None]) -> None: +def clear_layer_fx(layer: ArtLayer | LayerSet | None) -> None: """Removes all layer style effects. Args: @@ -107,7 +106,7 @@ def clear_layer_fx(layer: Union[ArtLayer, LayerSet, None]) -> None: desc1600.putReference(sID("target"), ref126) APP.executeAction(sID("disableLayerStyle"), desc1600, NO_DIALOG) except Exception as e: - print(e, f'\nLayer "{layer.name}" has no effects!') + print(e, f"""\nLayer "{layer.name if layer else ''}" has no effects!""") def rasterize_layer_fx(layer: ArtLayer) -> None: @@ -124,7 +123,7 @@ def rasterize_layer_fx(layer: ArtLayer) -> None: APP.executeAction(sID("rasterizeLayer"), desc1, NO_DIALOG) -def copy_layer_fx(from_layer: Union[ArtLayer, LayerSet], to_layer: Union[ArtLayer, LayerSet]) -> None: +def copy_layer_fx(from_layer: ArtLayer | LayerSet, to_layer: ArtLayer | LayerSet) -> None: """Copies the layer effects from one layer to another layer. Args: @@ -153,7 +152,7 @@ def copy_layer_fx(from_layer: Union[ArtLayer, LayerSet], to_layer: Union[ArtLaye """ -def apply_fx(layer: Union[ArtLayer, LayerSet], effects: list[LayerEffects]) -> None: +def apply_fx(layer: ArtLayer | LayerSet, effects: list[LayerEffects]) -> None: """Apply multiple layer effects to a layer. Args: @@ -179,7 +178,7 @@ def apply_fx(layer: Union[ArtLayer, LayerSet], effects: list[LayerEffects]) -> N apply_fx_drop_shadow(fx_action, fx) elif isinstance(fx, EffectGradientOverlay): apply_fx_gradient_overlay(fx_action, fx) - elif isinstance(fx, EffectStroke): + else: apply_fx_stroke(fx_action, fx) # Apply all fx actions @@ -195,26 +194,26 @@ def apply_fx_bevel(action: ActionDescriptor, fx: EffectBevel) -> None: fx: Bevel effect properties. """ d1, d2 = ActionDescriptor(), ActionDescriptor() - d1.PutEnumerated(sID("highlightMode"), sID("blendMode"), sID("screen")) + d1.putEnumerated(sID("highlightMode"), sID("blendMode"), sID("screen")) apply_color(d1, fx.highlight_color, 'highlightColor') - d1.PutUnitDouble(sID("highlightOpacity"), sID("percentUnit"), fx.highlight_opacity) - d1.PutEnumerated(sID("shadowMode"), sID("blendMode"), sID("multiply")) + d1.putUnitDouble(sID("highlightOpacity"), sID("percentUnit"), fx.highlight_opacity) + d1.putEnumerated(sID("shadowMode"), sID("blendMode"), sID("multiply")) apply_color(d1, fx.shadow_color, 'shadowColor') - d1.PutUnitDouble(sID("shadowOpacity"), sID("percentUnit"), fx.shadow_opacity) - d1.PutEnumerated(sID("bevelTechnique"), sID("bevelTechnique"), sID("softMatte")) - d1.PutEnumerated(sID("bevelStyle"), sID("bevelEmbossStyle"), sID("outerBevel")) - d1.PutBoolean(sID("useGlobalAngle"), fx.global_light) - d1.PutUnitDouble(sID("localLightingAngle"), sID("angleUnit"), fx.rotation) - d1.PutUnitDouble(sID("localLightingAltitude"), sID("angleUnit"), fx.altitude) - d1.PutUnitDouble(sID("strengthRatio"), sID("percentUnit"), fx.depth) - d1.PutUnitDouble(sID("blur"), sID("pixelsUnit"), fx.size) - d1.PutEnumerated(sID("bevelDirection"), sID("bevelEmbossStampStyle"), sID("in")) - d1.PutObject(sID("transferSpec"), sID("shapeCurveType"), d2) - d1.PutBoolean(sID("antialiasGloss"), False) - d1.PutUnitDouble(sID("softness"), sID("pixelsUnit"), fx.softness) - d1.PutBoolean(sID("useShape"), False) - d1.PutBoolean(sID("useTexture"), False) - action.PutObject(sID("bevelEmboss"), sID("bevelEmboss"), d1) + d1.putUnitDouble(sID("shadowOpacity"), sID("percentUnit"), fx.shadow_opacity) + d1.putEnumerated(sID("bevelTechnique"), sID("bevelTechnique"), sID("softMatte")) + d1.putEnumerated(sID("bevelStyle"), sID("bevelEmbossStyle"), sID("outerBevel")) + d1.putBoolean(sID("useGlobalAngle"), fx.global_light) + d1.putUnitDouble(sID("localLightingAngle"), sID("angleUnit"), fx.rotation) + d1.putUnitDouble(sID("localLightingAltitude"), sID("angleUnit"), fx.altitude) + d1.putUnitDouble(sID("strengthRatio"), sID("percentUnit"), fx.depth) + d1.putUnitDouble(sID("blur"), sID("pixelsUnit"), fx.size) + d1.putEnumerated(sID("bevelDirection"), sID("bevelEmbossStampStyle"), sID("in")) + d1.putObject(sID("transferSpec"), sID("shapeCurveType"), d2) + d1.putBoolean(sID("antialiasGloss"), False) + d1.putUnitDouble(sID("softness"), sID("pixelsUnit"), fx.softness) + d1.putBoolean(sID("useShape"), False) + d1.putBoolean(sID("useTexture"), False) + action.putObject(sID("bevelEmboss"), sID("bevelEmboss"), d1) def apply_fx_color_overlay(action: ActionDescriptor, fx: EffectColorOverlay) -> None: @@ -225,10 +224,10 @@ def apply_fx_color_overlay(action: ActionDescriptor, fx: EffectColorOverlay) -> fx: Color Overlay effect properties. """ d = ActionDescriptor() - d.PutEnumerated(sID("mode"), sID("blendMode"), sID("normal")) + d.putEnumerated(sID("mode"), sID("blendMode"), sID("normal")) apply_color(d, fx.color) - d.PutUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) - action.PutObject(sID("solidFill"), sID("solidFill"), d) + d.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) + action.putObject(sID("solidFill"), sID("solidFill"), d) def apply_fx_drop_shadow(action: ActionDescriptor, fx: EffectDropShadow) -> None: @@ -287,7 +286,7 @@ def apply_fx_gradient_overlay(action: ActionDescriptor, fx: EffectGradientOverla d3.putInteger(sID("midpoint"), 50) transparency_list.putObject(sID("transferSpec"), d3) d4.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - d4.putInteger(sID("location"), fx.size) + d4.putInteger(sID("location"), round(fx.size)) d4.putInteger(sID("midpoint"), 50) transparency_list.putObject(sID("transferSpec"), d4) d2.putList(sID("transparency"), transparency_list) diff --git a/src/helpers/layers.py b/src/helpers/layers.py index db8656b9..c60aa37b 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -3,10 +3,10 @@ """ # Standard Library Imports from contextlib import suppress -from typing import Optional, Union, Iterable +from collections.abc import Sequence, Iterable # Third Party Imports -from photoshop.api import DialogModes, ActionDescriptor, ActionReference, BlendMode +from photoshop.api import DialogModes, ActionDescriptor, ActionReference, BlendMode, ElementPlacement from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -27,8 +27,8 @@ def getLayer( name: str, - group: Union[str, None, list[str], LayerContainerTypes, Iterable[LayerContainerTypes]] = None -) -> Optional[ArtLayer]: + group: str | None | LayerContainerTypes | Iterable[LayerContainerTypes | str | None] = None +) -> ArtLayer | None: """Retrieve ArtLayer object from given name and group/group tree. Args: @@ -49,7 +49,7 @@ def getLayer( elif isinstance(group, LayerContainer): # LayerSet object given return group.artLayers[name] - elif isinstance(group, (tuple, list)): + elif isinstance(group, tuple | list): # Tuple or list of LayerSets layer_set = APP.activeDocument for g in group: @@ -61,7 +61,7 @@ def getLayer( layer_set = g return layer_set.artLayers[name] # ArtLayer can't be located - raise OSError(f"ArtLayer invalid") + raise OSError("ArtLayer invalid") except PS_EXCEPTIONS: # Layer couldn't be found if ENV.DEV_MODE: @@ -75,8 +75,8 @@ def getLayer( def getLayerSet( name: str, - group: Union[str, None, list[str], LayerContainerTypes, Iterable[LayerContainerTypes]] = None -) -> Optional[LayerSet]: + group: str | None | LayerContainerTypes | Iterable[LayerContainerTypes | str | None] = None +) -> LayerSet | None: """Retrieve layer group object. Args: @@ -94,7 +94,7 @@ def getLayerSet( elif isinstance(group, str): # LayerSet name given return APP.activeDocument.layerSets[group].layerSets[name] - elif isinstance(group, (tuple, list)): + elif isinstance(group, tuple | list): # Tuple or list of groups layer_set = APP.activeDocument for g in group: @@ -109,7 +109,7 @@ def getLayerSet( # LayerSet object given return group.layerSets[name] # LayerSet can't be located - raise OSError(f"LayerSet invalid") + raise OSError("LayerSet invalid") except PS_EXCEPTIONS: # LayerSet couldn't be found if ENV.DEV_MODE: @@ -121,7 +121,7 @@ def getLayerSet( return -def get_reference_layer(name: str, group: Union[None, str, LayerSet] = None) -> Optional[ReferenceLayer]: +def get_reference_layer(name: str, group: None | str | LayerSet | Document = None) -> ReferenceLayer | None: """Get an ArtLayer that is a static reference layer. Args: @@ -156,7 +156,7 @@ def get_reference_layer(name: str, group: Union[None, str, LayerSet] = None) -> """ -def create_new_layer(layer_name: Optional[str] = None) -> ArtLayer: +def create_new_layer(layer_name: str | None = None) -> ArtLayer: """Creates a new layer below the currently active layer. The layer will be visible. Args: @@ -174,16 +174,13 @@ def create_new_layer(layer_name: Optional[str] = None) -> ArtLayer: layer.blendMode = BlendMode.NormalBlend # Move the layer below - layer.moveAfter(active_layer) + layer.move(active_layer, ElementPlacement.PlaceAfter) return layer -def merge_layers(layers: list[ArtLayer] = None, name: Optional[str] = None) -> ArtLayer: +def merge_layers(layers: list[ArtLayer | LayerSet], name: str | None = None) -> ArtLayer: """Merge a set of layers together. - Todo: - Check if this can merge layer groups with layers. - Args: layers: Layers to be merged, uses active if not provided. name: Name of the newly created layer. @@ -191,10 +188,11 @@ def merge_layers(layers: list[ArtLayer] = None, name: Optional[str] = None) -> A Returns: Returns the merged layer. """ + # TODO Check if this can merge layer groups with layers. # Return layer if only one is present in the list - if len(layers) == 1: - return layers[0] + if len(layers) == 1 and isinstance((layer := layers[0]), ArtLayer): + return layer # Select none, then select entire list if layers: @@ -202,9 +200,14 @@ def merge_layers(layers: list[ArtLayer] = None, name: Optional[str] = None) -> A # Merge layers and return result APP.executeAction(sID("mergeLayersNew"), None, NO_DIALOG) + + active_layer = APP.activeDocument.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError("Failed to merge layers. Active layer is unexpectedly not an ArtLayer.") + if name: - APP.activeDocument.activeLayer.name = name - return APP.activeDocument.activeLayer + active_layer.name = name + return active_layer """ @@ -213,8 +216,8 @@ def merge_layers(layers: list[ArtLayer] = None, name: Optional[str] = None) -> A def group_layers( - name: Optional[str] = "New Group", - layers: Optional[list[Union[ArtLayer, LayerSet]]] = None, + name: str = "New Group", + layers: list[ArtLayer | LayerSet] | None = None, ) -> LayerSet: """Groups the selected layers. @@ -244,10 +247,15 @@ def group_layers( desc1.putInteger(sID("layerSectionEnd"), 1) desc1.putString(cID('Nm '), name) APP.executeAction(cID('Mk '), desc1, NO_DIALOG) - return APP.activeDocument.activeLayer + active_layer = APP.activeDocument.activeLayer + if not isinstance(active_layer, LayerSet): + raise ValueError("Failed to group layers. Active layer is unexpectedly not a LayerSet.") + + return active_layer -def duplicate_group(name: str) -> LayerSet: + +def duplicate_group(group: LayerSet, name: str) -> LayerSet: """Duplicates current active layer set without renaming contents. Args: @@ -256,6 +264,8 @@ def duplicate_group(name: str) -> LayerSet: Returns: The newly created layer set object. """ + select_layer(group) + desc241 = ActionDescriptor() ref4 = ActionReference() ref4.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) @@ -263,10 +273,15 @@ def duplicate_group(name: str) -> LayerSet: desc241.putString(sID("name"), name) desc241.putInteger(sID("version"), 5) APP.executeAction(sID("duplicate"), desc241, NO_DIALOG) - return APP.activeDocument.activeLayer + active_layer = APP.activeDocument.activeLayer + if not isinstance(active_layer, LayerSet): + raise ValueError("Failed to duplicate group. Active layer is unexpectedly not a LayerSet.") + + return active_layer -def merge_group(group: Optional[LayerSet] = None) -> None: + +def merge_group(group: LayerSet | None = None) -> None: """Merges a layer set into a single layer. Args: @@ -282,7 +297,7 @@ def merge_group(group: Optional[LayerSet] = None) -> None: """ -def smart_layer(layer: Union[ArtLayer, LayerSet] = None, docref: Optional[Document] = None) -> ArtLayer: +def smart_layer(layer: ArtLayer | LayerSet | None = None, docref: Document | None = None) -> ArtLayer: """Makes a given layer, or the currently selected layer(s) into a smart layer. Args: @@ -293,10 +308,15 @@ def smart_layer(layer: Union[ArtLayer, LayerSet] = None, docref: Optional[Docume if layer: docref.activeLayer = layer APP.executeAction(sID("newPlacedLayer"), None, NO_DIALOG) - return docref.activeLayer + + active_layer = APP.activeDocument.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError("Failed to convert layer to smart layer. Active layer is unexpectedly not an ArtLayer.") + + return active_layer -def edit_smart_layer(layer: Optional[ArtLayer] = None, docref: Optional[Document] = None) -> None: +def edit_smart_layer(layer: ArtLayer | None = None, docref: Document | None = None) -> None: """Opens the contents of a given smart layer (as a separate document) for editing. Args: @@ -309,7 +329,7 @@ def edit_smart_layer(layer: Optional[ArtLayer] = None, docref: Optional[Document APP.executeAction(sID("placedLayerEditContents"), None, NO_DIALOG) -def unpack_smart_layer(layer: Optional[ArtLayer] = None, docref: Optional[Document] = None) -> None: +def unpack_smart_layer(layer: ArtLayer | None = None, docref: Document | None = None) -> None: """Converts a smart layer back into its separate components. Args: @@ -327,7 +347,7 @@ def unpack_smart_layer(layer: Optional[ArtLayer] = None, docref: Optional[Docume """ -def lock_layer(layer: Union[ArtLayer, LayerSet], protection: str = "protectAll") -> None: +def lock_layer(layer: ArtLayer | LayerSet, protection: str = "protectAll") -> None: """Locks the given layer. Args: @@ -345,7 +365,7 @@ def lock_layer(layer: Union[ArtLayer, LayerSet], protection: str = "protectAll") APP.executeAction(sID("applyLocking"), d1, NO_DIALOG) -def unlock_layer(layer: Union[ArtLayer, LayerSet]) -> None: +def unlock_layer(layer: ArtLayer | LayerSet) -> None: """Unlocks the given layer. Args: @@ -359,7 +379,7 @@ def unlock_layer(layer: Union[ArtLayer, LayerSet]) -> None: """ -def select_layer(layer: Union[ArtLayer, LayerSet], make_visible: bool = False) -> None: +def select_layer(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None: """Select a layer and optionally make it visible. Args: @@ -375,7 +395,7 @@ def select_layer(layer: Union[ArtLayer, LayerSet], make_visible: bool = False) - def select_layer_add( - layer: Union[ArtLayer, LayerSet], + layer: ArtLayer | LayerSet, make_visible: bool = False ) -> None: """Add layer to currently selected and optionally force it to be visible. @@ -396,7 +416,7 @@ def select_layer_add( APP.executeAction(sID('select'), desc1, NO_DIALOG) -def select_layers(layers: list[Union[ArtLayer, LayerSet]]) -> None: +def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: """Makes a list of layers active (selected) in the layer panel. Args: @@ -419,14 +439,14 @@ def select_layers(layers: list[Union[ArtLayer, LayerSet]]) -> None: d1, r1 = ActionDescriptor(), ActionReference() # Select initial layer - r1.putIdentifier(idLayer, layers.pop().id) + r1.putIdentifier(idLayer, layers[0].id) d1.putReference(idTarget, r1) d1.putEnumerated(idSelMod, idSelModType, idAddToSel) d1.putBoolean(sID('makeVisible'), False) APP.executeAction(idSelect, d1, NO_DIALOG) # Select each additional layer - for lay in layers: + for lay in layers[1:]: r1.putIdentifier(idLayer, lay.id) d1.putReference(idTarget, r1) APP.executeAction(idSelect, d1, NO_DIALOG) diff --git a/src/helpers/masks.py b/src/helpers/masks.py index d2676c19..de2fc712 100644 --- a/src/helpers/masks.py +++ b/src/helpers/masks.py @@ -2,7 +2,7 @@ * Helpers: Masks """ # Standard Library Imports -from typing import Union +from _ctypes import COMError # Third Party Imports from photoshop.api import DialogModes, ActionDescriptor, ActionReference @@ -23,8 +23,8 @@ def copy_layer_mask( - layer_from: Union[ArtLayer, LayerSet], - layer_to: Union[ArtLayer, LayerSet] + layer_from: ArtLayer | LayerSet, + layer_to: ArtLayer | LayerSet ) -> None: """Copies mask from one layer to another. @@ -46,8 +46,8 @@ def copy_layer_mask( def copy_vector_mask( - layer_from: Union[ArtLayer, LayerSet], - layer_to: Union[ArtLayer, LayerSet] + layer_from: ArtLayer | LayerSet, + layer_to: ArtLayer | LayerSet ) -> None: """Copies vector mask from one layer to another. @@ -75,7 +75,7 @@ def copy_vector_mask( """ -def apply_mask_to_layer_fx(layer: Union[ArtLayer, LayerSet] = None) -> None: +def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: """Sets the layer mask to apply only to layer effects in blending options. Args: @@ -95,7 +95,7 @@ def apply_mask_to_layer_fx(layer: Union[ArtLayer, LayerSet] = None) -> None: def set_layer_mask( - layer: Union[ArtLayer, LayerSet, None] = None, + layer: ArtLayer | LayerSet | None = None, visible: bool = True ) -> None: """Set the visibility of a layer's mask. @@ -116,7 +116,7 @@ def set_layer_mask( APP.executeAction(cID("setd"), desc1, NO_DIALOG) -def enable_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def enable_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Enables a given layer's mask. Args: @@ -125,7 +125,7 @@ def enable_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: set_layer_mask(layer, True) -def disable_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def disable_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Disables a given layer's mask. Args: @@ -134,7 +134,7 @@ def disable_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: set_layer_mask(layer, False) -def apply_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def apply_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Applies a given layer's mask. Args: @@ -151,7 +151,7 @@ def apply_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: def set_layer_vector_mask( - layer: Union[ArtLayer, LayerSet, None] = None, + layer: ArtLayer | LayerSet | None = None, visible: bool = False ) -> None: """Set the visibility of a layer's vector mask. @@ -172,7 +172,7 @@ def set_layer_vector_mask( APP.executeAction(sID("set"), desc1, NO_DIALOG) -def enable_vector_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def enable_vector_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Enables a given layer's vector mask. Args: @@ -181,7 +181,7 @@ def enable_vector_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: set_layer_vector_mask(layer, True) -def disable_vector_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def disable_vector_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Disables a given layer's vector mask. Args: @@ -195,7 +195,7 @@ def disable_vector_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: """ -def enter_mask_channel(layer: Union[ArtLayer, LayerSet, None] = None): +def enter_mask_channel(layer: ArtLayer | LayerSet | None = None): """Enters mask channel to allow working with current layer's mask. Args: @@ -205,13 +205,13 @@ def enter_mask_channel(layer: Union[ArtLayer, LayerSet, None] = None): APP.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - r1.PutEnumerated(sID("channel"), sID("channel"), sID("mask")) - d1.PutReference(sID("target"), r1) - d1.PutBoolean(sID("makeVisible"), True) - APP.Executeaction(sID("select"), d1, NO_DIALOG) + r1.putEnumerated(sID("channel"), sID("channel"), sID("mask")) + d1.putReference(sID("target"), r1) + d1.putBoolean(sID("makeVisible"), True) + APP.executeAction(sID("select"), d1, NO_DIALOG) -def enter_rgb_channel(layer: Union[ArtLayer, LayerSet, None] = None): +def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): """Enters the RGB channel (default channel). Args: @@ -221,13 +221,13 @@ def enter_rgb_channel(layer: Union[ArtLayer, LayerSet, None] = None): APP.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - r1.PutEnumerated(sID("channel"), sID("channel"), sID("RGB")) - d1.PutReference(sID("target"), r1) - d1.PutBoolean(sID("makeVisible"), True) - APP.Executeaction(sID("select"), d1, NO_DIALOG) + r1.putEnumerated(sID("channel"), sID("channel"), sID("RGB")) + d1.putReference(sID("target"), r1) + d1.putBoolean(sID("makeVisible"), True) + APP.executeAction(sID("select"), d1, NO_DIALOG) -def create_mask(layer: Union[ArtLayer, LayerSet, None] = None): +def create_mask(layer: ArtLayer | LayerSet | None = None): """Add a mask to provided or active layer. Args: @@ -237,16 +237,16 @@ def create_mask(layer: Union[ArtLayer, LayerSet, None] = None): APP.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - d1.PutClass(sID("new"), sID("channel")) - r1.PutEnumerated(sID("channel"), sID("channel"), sID("mask")) - d1.PutReference(sID("at"), r1) - d1.PutEnumerated(sID("using"), sID("userMaskEnabled"), sID("revealAll")) - APP.Executeaction(sID("make"), d1, NO_DIALOG) + d1.putClass(sID("new"), sID("channel")) + r1.putEnumerated(sID("channel"), sID("channel"), sID("mask")) + d1.putReference(sID("at"), r1) + d1.putEnumerated(sID("using"), sID("userMaskEnabled"), sID("revealAll")) + APP.executeAction(sID("make"), d1, NO_DIALOG) def copy_to_mask( - target: Union[ArtLayer, LayerSet], - source: Union[ArtLayer, LayerSet, None] = None, + target: ArtLayer | LayerSet, + source: ArtLayer | LayerSet | None = None, ): """Copies the pixels of the current layer, creates a mask on target layer, enters that layer's mask, and pastes to the mask before exiting the mask. @@ -269,7 +269,11 @@ def copy_to_mask( docref.activeLayer = target create_mask() enter_mask_channel() - docref.paste() + try: + docref.paste() + except COMError: + # The operation likely succeeded, but an error was thrown anyways + pass enter_rgb_channel() @@ -278,7 +282,7 @@ def copy_to_mask( """ -def delete_mask(layer: Union[ArtLayer, LayerSet, None] = None) -> None: +def delete_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Removes a given layer's mask. Args: diff --git a/src/helpers/position.py b/src/helpers/position.py index e4802df2..089d7bf0 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -3,7 +3,8 @@ """ # Standard Library Imports import math -from typing import Optional, Sequence, Union +from typing import Literal +from collections.abc import Sequence # Third Party Imports from photoshop.api import DialogModes, AnchorPosition @@ -40,11 +41,12 @@ * Alignment Funcs """ +DimensionNames = Literal["width", "height", "center_x", "center_y", "left", "right", "top", "bottom"] def align( - axis: Union[Dimensions, list[Dimensions], None] = None, - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + axis: DimensionNames | Sequence[DimensionNames] | None = None, + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Align the currently active layer to current selection, vertically or horizontal. @@ -54,14 +56,14 @@ def align( ref: Reference to align the layer within. Uses current selection if not provided. """ # Default axis is both - axis = axis or [Dimensions.CenterX, Dimensions.CenterY] + axis = axis or ("center_x", "center_y") axis = [axis] if isinstance(axis, str) else axis x, y = 0, 0 # Get the dimensions of layer and reference if not provided layer = layer or APP.activeDocument.activeLayer - item: type[LayerDimensions] = get_layer_dimensions(layer) - area: type[LayerDimensions] = ref if isinstance(ref, dict) else ( + item = get_layer_dimensions(layer) + area = ref if isinstance(ref, dict) else ( get_dimensions_from_bounds(APP.activeDocument.selection.bounds) if not ref else get_layer_dimensions(ref)) @@ -77,59 +79,59 @@ def align( def align_all( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing CenterX and CenterY to align function.""" - align([Dimensions.CenterX, Dimensions.CenterY], layer, ref) + align(["center_x", "center_y"], layer, ref) def align_vertical( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing CenterY to align function.""" - align(Dimensions.CenterY, layer, ref) + align("center_y", layer, ref) def align_horizontal( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing CenterX to align function.""" - align(Dimensions.CenterX, layer, ref) + align("center_x", layer, ref) def align_left( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing Left to align function.""" - align(Dimensions.Left, layer, ref) + align("left", layer, ref) def align_right( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing Right to align function.""" - align(Dimensions.Right, layer, ref) + align("right", layer, ref) def align_top( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing Top to align function.""" - align(Dimensions.Top, layer, ref) + align("top", layer, ref) def align_bottom( - layer: Union[ArtLayer, LayerSet, None] = None, - ref: Union[ArtLayer, LayerSet, ReferenceLayer, type[LayerDimensions], None] = None + layer: ArtLayer | LayerSet | None = None, + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None ) -> None: """Utility definition for passing Bottom to align function.""" - align(Dimensions.Bottom, layer, ref) + align("bottom", layer, ref) """ @@ -138,10 +140,10 @@ def align_bottom( def position_between_layers( - layer: Union[ArtLayer, LayerSet], - top_layer: Union[ArtLayer, LayerSet], - bottom_layer: Union[ArtLayer, LayerSet], - docref: Optional[Document] = None + layer: ArtLayer | LayerSet, + top_layer: ArtLayer | LayerSet, + bottom_layer: ArtLayer | LayerSet, + docref: Document | None = None ) -> None: """Align layer vertically between two reference layers. @@ -159,7 +161,7 @@ def position_between_layers( def position_dividers( dividers: Sequence[ArtLayer | LayerSet], layers: Sequence[ArtLayer | LayerSet], - docref: Optional[Document] = None + docref: Document | None = None ) -> None: """Positions a list of dividers between a list of layers. @@ -179,8 +181,8 @@ def position_dividers( def spread_layers_over_reference( layers: list[ArtLayer], ref: ReferenceLayer, - gap: Optional[Union[int, float]] = None, - inside_gap: Union[int, float, None] = None, + gap: float = 0, + inside_gap: float = 0, outside_matching: bool = True ) -> None: """Spread layers apart across a reference layer. @@ -222,7 +224,7 @@ def spread_layers_over_reference( space_layers_apart(layers, inside_gap) -def space_layers_apart(layers: list[Union[ArtLayer, LayerSet]], gap: Union[int, float]) -> None: +def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) -> None: """Position list of layers apart using a given gap. Args: @@ -230,7 +232,7 @@ def space_layers_apart(layers: list[Union[ArtLayer, LayerSet]], gap: Union[int, gap: Gap in pixels. """ # Position each layer relative to the one above it - for i in range((len(layers) - 1)): + for i in range(len(layers) - 1): delta = (layers[i].bounds[3] + gap) - layers[i + 1].bounds[1] layers[i + 1].translate(0, delta) @@ -241,12 +243,12 @@ def space_layers_apart(layers: list[Union[ArtLayer, LayerSet]], gap: Union[int, def frame_layer( - layer: Union[ArtLayer, LayerSet], - ref: Union[ArtLayer, LayerSet, type[LayerDimensions]], + layer: ArtLayer | LayerSet, + ref: ArtLayer | LayerSet | LayerDimensions, smallest: bool = False, anchor: AnchorPosition = AnchorPosition.MiddleCenter, - alignments: Union[Dimensions, list[Dimensions], None] = None, - scale: int = 100, + alignments: DimensionNames | Sequence[DimensionNames] | None = None, + scale: float = 100, ) -> None: """Scale and position a layer within the bounds of a reference. @@ -270,15 +272,15 @@ def frame_layer( layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical - align(alignments or [Dimensions.CenterX, Dimensions.CenterY], layer, ref_dim) + align(alignments or ("center_x", "center_y"), layer, ref_dim) def frame_layer_by_height( - layer: Union[ArtLayer, LayerSet], - ref: Union[ArtLayer, LayerSet, type[LayerDimensions]], + layer: ArtLayer | LayerSet, + ref: ArtLayer | LayerSet | LayerDimensions, anchor: AnchorPosition = AnchorPosition.MiddleCenter, - alignments: Union[Dimensions, list[Dimensions], None] = None, - scale: int = 100, + alignments: DimensionNames | Sequence[DimensionNames] | None = None, + scale: float = 100, ) -> None: """Scale and position a layer based on the height of a reference layer. @@ -297,15 +299,15 @@ def frame_layer_by_height( layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical - align(alignments or [Dimensions.CenterX, Dimensions.CenterY], layer, ref_dim) + align(alignments or ("center_x", "center_y"), layer, ref_dim) def frame_layer_by_width( - layer: Union[ArtLayer, LayerSet], - ref: Union[ArtLayer, LayerSet, type[LayerDimensions]], + layer: ArtLayer | LayerSet, + ref: ArtLayer | LayerSet | LayerDimensions, anchor: AnchorPosition = AnchorPosition.MiddleCenter, - alignments: Union[Dimensions, list[Dimensions], None] = None, - scale: int = 100, + alignments: DimensionNames | Sequence[DimensionNames] | None = None, + scale: float = 100, ) -> None: """Scale and position a layer based on the width of a reference layer. @@ -324,7 +326,7 @@ def frame_layer_by_width( layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical - align(alignments or [Dimensions.CenterX, Dimensions.CenterY], layer, ref_dim) + align(alignments or ("center_x", "center_y"), layer, ref_dim) """ @@ -333,9 +335,9 @@ def frame_layer_by_width( def check_reference_overlap( - layer: Optional[ArtLayer], - ref_bounds: tuple[int, int, int, int], - docsel: Optional[Selection] = None + layer: ArtLayer, + ref_bounds: tuple[float,float,float,float], + docsel: Selection | None = None ): """Checks if a layer is overlapping with given set of bounds. @@ -347,10 +349,11 @@ def check_reference_overlap( Returns: Bounds if overlap exists, otherwise None. """ - select_bounds(ref_bounds, selection=docsel) + selection = docsel or APP.activeDocument.selection + select_bounds(ref_bounds, selection=selection) select_overlapping(layer) - if bounds := check_selection_bounds(docsel): - docsel.deselect() + if bounds := check_selection_bounds(selection): + selection.deselect() return ref_bounds[1] - bounds[3] return 0 @@ -358,8 +361,8 @@ def check_reference_overlap( def clear_reference_vertical( layer: ArtLayer, ref: ReferenceLayer, - docsel: Optional[Selection] = None -) -> Union[int, float]: + docsel: Selection | None = None +) -> int | float: """Nudges a layer clear vertically of a given reference layer or area. Args: @@ -385,12 +388,12 @@ def clear_reference_vertical_multi( text_layers: list[ArtLayer], ref: ReferenceLayer, loyalty_ref: ReferenceLayer, - space: Union[int, float], + space: int | float, uniform_gap: bool = False, - font_size: Optional[float] = None, + font_size: float | None = None, step: float = 0.2, - docref: Optional[Document] = None, - docsel: Optional[Selection] = None + docref: Document | None = None, + docsel: Selection | None = None ) -> None: """Shift or resize multiple text layers to prevent vertical collision with a reference area. @@ -460,7 +463,7 @@ def clear_reference_vertical_multi( spread_layers_over_reference( layers=text_layers, ref=ref, - gap=space if not uniform_gap else None, + gap=space if not uniform_gap else 0, outside_matching=False) # Check for another iteration diff --git a/src/helpers/selection.py b/src/helpers/selection.py index 92410abc..24d1f58d 100644 --- a/src/helpers/selection.py +++ b/src/helpers/selection.py @@ -3,11 +3,11 @@ """ # Standard Library Imports from contextlib import suppress -from typing import Optional # Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document +from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection from photoshop.api import ( ActionDescriptor, @@ -20,7 +20,7 @@ from src.utils.adobe import PS_EXCEPTIONS # Photoshop infrastructure -cID, sID = APP.charIDtoTypeID, APP.stringIDToTypeID +cID, sID = APP.charIDToTypeID, APP.stringIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -29,8 +29,8 @@ def select_bounds( - bounds: tuple[int, int, int, int], - selection: Optional[Selection] = None + bounds: tuple[float,float,float,float], + selection: Selection | None = None ) -> None: """Create a selection using a list of bound values. @@ -40,14 +40,14 @@ def select_bounds( """ selection = selection or APP.activeDocument.selection left, top, right, bottom = bounds - selection.select([ - [left, top], - [right, top], - [right, bottom], - [left, bottom]]) + selection.select( + ((left, top), + (right, top), + (right, bottom), + (left, bottom))) -def select_layer_bounds(layer: ArtLayer = None, selection: Optional[Selection] = None) -> None: +def select_layer_bounds(layer: ArtLayer | LayerSet | None = None, selection: Selection | None = None) -> None: """Select the bounding box of a given layer. Args: @@ -65,7 +65,7 @@ def select_overlapping(layer: ArtLayer) -> None: Args: layer: Layer with pixels to select. """ - with suppress(PS_EXCEPTIONS): + with suppress(*PS_EXCEPTIONS): idChannel = sID('channel') desc1, ref1, ref2 = ActionDescriptor(), ActionReference(), ActionReference() ref1.putEnumerated(idChannel, idChannel, sID("transparencyEnum")) @@ -76,7 +76,7 @@ def select_overlapping(layer: ArtLayer) -> None: APP.executeAction(sID("interfaceIconFrameDimmed"), desc1, NO_DIALOG) -def select_canvas(docref: Optional[Document] = None, bleed: int = 0): +def select_canvas(docref: Document | None = None, bleed: int = 0): """Select the entire canvas of a provided or active document. Args: @@ -84,12 +84,12 @@ def select_canvas(docref: Optional[Document] = None, bleed: int = 0): bleed: Amount of bleed edge to leave around selection, defaults to 0. """ docref = docref or APP.activeDocument - docref.selection.select([ - [0 + bleed, 0 + bleed], - [docref.width - bleed, 0 + bleed], - [docref.width - bleed, docref.height - bleed], - [0 + bleed, docref.height - bleed] - ]) + docref.selection.select( + ((0 + bleed, 0 + bleed), + (docref.width - bleed, 0 + bleed), + (docref.width - bleed, docref.height - bleed), + (0 + bleed, docref.height - bleed)) + ) """ @@ -97,7 +97,7 @@ def select_canvas(docref: Optional[Document] = None, bleed: int = 0): """ -def select_layer_pixels(layer: Optional[ArtLayer] = None) -> None: +def select_layer_pixels(layer: ArtLayer | None = None) -> None: """Select pixels of the active layer, or a target layer. Args: @@ -117,7 +117,7 @@ def select_layer_pixels(layer: Optional[ArtLayer] = None) -> None: APP.executeAction(sID("set"), des1, NO_DIALOG) -def select_vector_layer_pixels(layer: Optional[ArtLayer] = None) -> None: +def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: """Select pixels of the active vector layer, or a target layer. Args: @@ -142,7 +142,7 @@ def select_vector_layer_pixels(layer: Optional[ArtLayer] = None) -> None: """ -def check_selection_bounds(selection: Optional[Selection] = None) -> Optional[tuple[int, int, int, int]]: +def check_selection_bounds(selection: Selection | None = None) -> tuple[float, float, float, float] | None: """Verifies if a selection has valid bounds. Args: @@ -152,7 +152,7 @@ def check_selection_bounds(selection: Optional[Selection] = None) -> Optional[tu An empty list if selection is invalid, otherwise return bounds of selection. """ selection = selection or APP.activeDocument.selection - with suppress(PS_EXCEPTIONS): + with suppress(*PS_EXCEPTIONS): if selection.bounds != (0, 0, 0, 0): return selection.bounds return diff --git a/src/helpers/text.py b/src/helpers/text.py index fc6fdd03..cbd692ca 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -2,7 +2,8 @@ * Helpers: Text Items """ # Standard Library Imports -from typing import Sequence, Union, Optional, Any +from typing import Literal, overload +from collections.abc import Sequence # Third Party Imports from photoshop.api import ( @@ -44,7 +45,7 @@ def get_font_size(layer: ArtLayer) -> float: return round(layer.textItem.size * get_text_scale_factor(layer), 2) -def get_text_key(layer: ArtLayer) -> Any: +def get_text_key(layer: ArtLayer) -> ActionDescriptor: """Get the textKey action reference from a TextLayer. Args: @@ -56,7 +57,7 @@ def get_text_key(layer: ArtLayer) -> Any: return descriptor.getObjectValue(sID('textKey')) -def apply_text_key(text_layer, text_key) -> None: +def apply_text_key(text_layer: ArtLayer, text_key: ActionDescriptor) -> None: """Applies a TextKey action descriptor to a given TextLayer. Args: @@ -70,7 +71,7 @@ def apply_text_key(text_layer, text_key) -> None: APP.executeAction(sID("set"), action, NO_DIALOG) -def get_line_count(layer: Optional[ArtLayer] = None, docref: Optional[Document] = None) -> int: +def get_line_count(layer: ArtLayer, docref: Document | None = None) -> int: """Get the number of lines in a paragraph text layer. Args: @@ -152,7 +153,7 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: def replace_text_legacy( find: str, replace: str, - layer: Optional[ArtLayer] = None, + layer: ArtLayer | None = None, targeted_replace: bool = True ) -> None: """Replace all instances of `replace_this` in the specified layer with `replace_with`, using Photoshop's @@ -302,12 +303,27 @@ def remove_leading_text(layer: ArtLayer, idx: int) -> None: * Text Item Size """ +ScaleAxis = Literal["xx", "yy"] +@overload def get_text_scale_factor( - layer: Optional[ArtLayer] = None, - axis: Optional[Union[str, list]] = 'yy', - text_key=None -) -> Union[int, float, list[Union[int, float]]]: + layer: ArtLayer, + axis: list[ScaleAxis], + text_key: ActionDescriptor | None=None +) -> list[float]: ... + +@overload +def get_text_scale_factor( + layer: ArtLayer, + axis: ScaleAxis = 'yy', + text_key: ActionDescriptor | None=None +) -> float: ... + +def get_text_scale_factor( + layer: ArtLayer, + axis: ScaleAxis | list[ScaleAxis] = 'yy', + text_key: ActionDescriptor | None=None +) -> float | list[float]: """Get the scale factor of the document for changing text size. Args: @@ -318,6 +334,8 @@ def get_text_scale_factor( Returns: Float scale factor """ + + # Get the textKey if not provided if not text_key: # Get text key @@ -330,11 +348,11 @@ def get_text_scale_factor( # Check list of axis if isinstance(axis, list): return [transform.getUnitDoubleValue(sID(n)) for n in axis] - # Check string axis - if isinstance(axis, str): - return transform.getUnitDoubleValue(sID(axis)) - return 1 if not isinstance(axis, list) else [1] * len(axis) - + # String axis + return transform.getUnitDoubleValue(sID(axis)) + if isinstance(axis, list): + return [1.0] * len(axis) + return 1.0 """ * Text Alignment @@ -388,7 +406,7 @@ def align_text_center(action_list: ActionList, start: int, end: int) -> None: """ -def set_space_after(space: Union[int, float]) -> None: +def set_space_after(space: int | float) -> None: """Manually assign the 'space after' property for a paragraph text layer. Args: @@ -397,16 +415,16 @@ def set_space_after(space: Union[int, float]) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() deesc2 = ActionDescriptor() - ref1.PutProperty(sID("property"), sID("paragraphStyle")) - ref1.PutEnumerated(sID("textLayer"), sID("ordinal"), sID("targetEnum")) - desc1.PutReference(sID("target"), ref1) - deesc2.PutInteger(sID("textOverrideFeatureName"), 808464438) - deesc2.PutUnitDouble(sID("spaceAfter"), sID("pointsUnit"), space) - desc1.PutObject(sID("to"), sID("paragraphStyle"), deesc2) + ref1.putProperty(sID("property"), sID("paragraphStyle")) + ref1.putEnumerated(sID("textLayer"), sID("ordinal"), sID("targetEnum")) + desc1.putReference(sID("target"), ref1) + deesc2.putInteger(sID("textOverrideFeatureName"), 808464438) + deesc2.putUnitDouble(sID("spaceAfter"), sID("pointsUnit"), space) + desc1.putObject(sID("to"), sID("paragraphStyle"), deesc2) APP.executeAction(sID("set"), desc1, NO_DIALOG) -def set_text_leading(layer: ArtLayer, leading: Union[float, int]) -> None: +def set_text_leading(layer: ArtLayer, leading: float | int) -> None: """Manually assign font leading to a text layer using action descriptors. Args: @@ -421,10 +439,10 @@ def set_text_leading(layer: ArtLayer, leading: Union[float, int]) -> None: desc1.putReference(sID("target"), ref1) desc2.putUnitDouble(sID("leading"), sID("pointsUnit"), leading) desc1.putObject(sID("to"), sID("textStyle"), desc2) - APP.executeaction(sID("set"), desc1, NO_DIALOG) + APP.executeAction(sID("set"), desc1, NO_DIALOG) -def set_text_size(layer: ArtLayer, size: Union[float, int]) -> None: +def set_text_size(layer: ArtLayer, size: float | int) -> None: """Manually assign font size to a text layer using action descriptors. Args: @@ -443,7 +461,7 @@ def set_text_size(layer: ArtLayer, size: Union[float, int]) -> None: APP.executeAction(sID("set"), desc1, NO_DIALOG) -def set_text_size_and_leading(layer: ArtLayer, size: Union[int, float], leading: Union[int, float]) -> None: +def set_text_size_and_leading(layer: ArtLayer, size: int | float, leading: int | float) -> None: """Manually assign font size and leading space to a text layer using action descriptors. Args: @@ -482,7 +500,7 @@ def set_composer(layer: ArtLayer, every: bool = False) -> None: desc1.putReference(sID("target"), ref1) desc2.putBoolean(sID("textEveryLineComposer"), every) desc1.putObject(sID("to"), paraStyle, desc2) - APP.executeaction(sID("set"), desc1, NO_DIALOG) + APP.executeAction(sID("set"), desc1, NO_DIALOG) def set_composer_single_line(layer: ArtLayer) -> None: @@ -511,7 +529,8 @@ def set_font(layer: ArtLayer, font_name: str) -> None: layer: ArtLayer containing TextItem. font_name: Name of the font to set. """ - layer.textItem.font = APP.fonts.getByName(font_name).postScriptName + if font := APP.fonts.getByName(font_name): + layer.textItem.font = font.postScriptName """ @@ -519,7 +538,7 @@ def set_font(layer: ArtLayer, font_name: str) -> None: """ -def ensure_visible_reference(reference: ArtLayer) -> Optional[TextItem]: +def ensure_visible_reference(reference: ArtLayer) -> TextItem | None: """Ensures that a layer used for reference has bounds if it is a text layer. Args: @@ -601,7 +620,7 @@ def scale_text_left_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) gap: Minimum gap to ensure between the layer and reference (DPI adjusted). """ # Ensure a valid and visible reference layer - if not reference or reference.bounds == [0, 0, 0, 0]: + if not reference or reference.bounds == (0, 0, 0, 0): return ref_TI = ensure_visible_reference(reference) @@ -645,11 +664,11 @@ def scale_text_left_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) def scale_text_to_width( layer: ArtLayer, - width: int, + width: int | float, spacing: int = 64, step: float = 0.4, - font_size: Optional[float] = None -) -> Optional[float]: + font_size: float | None = None +) -> float | None: """Resize a given text layer's font size/leading until it fits inside a reference width. Args: @@ -692,11 +711,11 @@ def scale_text_to_width( def scale_text_to_height( layer: ArtLayer, - height: int, - spacing: int = 64, + height: float, + spacing: float = 64, step: float = 0.4, - font_size: Optional[float] = None -) -> Optional[float]: + font_size: float | None = None +) -> float | None: """Resize a given text layer's font size/leading until it fits inside a reference width. Args: @@ -739,7 +758,7 @@ def scale_text_to_height( def scale_text_to_width_textbox( layer: ArtLayer, - font_size: Optional[float] = None, + font_size: float | None = None, step: float = 0.1 ) -> None: """Check if the text in a TextLayer exceeds its bounding box. @@ -762,10 +781,10 @@ def scale_text_to_width_textbox( def scale_text_layers_to_height( text_layers: list[ArtLayer], - ref_height: Union[int, float], - font_size: Optional[float] = None, + ref_height: int | float, + font_size: float | None = None, step_sizes: Sequence[float] | None = None -) -> Optional[float]: +) -> float | None: """Scale multiple text layers until they all can fit within the same given height dimension. Args: diff --git a/src/layouts.py b/src/layouts.py index ef6c728e..9279ab23 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -4,7 +4,8 @@ # Standard Library Imports from datetime import date, datetime import re -from typing import NotRequired, Optional, Match, TypedDict, Union, Type +from typing import Any, NotRequired, TypedDict, Union +from re import Match from os import path as osp from pathlib import Path from functools import cached_property @@ -43,6 +44,25 @@ class PowerToughness(TypedDict): toughness: str +class ProtoDetails(TypedDict): + oracle_text: str + mana_cost: str + pt: str + +class PlaneswalkerAbility(TypedDict): + text: str + icon: str + cost: str + +class SagaLine(TypedDict): + text: str + icons: list[str] + +class ClassLine(TypedDict): + cost: str | None + level: str + text: str + """ * Layout Processing """ @@ -98,7 +118,7 @@ def assign_layout(filename: Path) -> "str | CardLayout": return msg_error(name_failed, reason="Layout incompatible") -def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]): +def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]) -> list["str | CardLayout"]: """Join any layout objects that are dual sides of the same card, i.e. Split cards. Args: @@ -108,19 +128,19 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]): List of layouts, with split layouts joined. """ # Check if we have any split cards - normal: list[Union[str, CardLayout]] = [ + normal: list[str | CardLayout] = [ n for n in layouts if isinstance(n, str) - or n.card_class != LayoutType.Split] + or not isinstance(n, SplitLayout)] split: list[SplitLayout] = [ n for n in layouts - if not isinstance(n, str) - and n.card_class == LayoutType.Split] + if isinstance(n, SplitLayout)] if not split: return normal # Join any identical split cards - skip, add = [], [] + skip: list[SplitLayout] = [] + add: list[SplitLayout] = [] for card in split: if card in skip: continue @@ -129,9 +149,9 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]): continue if str(c) == str(card): # Order them according to name position - card.art_file = [*card.art_file, *c.art_file] if ( + card.art_files = [*card.art_files, *c.art_files] if ( normalize_str(card.name[0]) == normalize_str(card.file['name']) - ) else [*c.art_file, *card.art_file] + ) else [*c.art_files, *card.art_files] skip.extend([card, c]) add.append(card) return [*normal, *add] @@ -144,13 +164,20 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]): class NormalLayout: """Defines unified properties for all cards and serves as the layout for any M15 style typical card.""" - card_class: str = LayoutType.Normal + @cached_property + def card_class(self) -> str: + return LayoutType.Normal # Static properties - is_transform: bool = False - is_mdfc: bool = False + @cached_property + def is_transform(self) -> bool: + return False + + @cached_property + def is_mdfc(self) -> bool: + return False - def __init__(self, scryfall: dict, file: dict): + def __init__(self, scryfall: dict[str,Any], file: CardDetails): # Establish core properties self._file = file @@ -176,7 +203,7 @@ def file(self) -> CardDetails: return self._file @cached_property - def scryfall(self) -> dict: + def scryfall(self) -> dict[str,Any]: """Card data fetched from Scryfall.""" return self._scryfall @@ -188,7 +215,7 @@ def template_file(self) -> Path: @cached_property def art_file(self) -> Path: """Path: Art image file path.""" - return self.file['file'] + return Path(self.file['file']) @cached_property def scryfall_scan(self) -> str: @@ -205,7 +232,7 @@ def set(self) -> str: return self.scryfall.get('set', 'MTG').upper() @cached_property - def set_data(self) -> dict: + def set_data(self) -> dict[str,Any]: """Set data from the current hexproof.io data file.""" return CON.set_data.get(self.scryfall.get('set', 'mtg').lower(), {}) @@ -227,7 +254,7 @@ def date(self) -> date: """ @cached_property - def card(self) -> dict: + def card(self) -> dict[str,Any]: """Main card data object to pull most relevant data from.""" if faces := self.scryfall.get('card_faces', []): # Card with multiple faces, first index is always front side @@ -239,15 +266,16 @@ def card(self) -> dict: break if not matching_face: + face_listing = '\n'.join(f" - {face['name']}" for face in faces) CONSOLE.update( msg_warn( f"""None of the card faces -{'\n'.join((f" - {face['name']}" for face in faces))} +{face_listing} matches the input file's name '{self.input_name}'. Defaulting to first face.""" ) ) - matching_face = faces[0] + return faces[0] return matching_face @@ -255,7 +283,7 @@ def card(self) -> dict: return self.scryfall @cached_property - def first_print(self) -> dict: + def first_print(self) -> dict[str,Any]: """Card data fetched from Scryfall representing the first print of this card.""" first = get_cards_oracle(self.scryfall.get('oracle_id', '')) return first[0] if first else {} @@ -310,7 +338,7 @@ def input_name(self) -> str: return self.file['name'] @cached_property - def mana_cost(self) -> Optional[str]: + def mana_cost(self) -> str: """Scryfall formatted card mana cost.""" return self.card.get('mana_cost', '') @@ -393,7 +421,7 @@ def color_identity(self) -> list[str]: @cached_property def color_indicator(self) -> str: - """Color indicator identity array, e.g. [W, U].""" + """Color indicator identity , e.g. "WU".""" return get_ordered_colors(self.card.get('color_indicator', [])) """ @@ -435,14 +463,15 @@ def artist(self) -> str: if self.file.get('artist'): return self.file['artist'] + artist = self.card.get('artist', 'Unknown') + count: set[str] = set() + # Check for duplicate last names - artist, count = self.card.get('artist', 'Unknown'), [] - if '&' in artist: + if isinstance(artist, str) and '&' in artist: for w in artist.split(' '): - if w in count: - count.remove(w) - count.append(w) + count.add(w) return ' '.join(count) + return artist @cached_property @@ -453,12 +482,12 @@ def collector_number(self) -> int: return 0 @cached_property - def collector_number_raw(self) -> Optional[str]: + def collector_number_raw(self) -> str | None: """str | None: Card number assigned within release set. Raw string representation, allows non-digits.""" return self.scryfall.get('collector_number') @cached_property - def card_count(self) -> Optional[int]: + def card_count(self) -> int | None: """int | None: Number of cards within the card's release set. Only required in 'Normal' Collector Mode.""" # Skip if collector mode doesn't require it or if collector number is bad @@ -492,7 +521,7 @@ def creator(self) -> str: """ @cached_property - def symbol_svg(self) -> Optional[Path]: + def symbol_svg(self) -> Path | None: """SVG path definition for card's expansion symbol.""" # If code is default, perform replacement and check if we have a local asset first @@ -521,7 +550,7 @@ def symbol_svg(self) -> Optional[Path]: return @cached_property - def watermark(self) -> Optional[str]: + def watermark(self) -> str | None: """Name of the card's watermark file that is actually used, if provided.""" if not self.watermark_svg: return @@ -530,14 +559,14 @@ def watermark(self) -> Optional[str]: return self.watermark_svg.stem.lower() @cached_property - def watermark_raw(self) -> Optional[str]: + def watermark_raw(self) -> str | None: """Name of the card's watermark from raw Scryfall data, if provided.""" return self.card.get('watermark') @cached_property - def watermark_svg(self) -> Optional[Path]: + def watermark_svg(self) -> Path | None: """Path to the watermark SVG file, if provided.""" - def _find_watermark_svg(wm: str) -> Optional[Path]: + def _find_watermark_svg(wm: str) -> Path | None: """Try to find a watermark SVG asset, allowing for special cases and set code fallbacks. Args: @@ -569,7 +598,7 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: return _find_watermark_svg(CFG.watermark_default) # WatermarkMode: Automatic - path = _find_watermark_svg(self.watermark_raw) + path = _find_watermark_svg(self.watermark_raw) if self.watermark_raw else None if path or CFG.watermark_mode == WatermarkMode.Automatic: return path @@ -577,7 +606,7 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: return _find_watermark_svg(CFG.watermark_default) @cached_property - def watermark_basic(self) -> Optional[Path]: + def watermark_basic(self) -> Path | None: """Optional[Path]: Path to basic land watermark, if card is a Basic Land.""" if not self.is_basic_land: return @@ -742,7 +771,7 @@ def transform_icon(self) -> str: return TransformIcons.CONVERT @cached_property - def other_face(self) -> dict: + def other_face(self) -> dict[str,Any]: """Card data from opposing face if provided.""" for face in self.scryfall.get('card_faces', []): if face.get('name') != self.name_raw: @@ -750,7 +779,7 @@ def other_face(self) -> dict: return {} @cached_property - def other_face_frame(self) -> Union[FrameDetails, dict]: + def other_face_frame(self) -> FrameDetails | dict[str, Any]: """Calculated frame information of opposing face, if provided.""" return get_frame_details(self.other_face) if self.other_face else {} @@ -799,7 +828,7 @@ def other_face_toughness(self) -> str: return self.other_face.get('toughness', '') @cached_property - def other_face_left(self) -> Optional[str]: + def other_face_left(self) -> str: """Abridged type of the opposing side to display on bottom MDFC bar.""" return self.other_face_type_line_raw.split(' ')[-1] if self.other_face else '' @@ -822,7 +851,9 @@ def other_face_right(self) -> str: class MutateLayout(NormalLayout): """Mutate card layout introduced in Ikoria: Lair of Behemoths.""" - card_class: str = LayoutType.Mutate + @cached_property + def card_class(self) -> str: + return LayoutType.Mutate """ * Text Info @@ -850,7 +881,9 @@ def mutate_text(self) -> str: class PrototypeLayout(NormalLayout): """Prototype card layout, introduced in The Brothers' War.""" - card_class: str = LayoutType.Prototype + @cached_property + def card_class(self) -> str: + return LayoutType.Prototype """ * Text Info @@ -865,14 +898,14 @@ def oracle_text(self) -> str: """ @cached_property - def proto_details(self) -> dict: + def proto_details(self) -> ProtoDetails: """Returns dictionary containing prototype data and separated oracle text.""" - proto_text, rules_text = self.card.get('oracle_text').split("\n", 1) + proto_text, rules_text = self.card.get('oracle_text', "\n").split("\n", 1) match = CardTextPatterns.PROTOTYPE.match(proto_text) return { 'oracle_text': rules_text, - 'mana_cost': match[1], - 'pt': match[2] + 'mana_cost': match[1] if match else "", + 'pt': match[2] if match else "" } @cached_property @@ -897,7 +930,10 @@ def proto_color(self) -> str: class PlaneswalkerLayout(NormalLayout): """Planeswalker card layout introduced in Lorwyn block.""" - card_class: str = LayoutType.Planeswalker + + @cached_property + def card_class(self) -> str: + return LayoutType.Planeswalker """ * Text Info @@ -934,7 +970,7 @@ def pw_size(self) -> int: return 3 if len(self.pw_abilities) <= 3 else 4 @cached_property - def pw_abilities(self) -> list[dict]: + def pw_abilities(self) -> list[PlaneswalkerAbility]: """Processes Planeswalker text into listed abilities.""" lines = CardTextPatterns.PLANESWALKER.findall(self.oracle_text_raw) en_lines = lines.copy() @@ -961,7 +997,7 @@ def pw_abilities(self) -> list[dict]: lines = new_lines # Create list of ability dictionaries - abilities: list[dict] = [] + abilities: list[PlaneswalkerAbility] = [] for i, line in enumerate(lines): index = en_lines[i].find(": ") abilities.append({ @@ -972,8 +1008,8 @@ def pw_abilities(self) -> list[dict]: } if 5 > index > 0 else { # Static ability 'text': line, - 'icon': None, - 'cost': None + 'icon': "", + 'cost': "" }) return abilities @@ -981,8 +1017,13 @@ def pw_abilities(self) -> list[dict]: class PlaneswalkerTransformLayout(PlaneswalkerLayout): """Transform version of the Planeswalker card layout introduced in Innistrad block.""" + def __init__(self, scryfall: dict[str, Any], file: CardDetails): + super().__init__(scryfall, file) + # Static properties - is_transform: bool = True + @cached_property + def is_transform(self) -> bool: + return True @cached_property def card_class(self) -> str: @@ -994,7 +1035,9 @@ class PlaneswalkerMDFCLayout(PlaneswalkerLayout): """MDFC version of the Planeswalker card layout introduced in Kaldheim.""" # Static properties - is_mdfc: bool = True + @cached_property + def is_mdfc(self) -> bool: + return True @cached_property def card_class(self) -> str: @@ -1006,7 +1049,9 @@ class TransformLayout(NormalLayout): """Transform card layout, introduced in Innistrad block.""" # Static properties - is_transform: bool = True + @cached_property + def is_transform(self) -> bool: + return True @cached_property def card_class(self) -> str: @@ -1018,7 +1063,9 @@ class ModalDoubleFacedLayout(NormalLayout): """Modal Double Faced card layout, introduced in Zendikar Rising.""" # Static properties - is_mdfc: bool = True + @cached_property + def is_mdfc(self) -> bool: + return True @cached_property def card_class(self) -> str: @@ -1041,14 +1088,16 @@ def oracle_text(self) -> str: class AdventureLayout(NormalLayout): """Adventure card layout, introduced in Throne of Eldraine.""" - card_class: str = LayoutType.Adventure + @cached_property + def card_class(self) -> str: + return LayoutType.Adventure """ * Core Data """ @cached_property - def adventure(self) -> dict: + def adventure(self) -> dict[str,Any]: """Card object for adventure side.""" return self.scryfall['card_faces'][1] @@ -1110,14 +1159,16 @@ def adventure_colors(self) -> str: class LevelerLayout(NormalLayout): """Leveler card layout, introduced in Rise of the Eldrazi.""" - card_class: str = LayoutType.Leveler + @cached_property + def card_class(self) -> str: + return LayoutType.Leveler """ * Leveler Text """ @cached_property - def leveler_match(self) -> Optional[Match[str]]: + def leveler_match(self) -> Match[str] | None: """Unpack leveler text fields from oracle text string.""" return CardTextPatterns.LEVELER.match(self.oracle_text) @@ -1159,7 +1210,9 @@ def bottom_text(self) -> str: class SagaLayout(NormalLayout): """Saga card layout, introduced in Dominaria.""" - card_class: str = LayoutType.Saga + @cached_property + def card_class(self) -> str: + return LayoutType.Saga _chapter_regex = re.compile(r"^[ ,IVXLCDM]+ — ") @@ -1180,11 +1233,11 @@ def is_transform(self) -> bool: def saga_text(self) -> str: """Text comprised of Saga ability lines.""" return "\n".join( - ( + text for text in self.oracle_text.splitlines() if self._chapter_regex.match(text) - ) + ) @cached_property @@ -1212,9 +1265,9 @@ def saga_description(self) -> str: return "" if self._chapter_regex.match(line) else line @cached_property - def saga_lines(self) -> list[dict]: + def saga_lines(self) -> list[SagaLine]: """Unpack saga text into list of dictionaries containing ability text and icons.""" - abilities: list[dict] = [] + abilities: list[SagaLine] = [] for i, line in enumerate(self.saga_text.split("\n")): # Full ability line if "—" in line: @@ -1238,7 +1291,9 @@ def saga_lines(self) -> list[dict]: class ClassLayout(NormalLayout): """Class card layout, introduced in Adventures in the Forgotten Realms.""" - card_class: str = LayoutType.Class + @cached_property + def card_class(self) -> str: + return LayoutType.Class """ * Class Properties @@ -1259,12 +1314,12 @@ def class_description(self) -> str: return "" if CFG.remove_reminder else get_line(self.oracle_text, 0) @cached_property - def class_lines(self) -> list[dict]: + def class_lines(self) -> list[ClassLine]: """Unpack class text into list of dictionaries containing ability levels, cost, and text.""" # Initial class ability initial, *lines = self.class_text.split('\n') - abilities: list[dict] = [{'text': initial, 'cost': None, 'level': 1}] + abilities: list[ClassLine] = [{'text': initial, 'cost': None, 'level': "1"}] # Add level-up abilities for line in ["\n".join(lines[i:i + 2]) for i in range(0, len(lines), 2)]: @@ -1284,7 +1339,9 @@ def class_lines(self) -> list[dict]: class CaseLayout(NormalLayout): """Case card layout, introduced in Murders at Karlov Manor.""" - card_class: str = LayoutType.Case + @cached_property + def card_class(self) -> str: + return LayoutType.Case @cached_property def case_lines(self) -> list[str]: @@ -1294,7 +1351,9 @@ def case_lines(self) -> list[str]: class BattleLayout(TransformLayout): """Battle card layout, introduced in March of the Machine.""" - card_class: str = LayoutType.Battle + @cached_property + def card_class(self) -> str: + return LayoutType.Battle """ * Text Info @@ -1308,23 +1367,53 @@ def defense(self) -> str: class PlanarLayout(NormalLayout): """Planar card layout, introduced in Planechase block.""" - card_class: str = LayoutType.Planar + @cached_property + def card_class(self) -> str: + return LayoutType.Planar class SplitLayout(NormalLayout): """Split card layout, introduced in Invasion.""" - card_class: str = LayoutType.Split + @cached_property + def card_class(self) -> str: + return LayoutType.Split # Static properties - is_nyx: bool = False - is_land: bool = False - is_basic_land: bool = False - is_artifact: bool = False - is_creature: bool = False - is_legendary: bool = False - is_companion: bool = False - toughness: str = '' - power: str = '' + @cached_property + def is_nyx(self) -> bool: + return False + + @cached_property + def is_land(self) -> bool: + return False + + @cached_property + def is_basic_land(self) -> bool: + return False + + @cached_property + def is_artifact(self) -> bool: + return False + + @cached_property + def is_creature(self) -> bool: + return False + + @cached_property + def is_legendary(self) -> bool: + return False + + @cached_property + def is_companion(self) -> bool: + return False + + @cached_property + def toughness(self) -> str: + return "" + + @cached_property + def power(self) -> str: + return "" def __str__(self): return (f"{self.display_name}" @@ -1350,11 +1439,11 @@ def display_name(self) -> str: return f"{self.names[0]} // {self.names[1]}" @cached_property - def card(self) -> dict: + def card(self) -> dict[str,Any]: return self.cards[0] @cached_property - def cards(self) -> list[dict]: + def cards(self) -> list[dict[str,Any]]: """Both side objects.""" return [*self.scryfall.get('card_faces', [])] @@ -1381,26 +1470,6 @@ def scryfall_scan(self) -> str: """Scryfall large image scan, if available.""" return self.scryfall.get('image_uris', {}).get('large', '') - """ - * Collector Info - """ - - @cached_property - def artist(self) -> str: - """Card artist name, use Scryfall raw data instead of 'card' data.""" - if self.file.get('artist'): - return self.file['artist'] - - # Check for duplicate last names - artist, count = self.scryfall.get('artist', 'Unknown'), [] - if '&' in artist: - for w in artist.split(' '): - if w in count: - count.remove(w) - count.append(w) - return ' '.join(count) - return artist - """ * Symbols """ @@ -1412,7 +1481,7 @@ def watermark(self) -> str | None: @cached_property def watermarks(self) -> list[str | None]: """Name of the card's watermark file that is actually used, if provided.""" - watermarks: list[Optional[str]] = [] + watermarks: list[str | None] = [] for wm in self.watermark_svgs: if not wm: watermarks.append(None) @@ -1438,7 +1507,7 @@ def watermark_svg(self) -> Path | None: @cached_property def watermark_svgs(self) -> list[Path | None]: """Path to the watermark SVG file, if provided.""" - def _find_watermark_svg(wm: str) -> Optional[Path]: + def _find_watermark_svg(wm: str) -> Path | None: """Try to find a watermark SVG asset, allowing for special cases and set code fallbacks. Args: @@ -1464,7 +1533,7 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: return get_watermark_svg(wm) # Find a watermark SVG for each face - watermarks = [] + watermarks: list[Path | None] = [] for w in self.watermarks_raw: if CFG.watermark_mode == WatermarkMode.Disabled: # Disabled Mode @@ -1478,7 +1547,7 @@ def _find_watermark_svg(wm: str) -> Optional[Path]: continue else: # Automatic Mode - path = _find_watermark_svg(w) + path = _find_watermark_svg(w) if w else None if path or CFG.watermark_mode == WatermarkMode.Automatic: watermarks.append(path) continue @@ -1535,7 +1604,7 @@ def oracle_text(self) -> str: @cached_property def oracle_texts(self) -> list[str]: """Both side oracle texts.""" - text = [] + text: list[str] = [] for t in [ c.get('printed_text', c.get('oracle_text', '')) if self.is_alt_lang else c.get('oracle_text', '') @@ -1646,7 +1715,7 @@ def identities(self) -> list[str]: class TokenLayout(NormalLayout): """Token card layout for token game pieces.""" - @property + @cached_property def display_name(self) -> str: """str: Add Token for display on GUI.""" return f"{self.name} Token" @@ -1670,7 +1739,7 @@ def set(self) -> str: return self.set_data.get('code_parent', super().set).upper() @cached_property - def card_count(self) -> Optional[int]: + def card_count(self) -> int | None: """Optional[int]: Use token count for token cards.""" # Skip if collector mode doesn't require it or if collector number is bad @@ -1690,7 +1759,9 @@ class StationDetails(TypedDict): class StationLayout(NormalLayout): _pt_pattern = re.compile(r"([0-9]+)/([0-9]+)") - card_class: str = LayoutType.Station + @cached_property + def card_class(self) -> str: + return LayoutType.Station @cached_property def oracle_text_unprocessed(self) -> str: @@ -1738,30 +1809,30 @@ def stations(self) -> list[StationDetails]: """ """All card layout classes.""" -CardLayout = Union[ - NormalLayout, - TransformLayout, - ModalDoubleFacedLayout, - AdventureLayout, - LevelerLayout, - SagaLayout, - MutateLayout, - PrototypeLayout, - ClassLayout, - SplitLayout, - PlanarLayout, +CardLayout = ( + NormalLayout| + TransformLayout| + ModalDoubleFacedLayout| + AdventureLayout| + LevelerLayout| + SagaLayout| + MutateLayout| + PrototypeLayout| + ClassLayout| + SplitLayout| + PlanarLayout| TokenLayout -] +) """Planeswalker card layout classes.""" -PlaneswalkerLayouts = Union[ - PlaneswalkerLayout, - PlaneswalkerTransformLayout, +PlaneswalkerLayouts = ( + PlaneswalkerLayout| + PlaneswalkerTransformLayout| PlaneswalkerMDFCLayout -] +) """Maps Scryfall layout names to their respective layout class.""" -layout_map: dict[str, Type[CardLayout]] = { +layout_map: dict[str, type[CardLayout]] = { # Definitions supported by Scryfall natively LayoutScryfall.Normal: NormalLayout, diff --git a/src/schema/adobe.py b/src/schema/adobe.py index ab93bd48..d3197a0c 100644 --- a/src/schema/adobe.py +++ b/src/schema/adobe.py @@ -1,11 +1,13 @@ """ * Schema: Photoshop """ + # Standard Library Imports -from typing import Union, Literal +from typing import Literal # Third Party Imports -from omnitils.schema import ArbitrarySchema, Schema +from omnitils.schema import ArbitrarySchema +from pydantic import BaseModel # Local Imports from src.schema.colors import ColorObject, GradientColor @@ -15,8 +17,9 @@ """ -class LayerDimensions(Schema): +class LayerDimensions(BaseModel): """Calculated layer dimension info for a layer.""" + width: int height: int center_x: int @@ -65,9 +68,10 @@ class LayerDimensions(Schema): class EffectBevel(ArbitrarySchema): """Layer Effect: Bevel""" - highlight_color: ColorObject = [255, 255, 255] + + highlight_color: ColorObject = (255, 255, 255) highlight_opacity: float | int = 70 - shadow_color: ColorObject = [0, 0, 0] + shadow_color: ColorObject = (0, 0, 0) shadow_opacity: float | int = 72 global_light: bool = False rotation: float | int = 45 @@ -79,13 +83,15 @@ class EffectBevel(ArbitrarySchema): class EffectColorOverlay(ArbitrarySchema): """Layer Effect: Color Overlay""" - color: ColorObject = [0, 0, 0] + + color: ColorObject = (0, 0, 0) opacity: float | int = 100 class EffectDropShadow(ArbitrarySchema): """Layer Effect: Drop Shadow""" - color: ColorObject = [0, 0, 0] + + color: ColorObject = (0, 0, 0) opacity: float | int = 100 rotation: float | int = 45 distance: float | int = 10 @@ -96,6 +102,7 @@ class EffectDropShadow(ArbitrarySchema): class EffectGradientOverlay(ArbitrarySchema): """Layer Effect: Drop Shadow""" + colors: list[GradientColor] = [] blend_mode: BlendMode = "normal" dither: bool = False @@ -108,17 +115,20 @@ class EffectGradientOverlay(ArbitrarySchema): class EffectStroke(ArbitrarySchema): """Layer Effect: Stroke""" - color: ColorObject = [0, 0, 0] + + color: ColorObject = (0, 0, 0) weight: int | float = 6 opacity: int | float = 100 - style: Literal['in', 'insetFrame', 'out', 'outsetFrame', 'center', 'centeredFrame'] = 'out' + style: Literal[ + "in", "insetFrame", "out", "outsetFrame", "center", "centeredFrame" + ] = "out" # Type: Any layer effect -LayerEffects = Union[ - EffectBevel, - EffectColorOverlay, - EffectDropShadow, - EffectGradientOverlay, - EffectStroke -] +LayerEffects = ( + EffectBevel + | EffectColorOverlay + | EffectDropShadow + | EffectGradientOverlay + | EffectStroke +) diff --git a/src/schema/colors.py b/src/schema/colors.py index 8d2f0a4b..3fd44aec 100644 --- a/src/schema/colors.py +++ b/src/schema/colors.py @@ -1,20 +1,19 @@ """ * Schema: Colors and Gradients """ + # Standard Library Imports +from collections.abc import Sequence from functools import cache -from typing import Any +from typing import Any, TypeGuard, TypedDict # Third Party Imports -from omnitils.schema import Schema, DictSchema, ArbitrarySchema +from omnitils.schema import ArbitrarySchema from photoshop.api import SolidColor, CMYKColor, RGBColor, LabColor, HSBColor -from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler +from pydantic import BaseModel, GetCoreSchemaHandler, GetJsonSchemaHandler from pydantic.json_schema import JsonSchemaValue from pydantic_core import core_schema -from typing_extensions import Annotated - -# Color not evaluated as a SolidColor object -LazyColor = list[int] | str +from typing import Annotated """ @@ -23,37 +22,45 @@ class _SolidColorAnnotation: - @classmethod @cache def get_validation_func(cls): """Lazy loads outside function that validates value.""" from src.helpers.colors import get_color + return get_color @classmethod def __get_pydantic_core_schema__( - cls, - _source_type: Any, - _handler: GetCoreSchemaHandler + cls, _source_type: Any, _handler: GetCoreSchemaHandler ) -> core_schema.CoreSchema: """Provides a Pydantic core schema for a SolidColor object.""" - def validate_from_lazy(value: list[int] | str | SolidColor) -> SolidColor: + def validate_from_lazy(value: ColorObject) -> SolidColor: """Generates a SolidColor object from a provided list or str value.""" _func = cls.get_validation_func() return _func(value) - def serialize_value(obj: SolidColor) -> list[int]: + def serialize_value( + obj: SolidColor, + ) -> tuple[float, float, float] | tuple[float, float, float, float]: """Serializes the SolidColor object to a list.""" if obj.model == RGBColor: - return [obj.rgb.red, obj.rgb.green, obj.rgb.blue] + return (obj.rgb.red, obj.rgb.green, obj.rgb.blue) elif obj.model == CMYKColor: - return [obj.cmyk.cyan, obj.cmyk.magenta, obj.cmyk.yellow, obj.cmyk.black] + return ( + obj.cmyk.cyan, + obj.cmyk.magenta, + obj.cmyk.yellow, + obj.cmyk.black, + ) elif obj.model == LabColor: - return [obj.lab.L, obj.lab.A, obj.lab.B] + return (obj.lab.L, obj.lab.A, obj.lab.B) elif obj.model == HSBColor: - return [obj.hsb.hue, obj.hsb.saturation, obj.hsb.brightness] + return (obj.hsb.hue, obj.hsb.saturation, obj.hsb.brightness) + raise ValueError( + f"Got SolidColor which has an unsupported color model '{obj.model}'" + ) from_list_schema = core_schema.chain_schema( [ @@ -66,8 +73,11 @@ def serialize_value(obj: SolidColor) -> list[int]: return core_schema.json_or_python_schema( json_schema=from_list_schema, python_schema=core_schema.union_schema( - [core_schema.is_instance_schema(SolidColor), from_list_schema]), - serialization=core_schema.plain_serializer_function_ser_schema(serialize_value), + [core_schema.is_instance_schema(SolidColor), from_list_schema] + ), + serialization=core_schema.plain_serializer_function_ser_schema( + serialize_value + ), ) @classmethod @@ -79,14 +89,54 @@ def __get_pydantic_json_schema__( # Generic color object types SolidColorType = Annotated[SolidColor, _SolidColorAnnotation] -ColorObject = list[int] | str | SolidColorType +ColorObject = ( + tuple[float, float, float] + | tuple[float, float, float, float] + | str + | SolidColorType +) class GradientColor(ArbitrarySchema): """Defines a color within a gradient.""" - color: ColorObject = [0, 0, 0] - location: int = 0 - midpoint: int = 50 + + color: ColorObject = (0, 0, 0) + location: float = 0 + midpoint: float = 50 + + +class GradientConfig(TypedDict): + color: ColorObject + location: int | float + midpoint: int | float + + +def _is_num_tuple_of_length( + val: ColorObject | Sequence[ColorObject] | Sequence[GradientConfig], length: int +) -> bool: + return ( + isinstance(val, tuple) + and len(val) == length + and all(isinstance(item, float | int) for item in val) + ) + + +def is_rgb_tuple( + val: ColorObject | Sequence[ColorObject] | Sequence[GradientConfig], +) -> TypeGuard[tuple[float, float, float]]: + return _is_num_tuple_of_length(val, 3) + + +def is_cmyk_tuple( + val: ColorObject | Sequence[ColorObject] | Sequence[GradientConfig], +) -> TypeGuard[tuple[float, float, float, float]]: + return _is_num_tuple_of_length(val, 4) + + +def is_rgb_or_cmyk_tuple( + val: ColorObject | Sequence[ColorObject] | Sequence[GradientConfig], +) -> TypeGuard[tuple[float, float, float] | tuple[float, float, float, float]]: + return is_rgb_tuple(val) or is_cmyk_tuple(val) """ @@ -94,145 +144,143 @@ class GradientColor(ArbitrarySchema): """ -class ManaColors(DictSchema): - """Defines the mana colors for a specific symbol map (inner, outer, hybrid).""" - C: ColorObject = [204, 194, 193] - W: ColorObject = [255, 251, 214] - U: ColorObject = [170, 224, 250] - B: ColorObject = [204, 194, 193] - R: ColorObject = [249, 169, 143] - G: ColorObject = [154, 211, 175] - - def __new__(cls, **data): - d = super().__new__(cls, **data) - d['2'] = d.pop('C', [0, 0, 0]) - return d +mana_colors: dict[str, ColorObject] = { + "2": (204, 194, 193), # same as C + "C": (204, 194, 193), + "W": (255, 251, 214), + "U": (170, 224, 250), + "B": (204, 194, 193), + "R": (249, 169, 143), + "G": (154, 211, 175), +} -class ManaColorsInner(ManaColors): - """Default mana colors for the inside character (black).""" - C: ColorObject = [0, 0, 0] - W: ColorObject = [0, 0, 0] - U: ColorObject = [0, 0, 0] - B: ColorObject = [0, 0, 0] - R: ColorObject = [0, 0, 0] - G: ColorObject = [0, 0, 0] +mana_colors_inner: dict[str, ColorObject] = { + "2": (0, 0, 0), + "C": (0, 0, 0), + "W": (0, 0, 0), + "U": (0, 0, 0), + "B": (0, 0, 0), + "R": (0, 0, 0), + "G": (0, 0, 0), +} -class SymbolColorMap(Schema): +class SymbolColorMap(BaseModel): """Color map schema.""" - primary: ColorObject = [0, 0, 0] - secondary: ColorObject = [255, 255, 255] - colorless: ColorObject = [204, 194, 193] - colors: dict[str, ColorObject] = ManaColors() - hybrid: dict[str, ColorObject] = ManaColors(B=[159, 146, 143]) - colors_inner: dict[str, ColorObject] = ManaColorsInner() - hybrid_inner: dict[str, ColorObject] = ManaColorsInner() + + primary: ColorObject = (0, 0, 0) + secondary: ColorObject = (255, 255, 255) + colorless: ColorObject = (204, 194, 193) + colors: dict[str, ColorObject] = mana_colors.copy() + hybrid: dict[str, ColorObject] = {**mana_colors, "B": (159, 146, 143)} + colors_inner: dict[str, ColorObject] = mana_colors_inner.copy() + hybrid_inner: dict[str, ColorObject] = mana_colors_inner.copy() """ * Layer Color Maps """ - -class ColorMap(DictSchema): - """Defines RGB or CMYK color values mapped to string keys.""" - W: ColorObject = [246, 246, 239] - U: ColorObject = [0, 117, 190] - B: ColorObject = [39, 38, 36] - R: ColorObject = [239, 56, 39] - G: ColorObject = [0, 123, 67] - Gold: ColorObject = [246, 210, 98] - Land: ColorObject = [174, 151, 135] - Artifact: ColorObject = [230, 236, 242] - Colorless: ColorObject = [230, 236, 242] - Vehicle: ColorObject = [77, 45, 5] +color_map: dict[str, ColorObject] = { + "W": (246, 246, 239), + "U": (0, 117, 190), + "B": (39, 38, 36), + "R": (239, 56, 39), + "G": (0, 123, 67), + "Gold": (246, 210, 98), + "Land": (174, 151, 135), + "Artifact": (230, 236, 242), + "Colorless": (230, 236, 242), + "Vehicle": (77, 45, 5), +} +"""Defines RGB or CMYK color values mapped to string keys.""" watermark_color_map = { # Default watermark colors - 'W': [183, 157, 88], - 'U': [140, 172, 197], - 'B': [94, 94, 94], - 'R': [198, 109, 57], - 'G': [89, 140, 82], - 'Gold': [202, 179, 77], - 'Land': [94, 84, 72], - 'Artifact': [100, 125, 134], - 'Colorless': [100, 125, 134] + "W": (183, 157, 88), + "U": (140, 172, 197), + "B": (94, 94, 94), + "R": (198, 109, 57), + "G": (89, 140, 82), + "Gold": (202, 179, 77), + "Land": (94, 84, 72), + "Artifact": (100, 125, 134), + "Colorless": (100, 125, 134), } basic_watermark_color_map = { # Basic land watermark colors - 'W': [248, 249, 243], - 'U': [0, 115, 178], - 'B': [6, 0, 0], - 'R': [212, 39, 44], - 'G': [1, 131, 69], - 'Land': [165, 150, 132], - 'Snow': [255, 255, 255] + "W": (248, 249, 243), + "U": (0, 115, 178), + "B": (6, 0, 0), + "R": (212, 39, 44), + "G": (1, 131, 69), + "Land": (165, 150, 132), + "Snow": (255, 255, 255), } pinlines_color_map = { # Default pinline colors - 'W': [246, 246, 239], - 'U': [0, 117, 190], - 'B': [39, 38, 36], - 'R': [239, 56, 39], - 'G': [0, 123, 67], - 'Gold': [246, 210, 98], - 'Land': [174, 151, 135], - 'Artifact': [230, 236, 242], - 'Colorless': [230, 236, 242], - 'Vehicle': [77, 45, 5] + "W": (246, 246, 239), + "U": (0, 117, 190), + "B": (39, 38, 36), + "R": (239, 56, 39), + "G": (0, 123, 67), + "Gold": (246, 210, 98), + "Land": (174, 151, 135), + "Artifact": (230, 236, 242), + "Colorless": (230, 236, 242), + "Vehicle": (77, 45, 5), } indicator_color_map = { # Default color indicator colors - 'W': [248, 244, 240], - 'U': [0, 109, 174], - 'B': [57, 52, 49], - 'R': [222, 60, 35], - 'G': [0, 109, 66], - 'Artifact': [181, 197, 205], - 'Colorless': [214, 214, 220], - 'Land': [165, 150, 132] + "W": (248, 244, 240), + "U": (0, 109, 174), + "B": (57, 52, 49), + "R": (222, 60, 35), + "G": (0, 109, 66), + "Artifact": (181, 197, 205), + "Colorless": (214, 214, 220), + "Land": (165, 150, 132), } crown_color_map = { # Legendary crown colors - 'W': [248, 244, 240], - 'U': [0, 109, 174], - 'B': [57, 52, 49], - 'R': [222, 60, 35], - 'G': [0, 109, 66], - 'Gold': [239, 209, 107], - 'Land': [165, 150, 132], - 'Artifact': [181, 197, 205], - 'Colorless': [214, 214, 220] + "W": (248, 244, 240), + "U": (0, 109, 174), + "B": (57, 52, 49), + "R": (222, 60, 35), + "G": (0, 109, 66), + "Gold": (239, 209, 107), + "Land": (165, 150, 132), + "Artifact": (181, 197, 205), + "Colorless": (214, 214, 220), } saga_banner_color_map = { # Saga banner colors - 'W': [241, 225, 193], - 'U': [37, 89, 177], - 'B': [59, 51, 48], - 'R': [218, 56, 44], - 'G': [1, 99, 58], - 'Gold': [204, 173, 116], - 'Artifact': [200, 205, 234], - 'Land': [106, 89, 81] + "W": (241, 225, 193), + "U": (37, 89, 177), + "B": (59, 51, 48), + "R": (218, 56, 44), + "G": (1, 99, 58), + "Gold": (204, 173, 116), + "Artifact": (200, 205, 234), + "Land": (106, 89, 81), } saga_stripe_color_map = { # Saga stripe colors - 'W': [235, 209, 156], - 'U': [34, 72, 153], - 'B': [30, 24, 18], - 'R': [197, 41, 30], - 'G': [2, 84, 46], - 'Gold': [135, 107, 45], - 'Artifact': [163, 171, 202], - 'Land': [55, 47, 41], - 'Dual': [42, 42, 42] + "W": (235, 209, 156), + "U": (34, 72, 153), + "B": (30, 24, 18), + "R": (197, 41, 30), + "G": (2, 84, 46), + "Gold": (135, 107, 45), + "Artifact": (163, 171, 202), + "Land": (55, 47, 41), + "Dual": (42, 42, 42), } diff --git a/src/templates/_core.py b/src/templates/_core.py index df0fa284..5f1e5ceb 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -1,61 +1,71 @@ """ * CORE PROXYSHOP TEMPLATES """ + # Standard Library Imports import os.path as osp from contextlib import suppress from functools import cached_property from pathlib import Path from threading import Event -from typing import Optional, Callable, Any, Union, Iterable +from typing import Any, Unpack +from collections.abc import Callable, Sequence + +from omnitils.files import get_unique_filename # Third Party Imports from pathvalidate import sanitize_filename +from photoshop.api import BlendMode, ElementPlacement, SaveOptions, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection -from photoshop.api import ( - ElementPlacement, - SaveOptions, - SolidColor, - BlendMode) from PIL import Image -from omnitils.files import get_unique_filename + +from src.gui.console import GUIConsole +import src.helpers as psd # Local Imports -from src import APP, CON, CONSOLE, CFG, ENV, PATH -from src.utils.scryfall import get_card_scan +from src import APP, CFG, CON, CONSOLE, ENV, PATH from src.cards import strip_reminder_text -from src.console import msg_error, msg_warn -from src.enums.mtg import MagicIcons -from src.enums.adobe import Dimensions +from src.console import TerminalConsole, msg_error, msg_warn from src.enums.layers import LAYERS +from src.enums.mtg import CardTextPatterns, MagicIcons from src.enums.settings import ( + BorderColor, CollectorMode, - OutputFileType, CollectorPromo, + OutputFileType, WatermarkMode, - BorderColor) -from src.enums.mtg import CardTextPatterns +) from src.frame_logic import is_multicolor_string -import src.helpers as psd +from src.helpers.adjustments import CreateColorLayerKwargs +from src.schema.colors import GradientConfig, is_rgb_or_cmyk_tuple from src.helpers.effects import LayerEffects -from src.schema.adobe import EffectColorOverlay, EffectGradientOverlay, EffectBevel -from src.schema.colors import watermark_color_map, basic_watermark_color_map, ColorObject, GradientColor +from src.helpers.position import DimensionNames +from src.layouts import NormalLayout, SplitLayout +from src.schema.adobe import EffectBevel, EffectColorOverlay, EffectGradientOverlay +from src.schema.colors import ( + ColorObject, + GradientColor, + basic_watermark_color_map, + watermark_color_map, +) from src.text_layers import ( - TextField, - ScaledTextField, FormattedTextArea, FormattedTextField, - FormattedTextLayer) + FormattedTextLayer, + ScaledTextField, + TextField, +) from src.utils.adobe import ( - get_photoshop_error_message, - LayerContainer, - PhotoshopHandler, PS_EXCEPTIONS, + PhotoshopHandler, ReferenceLayer, - try_photoshop) + get_photoshop_error_message, + try_photoshop, +) +from src.utils.scryfall import get_card_scan from src.utils.windows import WindowState """ @@ -70,30 +80,29 @@ class BaseTemplate: - Contains all the core architecture that is required for any template to function in Proxyshop, as well as a ton of optional built-in utility properties and methods for building templates. """ - frame_suffix = 'Normal' - template_suffix = '' - def __init__(self, layout: Any, **kwargs): + frame_suffix = "Normal" + template_suffix = "" + def __init__(self, layout: NormalLayout): # Setup manual properties self.layout = layout - self._text = [] + self._text: list[FormattedTextLayer] = [] """ * Template Class Routing """ - def __new__(cls, layout, **kwargs) -> 'BaseTemplate': + def __new__(cls, layout: NormalLayout) -> "BaseTemplate": """Governs which class is called to instantiate a new object.""" - return cls.get_template_route(layout, **kwargs) + return cls.get_template_route(layout) @staticmethod def redirect_template( - template_class: type['BaseTemplate'], - template_file: Union[str, Path], - layout, - **kwargs - ) -> 'BaseTemplate': + template_class: type["BaseTemplate"], + template_file: str | Path, + layout: NormalLayout, + ) -> "BaseTemplate": """Reroutes template initialization to another template class and PSD file. Args: @@ -106,12 +115,12 @@ def redirect_template( """ if isinstance(template_file, Path): layout.template_file = template_file - elif isinstance(template_file, str): + else: layout.template_file = layout.template_file.with_name(template_file) - return template_class(layout, **kwargs) + return template_class(layout) @classmethod - def get_template_route(cls, layout, **kwargs) -> 'BaseTemplate': + def get_template_route(cls, layout: NormalLayout) -> "BaseTemplate": """Overwrite this method to reroute a template class to another class under a set of conditions. See the 'IxalanTemplate' class for an example. @@ -127,8 +136,8 @@ def get_template_route(cls, layout, **kwargs) -> 'BaseTemplate': * Enabled Method Lists """ - @property - def pre_render_methods(self) -> list[Callable]: + @cached_property + def pre_render_methods(self) -> list[Callable[[], None]]: """list[Callable]: Methods called before rendering begins. Methods: @@ -136,8 +145,8 @@ def pre_render_methods(self) -> list[Callable]: """ return [self.process_layout_data] - @property - def frame_layer_methods(self) -> list[Callable]: + @cached_property + def frame_layer_methods(self) -> list[Callable[[], None]]: """list[Callable]: Methods called to insert and enable frame layers. Methods: @@ -146,22 +155,22 @@ def frame_layer_methods(self) -> list[Callable]: """ return [self.color_border, self.enable_frame_layers] - @property - def text_layer_methods(self) -> list[Callable]: + @cached_property + def text_layer_methods(self) -> list[Callable[[], None]]: """list[Callable]: Methods called to insert and format text layers.""" return [ self.collector_info, self.basic_text_layers, - self.rules_text_and_pt_layers + self.rules_text_and_pt_layers, ] - @property - def post_text_methods(self) -> list[Callable]: + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: """list[Callable]: Methods called after text is inserted and formatted.""" return [] - @property - def post_save_methods(self) -> list[Callable]: + @cached_property + def post_save_methods(self) -> list[Callable[[], None]]: """list[Callable]: Methods called after the rendered image is saved.""" return [] @@ -169,14 +178,14 @@ def post_save_methods(self) -> list[Callable]: * Hook Method List """ - @property - def hooks(self) -> list[Callable]: + @cached_property + def hooks(self) -> list[Callable[[], None]]: """list[Callable]: List of methods that will be called during the hooks execution step""" - hooks = [] + hooks: list[Callable[[], None]] = [] if self.is_creature: # Creature hook hooks.append(self.hook_creature) - if 'P' in self.layout.mana_cost or '/' in self.layout.mana_cost: + if "P" in self.layout.mana_cost or "/" in self.layout.mana_cost: # Large mana symbol hook hooks.append(self.hook_large_mana) return hooks @@ -199,7 +208,7 @@ def event(self) -> Event: return Event() @cached_property - def console(self) -> type[CONSOLE]: + def console(self) -> GUIConsole | TerminalConsole: """type[CONSOLE]: Console output object used to communicate with the user.""" return CONSOLE @@ -209,11 +218,11 @@ def app(self) -> PhotoshopHandler: return APP @cached_property - def docref(self) -> Optional[Document]: + def docref(self) -> Document: """Optional[Document]: This template's document open in Photoshop.""" if doc := psd.get_document(osp.basename(self.layout.template_file)): return doc - return + raise ValueError("Failed to get document reference for the template.") @cached_property def doc_selection(self) -> Selection: @@ -221,12 +230,12 @@ def doc_selection(self) -> Selection: return self.docref.selection @property - def active_layer(self) -> Union[ArtLayer, LayerSet]: + def active_layer(self) -> ArtLayer | LayerSet: """Union[ArtLayer, LayerSet]: Get the currently active layer in the Photoshop document.""" return self.docref.activeLayer @active_layer.setter - def active_layer(self, value: Union[ArtLayer, LayerSet]): + def active_layer(self, value: ArtLayer | LayerSet): """Set the currently active layer in the Photoshop document. Args: @@ -253,7 +262,7 @@ def RGB_WHITE(self) -> SolidColor: """ @cached_property - def save_mode(self) -> Callable: + def save_mode(self) -> Callable[[Path, Document | None], None]: """Callable: Function called to save the rendered image.""" if CFG.output_file_type == OutputFileType.PNG: return psd.save_document_png @@ -263,9 +272,9 @@ def save_mode(self) -> Callable: @cached_property def output_directory(self) -> Path: - """PathL Directory to save the rendered image.""" + """Path: Directory where the rendered image will be saved to.""" if ENV.TEST_MODE: - path = PATH.OUT / self.__class__.__name__ + path = PATH.OUT / "test_mode_renders" / self.__class__.__name__ path.mkdir(mode=777, parents=True, exist_ok=True) return path return PATH.OUT @@ -273,23 +282,26 @@ def output_directory(self) -> Path: @cached_property def output_file_name(self) -> Path: """Path: The formatted filename for the rendered image.""" - name, tag_map = CFG.output_file_name, { - '#name': self.layout.name_raw, - '#artist': self.layout.artist, - '#set': self.layout.set, - '#num': str(self.layout.collector_number), - '#frame': self.frame_suffix, - '#suffix': self.template_suffix, - '#creator': self.layout.creator, - '#lang': self.layout.lang - } + name, tag_map = ( + CFG.output_file_name, + { + "#name": self.layout.name_raw, + "#artist": self.layout.artist, + "#set": self.layout.set, + "#num": str(self.layout.collector_number), + "#frame": self.frame_suffix, + "#suffix": self.template_suffix, + "#creator": self.layout.creator, + "#lang": self.layout.lang, + }, + ) # Replace conditional tags for n in CardTextPatterns.PATH_CONDITION.findall(name): - case_new, case = n, f'<{n}>' + case_new, case = n, f"<{n}>" for tag, val in tag_map.items(): if tag in case and not val: - name, case_new = name.replace(case, ''), '' + name, case_new = name.replace(case, ""), "" break if tag in case: case_new = case_new.replace(tag, val) @@ -300,10 +312,9 @@ def output_file_name(self) -> Path: for tag, value in tag_map.items(): if value: name = name.replace(tag, value) - path = Path( - self.output_directory, - sanitize_filename(name) - ).with_suffix(f'.{CFG.output_file_type}') + path = Path(self.output_directory, sanitize_filename(name)).with_suffix( + f".{CFG.output_file_type}" + ) # Are we overwriting duplicate names? if not CFG.overwrite_duplicate: @@ -314,12 +325,12 @@ def output_file_name(self) -> Path: * Cosmetic Extendable Checks """ - @property + @cached_property def is_hollow_crown(self) -> bool: """bool: Governs whether a hollow crown should be rendered.""" return False - @property + @cached_property def is_fullart(self) -> bool: """bool: Returns True if art must be treated as Fullart.""" return False @@ -328,82 +339,82 @@ def is_fullart(self) -> bool: * Boolean Checks """ - @property + @cached_property def is_creature(self) -> bool: """bool: Governs whether to add PT box and use Creature rules text.""" return self.layout.is_creature - @property + @cached_property def is_legendary(self) -> bool: """bool: Enables the legendary crown step.""" return self.layout.is_legendary - @property + @cached_property def is_land(self) -> bool: """bool: Governs whether to use normal or land pinlines group.""" return self.layout.is_land - @property + @cached_property def is_artifact(self) -> bool: """bool: Utility definition for custom templates. Returns True if card is an Artifact.""" return self.layout.is_artifact - @property + @cached_property def is_vehicle(self) -> bool: """bool: Utility definition for custom templates. Returns True if card is a Vehicle.""" return self.layout.is_vehicle - @property + @cached_property def is_hybrid(self) -> bool: """bool: Utility definition for custom templates. Returns True if card is hybrid color.""" return self.layout.is_hybrid - @property + @cached_property def is_colorless(self) -> bool: """bool: Enforces fullart framing for card art on many templates.""" return self.layout.is_colorless - @property + @cached_property def is_front(self) -> bool: """bool: Governs render behavior on MDFC and Transform cards.""" return self.layout.is_front - @property + @cached_property def is_transform(self) -> bool: """bool: Governs behavior on double faced card varieties.""" return self.layout.is_transform - @property + @cached_property def is_mdfc(self) -> bool: """bool: Governs behavior on double faced card varieties.""" return self.layout.is_mdfc - @property + @cached_property def is_companion(self) -> bool: """bool: Enables companion cosmetic elements.""" return self.layout.is_companion - @property + @cached_property def is_nyx(self) -> bool: """bool: Enables nyxtouched cosmetic elements.""" return self.layout.is_nyx - @property + @cached_property def is_snow(self) -> bool: """bool: Enables snow cosmetic elements.""" return self.layout.is_snow - @property + @cached_property def is_miracle(self) -> bool: """bool: Enables miracle cosmetic elements.""" return self.layout.is_miracle - @property + @cached_property def is_token(self) -> bool: """bool: Enables token cosmetic elements.""" return self.layout.is_token - @property + @cached_property def is_emblem(self) -> bool: """bool: Enables emblem cosmetic elements.""" return self.layout.is_emblem @@ -424,7 +435,8 @@ def is_centered(self) -> bool: return bool( len(self.layout.flavor_text) <= 1 and len(self.layout.oracle_text) <= 70 - and "\n" not in self.layout.oracle_text) + and "\n" not in self.layout.oracle_text + ) @cached_property def is_name_shifted(self) -> bool: @@ -455,7 +467,10 @@ def is_art_vertical(self) -> bool: @cached_property def is_content_aware_enabled(self) -> bool: """bool: Governs whether content aware fill should be performed during the art loading step.""" - if self.is_fullart and all([n not in self.art_reference.name for n in ['Full', 'Borderless']]): + if self.is_fullart and ( + not self.art_reference + or all([n not in self.art_reference.name for n in ["Full", "Borderless"]]) + ): # By default, fill when we want a fullart image but didn't receive one return True return False @@ -473,12 +488,12 @@ def is_collector_promo(self) -> bool: * Frame Details """ - @property + @cached_property def art_frame(self) -> str: """str: Normal frame to use for positioning the art.""" return LAYERS.ART_FRAME - @property + @cached_property def art_frame_vertical(self) -> str: """str: Vertical orientation frame to use for positioning the art.""" return LAYERS.FULL_ART_FRAME @@ -520,33 +535,29 @@ def legal_group(self) -> LayerSet: return self.docref.layerSets[LAYERS.LEGAL] @cached_property - def border_group(self) -> Optional[Union[LayerSet, ArtLayer]]: + def border_group(self) -> LayerSet | None: """Optional[Union[LayerSet, ArtLayer]]: Group, or sometimes a layer, containing the card border.""" if group := psd.getLayerSet(LAYERS.BORDER, self.docref): return group - if layer := psd.getLayer(LAYERS.BORDER, self.docref): - return layer return @cached_property - def text_group(self) -> Optional[LayerSet]: + def text_group(self) -> LayerSet | None: """Optional[LayerSet]: Text and icon group, contains rules text and necessary symbols.""" - with suppress(Exception): - return self.docref.layerSets[LAYERS.TEXT_AND_ICONS] - return self.docref + return psd.getLayerSet(LAYERS.TEXT_AND_ICONS) @cached_property - def dfc_group(self) -> Optional[LayerSet]: + def dfc_group(self) -> LayerSet | None: """Optional[LayerSet]: Group containing double face elements.""" face = LAYERS.FRONT if self.is_front else LAYERS.BACK if self.is_transform: return psd.getLayerSet(face, [self.text_group, LAYERS.TRANSFORM]) if self.is_mdfc: - return psd.getLayerSet(f'{LAYERS.MDFC} {face}', self.text_group) + return psd.getLayerSet(f"{LAYERS.MDFC} {face}", self.text_group) return @cached_property - def mask_group(self) -> Optional[LayerSet]: + def mask_group(self) -> LayerSet | None: """LayerSet: Group containing masks used to blend and adjust various layers.""" with suppress(Exception): return self.docref.layerSets[LAYERS.MASKS] @@ -557,52 +568,54 @@ def mask_group(self) -> Optional[LayerSet]: """ @cached_property - def text_layer_creator(self) -> Optional[ArtLayer]: + def text_layer_creator(self) -> ArtLayer | None: """Optional[ArtLayer]: Proxy creator name text layer.""" return psd.getLayer(LAYERS.CREATOR, self.legal_group) @cached_property - def text_layer_name(self) -> Optional[ArtLayer]: + def text_layer_name(self) -> ArtLayer | None: """Optional[ArtLayer]: Card name text layer.""" - if self.is_name_shifted: - psd.getLayer(LAYERS.NAME, self.text_group).visible = False - name = psd.getLayer(LAYERS.NAME_SHIFT, self.text_group) - name.visible = True - return name - return psd.getLayer(LAYERS.NAME, self.text_group) + layer = psd.getLayer(LAYERS.NAME, self.text_group) + if layer and self.is_name_shifted: + layer.visible = False + if shift_layer := psd.getLayer(LAYERS.NAME_SHIFT, self.text_group): + shift_layer.visible = True + return shift_layer + return layer @cached_property - def text_layer_mana(self) -> Optional[ArtLayer]: + def text_layer_mana(self) -> ArtLayer | None: """Optional[ArtLayer]: Card mana cost text layer.""" return psd.getLayer(LAYERS.MANA_COST, self.text_group) @cached_property - def text_layer_type(self) -> Optional[ArtLayer]: + def text_layer_type(self) -> ArtLayer | None: """Optional[ArtLayer]: Card typeline text layer.""" - if self.is_type_shifted: - psd.getLayer(LAYERS.TYPE_LINE, self.text_group).visible = False - typeline = psd.getLayer(LAYERS.TYPE_LINE_SHIFT, self.text_group) - typeline.visible = True + layer = psd.getLayer(LAYERS.TYPE_LINE, self.text_group) + if layer and self.is_type_shifted: + layer.visible = False + if typeline := psd.getLayer(LAYERS.TYPE_LINE_SHIFT, self.text_group): + typeline.visible = True return typeline return psd.getLayer(LAYERS.TYPE_LINE, self.text_group) @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """Optional[ArtLayer]: Card rules text layer.""" if self.is_creature: return psd.getLayer(LAYERS.RULES_TEXT_CREATURE, self.text_group) return psd.getLayer(LAYERS.RULES_TEXT_NONCREATURE, self.text_group) @cached_property - def text_layer_pt(self) -> Optional[ArtLayer]: + def text_layer_pt(self) -> ArtLayer | None: """Optional[ArtLayer]: Card power and toughness text layer.""" return psd.getLayer(LAYERS.POWER_TOUGHNESS, self.text_group) @cached_property - def divider_layer(self) -> Optional[ArtLayer]: + def divider_layer(self) -> ArtLayer | LayerSet | None: """Optional[ArtLayer]: Divider layer between rules text and flavor text.""" if self.is_transform and self.is_front and self.is_flipside_creature: - if TF_DIVIDER := psd.getLayer('Divider TF', self.text_group): + if TF_DIVIDER := psd.getLayer("Divider TF", self.text_group): return TF_DIVIDER return psd.getLayer(LAYERS.DIVIDER, self.text_group) @@ -611,64 +624,64 @@ def divider_layer(self) -> Optional[ArtLayer]: """ @property - def art_layer(self) -> ArtLayer: + def art_layer(self) -> ArtLayer | None: """ArtLayer: Layer the art image is imported into.""" return psd.getLayer(LAYERS.DEFAULT, self.docref) @cached_property - def twins_layer(self) -> Optional[ArtLayer]: + def twins_layer(self) -> ArtLayer | None: """Name and title boxes layer.""" return psd.getLayer(self.twins, LAYERS.TWINS) @cached_property - def pinlines_layer(self) -> Optional[ArtLayer]: + def pinlines_layer(self) -> ArtLayer | None: """Pinlines (and textbox) layer.""" if self.is_land: return psd.getLayer(self.pinlines, LAYERS.LAND_PINLINES_TEXTBOX) return psd.getLayer(self.pinlines, LAYERS.PINLINES_TEXTBOX) @cached_property - def background_layer(self) -> Optional[ArtLayer]: + def background_layer(self) -> ArtLayer | None: """Background texture layer.""" # Try finding Vehicle background if self.is_vehicle and self.background == LAYERS.VEHICLE: - return psd.getLayer( - LAYERS.VEHICLE, LAYERS.BACKGROUND - ) or psd.getLayer( - LAYERS.ARTIFACT, LAYERS.BACKGROUND) + return psd.getLayer(LAYERS.VEHICLE, LAYERS.BACKGROUND) or psd.getLayer( + LAYERS.ARTIFACT, LAYERS.BACKGROUND + ) # All other backgrounds return psd.getLayer(self.background, LAYERS.BACKGROUND) @cached_property - def pt_layer(self) -> Optional[ArtLayer]: + def pt_layer(self) -> ArtLayer | None: """Power and toughness box layer.""" # Test for Vehicle PT support if self.is_vehicle and self.background == LAYERS.VEHICLE: if layer := psd.getLayer(LAYERS.VEHICLE, LAYERS.PT_BOX): # Change font to white for Vehicle PT box - self.text_layer_pt.textItem.color = self.RGB_WHITE + if self.text_layer_pt: + self.text_layer_pt.textItem.color = self.RGB_WHITE return layer return psd.getLayer(self.twins, LAYERS.PT_BOX) @cached_property - def crown_layer(self) -> Optional[ArtLayer]: + def crown_layer(self) -> ArtLayer | None: """Legendary crown layer.""" return psd.getLayer(self.pinlines, LAYERS.LEGENDARY_CROWN) @cached_property - def crown_shadow_layer(self) -> Union[ArtLayer, LayerSet, None]: + def crown_shadow_layer(self) -> ArtLayer | LayerSet | None: """Legendary crown hollow shadow layer.""" return psd.getLayer(LAYERS.HOLLOW_CROWN_SHADOW, self.docref) @cached_property - def color_indicator_layer(self) -> Optional[ArtLayer]: + def color_indicator_layer(self) -> ArtLayer | None: """Color indicator icon layer.""" if self.layout.color_indicator: return psd.getLayer(self.layout.color_indicator, LAYERS.COLOR_INDICATOR) return @cached_property - def transform_icon_layer(self) -> Optional[ArtLayer]: + def transform_icon_layer(self) -> ArtLayer | None: """Optional[ArtLayer]: Transform icon layer.""" return psd.getLayer(self.layout.transform_icon, self.dfc_group) @@ -677,24 +690,26 @@ def transform_icon_layer(self) -> Optional[ArtLayer]: """ @cached_property - def art_reference(self) -> ReferenceLayer: + def art_reference(self) -> ReferenceLayer | None: """ReferenceLayer: Reference frame used to scale and position the art layer.""" # Check if art is vertically oriented, or forced vertical, and for valid vertical frame if self.is_art_vertical or (self.is_fullart and CFG.vertical_fullart): if layer := psd.get_reference_layer(self.art_frame_vertical): return layer # Check for normal art frame - return psd.get_reference_layer(self.art_frame) or psd.get_reference_layer(LAYERS.ART_FRAME) + return psd.get_reference_layer(self.art_frame) or psd.get_reference_layer( + LAYERS.ART_FRAME + ) @cached_property - def name_reference(self) -> Optional[ArtLayer]: + def name_reference(self) -> ArtLayer | None: """ArtLayer: By default, name uses Mana Cost as a reference to check collision against.""" if self.is_basic_land: return return self.text_layer_mana @cached_property - def type_reference(self) -> Optional[ArtLayer]: + def type_reference(self) -> ArtLayer | None: """ArtLayer: By default, typeline uses the expansion symbol to check collision against, otherwise fallback to the expansion symbols reference layer.""" if self.is_basic_land: @@ -702,12 +717,12 @@ def type_reference(self) -> Optional[ArtLayer]: return self.expansion_symbol_layer or self.expansion_reference @cached_property - def textbox_reference(self) -> ReferenceLayer: + def textbox_reference(self) -> ReferenceLayer | None: """ReferenceLayer: Reference frame used to scale and position the rules text layer.""" return psd.get_reference_layer(LAYERS.TEXTBOX_REFERENCE, self.text_group) @cached_property - def pt_reference(self) -> Optional[ReferenceLayer]: + def pt_reference(self) -> ReferenceLayer | None: """ArtLayer: Reference used to check rules text overlap with the PT Box.""" if not self.is_creature: return @@ -735,37 +750,34 @@ def process_layout_data(self) -> None: # Strip flavor text, string or list if CFG.remove_flavor: - self.layout.flavor_text = "" if isinstance( - self.layout.flavor_text, str - ) else ['' for _ in self.layout.flavor_text] + if isinstance(self.layout, SplitLayout): + self.layout.flavor_texts = ["" for _ in self.layout.flavor_texts] + else: + self.layout.flavor_text = "" # Strip reminder text, string or list if CFG.remove_reminder: - self.layout.oracle_text = strip_reminder_text( - self.layout.oracle_text - ) if isinstance( - self.layout.oracle_text, str - ) else [strip_reminder_text(n) for n in self.layout.oracle_text] + if isinstance(self.layout, SplitLayout): + self.layout.oracle_texts = [ + strip_reminder_text(txt) for txt in self.layout.oracle_texts + ] + else: + self.layout.oracle_text = strip_reminder_text(self.layout.oracle_text) """ * Loading Artwork """ - @property - def art_action(self) -> Optional[Callable]: + @cached_property + def art_action(self) -> Callable[[], None] | None: """Function that is called to perform an action on the imported art.""" return - @property - def art_action_args(self) -> Optional[dict]: - """Args to pass to art_action.""" - return - def load_artwork( self, - art_file: Optional[str | Path] = None, - art_layer: Optional[ArtLayer] = None, - art_reference: Optional[ReferenceLayer] = None + art_file: str | Path | None = None, + art_layer: ArtLayer | None = None, + art_reference: ReferenceLayer | None = None, ) -> None: """Loads the specified art file into the specified layer. @@ -783,6 +795,9 @@ def load_artwork( art_layer = art_layer or self.art_layer art_reference = art_reference or self.art_reference + if not art_layer: + raise ValueError("Failed to get art layer.") + # Check for full-art test image if ENV.TEST_MODE and self.is_fullart: art_file = PATH.SRC_IMG / "test-fa.jpg" @@ -794,44 +809,42 @@ def load_artwork( layer=art_layer, path=art_file, action=self.art_action, - action_args=self.art_action_args, - docref=self.docref) + docref=self.docref, + ) else: # Use traditional pipeline art_layer = psd.import_art( - layer=art_layer, - path=art_file, - docref=self.docref) - self.active_layer = art_layer + layer=art_layer, path=art_file, docref=self.docref + ) - # Frame the artwork - psd.frame_layer( - layer=art_layer, - ref=art_reference) + if art_reference: + # Frame the artwork + psd.frame_layer(layer=art_layer, ref=art_reference) # Perform content aware fill if needed if self.is_content_aware_enabled: - # Perform a generative fill if CFG.generative_fill: if _doc_generated := psd.generative_fill_edges( layer=art_layer, feather=CFG.feathered_fill, close_doc=bool(not CFG.select_variation), - docref=self.docref + docref=self.docref, ): # Document reference was returned, await user intervention self.console.await_choice( - self.event, msg="Select a Generative Fill variation, then click Continue ...") + self.event, + msg="Select a Generative Fill variation, then click Continue ...", + ) _doc_generated.close(SaveOptions.SaveChanges) return # Perform a content aware fill - psd.content_aware_fill_edges( - layer=art_layer, - feather=CFG.feathered_fill) + psd.content_aware_fill_edges(layer=art_layer, feather=CFG.feathered_fill) - def paste_scryfall_scan(self, rotate: bool = False, visible: bool = True) -> Optional[ArtLayer]: + def paste_scryfall_scan( + self, rotate: bool = False, visible: bool = True + ) -> ArtLayer | None: """Downloads the card's scryfall scan, pastes it into the document next to the active layer, and frames it to fill the given reference layer. @@ -851,9 +864,7 @@ def paste_scryfall_scan(self, rotate: bool = False, visible: bool = True) -> Opt # Paste the scan into a new layer if layer := psd.import_art_into_new_layer( - path=scryfall_scan, - name="Scryfall Reference", - docref=self.docref + path=scryfall_scan, name="Scryfall Reference", docref=self.docref ): # Rotate the layer if necessary if rotate: @@ -862,9 +873,11 @@ def paste_scryfall_scan(self, rotate: bool = False, visible: bool = True) -> Opt # Frame the layer and position it above the art layer bleed = int(self.docref.resolution / 8) dims = psd.get_dimensions_from_bounds( - (bleed, bleed, self.docref.width - bleed, self.docref.height - bleed)) + (bleed, bleed, self.docref.width - bleed, self.docref.height - bleed) + ) psd.frame_layer(layer, dims) - layer.move(self.art_layer, ElementPlacement.PlaceBefore) + if self.art_layer: + layer.move(self.art_layer, ElementPlacement.PlaceBefore) layer.visible = visible return layer @@ -884,9 +897,10 @@ def collector_info(self) -> None: self.text_layer_creator.textItem.contents = self.layout.creator # Which collector info mode? - if CFG.collector_mode in [ - CollectorMode.Default, CollectorMode.Modern - ] and self.layout.collector_data: + if ( + CFG.collector_mode in [CollectorMode.Default, CollectorMode.Modern] + and self.layout.collector_data + ): return self.collector_info_authentic() elif CFG.collector_mode == CollectorMode.ArtistOnly: return self.collector_info_artist_only() @@ -898,84 +912,99 @@ def collector_info_basic(self) -> None: # Collector layers artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group) set_layer = psd.getLayer(LAYERS.SET, self.legal_group) - set_TI = set_layer.textItem + set_TI = set_layer.textItem if set_layer else None # Correct color for non-black border if self.border_color != BorderColor.Black: - set_TI.color = self.RGB_BLACK - artist_layer.textItem.color = self.RGB_BLACK + if set_TI: + set_TI.color = self.RGB_BLACK + if artist_layer: + artist_layer.textItem.color = self.RGB_BLACK # Fill optional collector star - if self.is_collector_promo: + if set_layer and self.is_collector_promo: psd.replace_text(set_layer, "•", MagicIcons.COLLECTOR_STAR) # Fill language, artist, and set - if self.layout.lang != "en": + if set_layer and self.layout.lang != "en": psd.replace_text(set_layer, "EN", self.layout.lang.upper()) - psd.replace_text(artist_layer, "Artist", self.layout.artist) - set_TI.contents = self.layout.set + set_TI.contents + + if artist_layer: + psd.replace_text(artist_layer, "Artist", self.layout.artist) + + if set_TI: + set_TI.contents = self.layout.set + set_TI.contents def collector_info_authentic(self) -> None: """Called to generate realistic collector info.""" # Hide basic layers - psd.getLayer(LAYERS.ARTIST, self.legal_group).visible = False - psd.getLayer(LAYERS.SET, self.legal_group).visible = False + if layer := psd.getLayer(LAYERS.ARTIST, self.legal_group): + layer.visible = False + if layer := psd.getLayer(LAYERS.SET, self.legal_group): + layer.visible = False # Get the collector layers group = psd.getLayerSet(LAYERS.COLLECTOR, self.legal_group) - top = psd.getLayer(LAYERS.TOP, group).textItem - bottom = psd.getLayer(LAYERS.BOTTOM, group) - group.visible = True - - # Correct color for non-black border - if self.border_color != 'black': - top.color = self.RGB_BLACK - bottom.textItem.color = self.RGB_BLACK - - # Fill in language if needed - if self.layout.lang != "en": - psd.replace_text(bottom, "EN", self.layout.lang.upper()) - - # Fill optional collector star - if self.is_collector_promo: - psd.replace_text(bottom, "•", MagicIcons.COLLECTOR_STAR) - - # Apply the collector info - top.contents = self.layout.collector_data - psd.replace_text(bottom, "SET", self.layout.set) - psd.replace_text(bottom, "Artist", self.layout.artist) + if group: + group.visible = True + top = layer.textItem if (layer := psd.getLayer(LAYERS.TOP, group)) else None + bottom = psd.getLayer(LAYERS.BOTTOM, group) + + # Correct color for non-black border + if self.border_color != "black": + if top: + top.color = self.RGB_BLACK + if bottom: + bottom.textItem.color = self.RGB_BLACK + + # Fill in language if needed + if bottom and self.layout.lang != "en": + psd.replace_text(bottom, "EN", self.layout.lang.upper()) + + # Fill optional collector star + if bottom and self.is_collector_promo: + psd.replace_text(bottom, "•", MagicIcons.COLLECTOR_STAR) + + # Apply the collector info + if top: + top.contents = self.layout.collector_data + if bottom: + psd.replace_text(bottom, "SET", self.layout.set) + psd.replace_text(bottom, "Artist", self.layout.artist) def collector_info_artist_only(self) -> None: """Called to generate 'Artist Only' collector info.""" # Collector layers artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group) - psd.getLayer(LAYERS.SET, self.legal_group).visible = False + if layer := psd.getLayer(LAYERS.SET, self.legal_group): + layer.visible = False # Correct color for non-black border - if self.border_color != BorderColor.Black: + if artist_layer and self.border_color != BorderColor.Black: artist_layer.textItem.color = self.RGB_BLACK # Insert artist name - psd.replace_text(artist_layer, "Artist", self.layout.artist) + if artist_layer: + psd.replace_text(artist_layer, "Artist", self.layout.artist) """ * Expansion Symbol """ - @property - def expansion_symbol_alignments(self) -> list[Dimensions]: + @cached_property + def expansion_symbol_alignments(self) -> list[DimensionNames]: """Alignments used for positioning the expansion symbol""" - return [Dimensions.Right, Dimensions.CenterY] + return ["right", "center_y"] @cached_property - def expansion_symbol_layer(self) -> Optional[ArtLayer]: + def expansion_symbol_layer(self) -> ArtLayer | None: """Expansion symbol layer, value set during the `load_expansion_symbol` method.""" return @cached_property - def expansion_reference(self) -> Optional[ArtLayer]: + def expansion_reference(self) -> ArtLayer | None: """Expansion symbol reference layer""" return psd.getLayer(LAYERS.EXPANSION_REFERENCE, self.text_group) @@ -990,26 +1019,27 @@ def load_expansion_symbol(self) -> None: # Try to import the expansion symbol try: - # Import and place the symbol svg = psd.import_svg( path=str(self.layout.symbol_svg), ref=self.expansion_reference, placement=ElementPlacement.PlaceBefore, - docref=self.docref) + docref=self.docref, + ) # Frame the symbol psd.frame_layer_by_height( layer=svg, ref=self.expansion_reference, - alignments=self.expansion_symbol_alignments) + alignments=self.expansion_symbol_alignments, + ) # Rename and reset property - svg.name = 'Expansion Symbol' + svg.name = "Expansion Symbol" self.expansion_symbol_layer = svg except Exception as e: - return self.log('Expansion symbol disabled due to an error.', e) + return self.log("Expansion symbol disabled due to an error.", e) """ * Watermark @@ -1021,53 +1051,54 @@ def watermark_blend_mode(self) -> BlendMode: return BlendMode.ColorBurn @cached_property - def watermark_color_map(self) -> dict: + def watermark_color_map( + self, + ) -> dict[str, tuple[float | int, float | int, float | int]]: """Maps color values for Watermark.""" - return watermark_color_map.copy() + return {**watermark_color_map} @cached_property - def watermark_colors(self) -> list[SolidColor]: + def watermark_colors(self) -> list[ColorObject]: """Colors to use for the Watermark.""" if self.pinlines in self.watermark_color_map: return [self.watermark_color_map.get(self.pinlines, self.RGB_WHITE)] elif len(self.identity) < 3: - return [self.watermark_color_map.get(c, self.RGB_WHITE) for c in self.identity] + return [ + self.watermark_color_map.get(c, self.RGB_WHITE) for c in self.identity + ] return [] @cached_property def watermark_fx(self) -> list[LayerEffects]: """Defines the layer effects to use for the Watermark.""" if len(self.watermark_colors) == 1: - return [EffectColorOverlay( - opacity=100, - color=self.watermark_colors[0] - )] + return [EffectColorOverlay(opacity=100, color=self.watermark_colors[0])] if len(self.watermark_colors) == 2: - return [EffectGradientOverlay( - rotation=0, - colors=[ - GradientColor( - color=self.watermark_colors[0], - location=0, - midpoint=50), - GradientColor( - color=self.watermark_colors[1], - location=4096, - midpoint=50) - ] - )] + return [ + EffectGradientOverlay( + rotation=0, + colors=[ + GradientColor( + color=self.watermark_colors[0], location=0, midpoint=50 + ), + GradientColor( + color=self.watermark_colors[1], location=4096, midpoint=50 + ), + ], + ) + ] return [] def create_watermark(self) -> None: """Builds the watermark.""" # Required values to generate a Watermark - if not all([ - self.layout.watermark_svg, - self.layout.watermark, - self.textbox_reference, - self.watermark_colors, - self.text_group - ]): + if not ( + self.layout.watermark_svg + and self.layout.watermark + and self.textbox_reference + and self.watermark_colors + and self.text_group + ): return # Get watermark custom settings if available @@ -1078,15 +1109,17 @@ def create_watermark(self) -> None: path=self.layout.watermark_svg, ref=self.text_group, placement=ElementPlacement.PlaceAfter, - docref=self.docref) + docref=self.docref, + ) psd.frame_layer( layer=wm, ref=self.textbox_reference.dims, smallest=True, - scale=wm_details.get('scale', 80)) + scale=wm_details.get("scale", 80), + ) # Apply opacity, blending, and effects - wm.opacity = wm_details.get('opacity', CFG.watermark_opacity) + wm.opacity = wm_details.get("opacity", CFG.watermark_opacity) wm.blendMode = self.watermark_blend_mode psd.apply_fx(wm, self.watermark_fx) @@ -1095,9 +1128,11 @@ def create_watermark(self) -> None: """ @cached_property - def basic_watermark_color_map(self) -> dict: + def basic_watermark_color_map( + self, + ) -> dict[str, tuple[float | int, float | int, float | int]]: """Maps color values for Basic Land Watermark.""" - return basic_watermark_color_map.copy() + return {**basic_watermark_color_map} @cached_property def basic_watermark_color(self) -> SolidColor: @@ -1108,9 +1143,7 @@ def basic_watermark_color(self) -> SolidColor: def basic_watermark_fx(self) -> list[LayerEffects]: """Defines the layer effects used on the Basic Land Watermark.""" return [ - EffectColorOverlay( - opacity=100, - color=self.basic_watermark_color), + EffectColorOverlay(opacity=100, color=self.basic_watermark_color), EffectBevel( highlight_opacity=70, shadow_opacity=72, @@ -1118,34 +1151,36 @@ def basic_watermark_fx(self) -> list[LayerEffects]: rotation=45, altitude=22, depth=100, - size=28) + size=28, + ), ] def create_basic_watermark(self) -> None: """Builds a basic land watermark.""" + if self.layout.watermark_basic: + # Generate the watermark + wm = psd.import_svg( + path=self.layout.watermark_basic, + ref=self.text_group, + placement=ElementPlacement.PlaceAfter, + docref=self.docref, + ) + if self.textbox_reference: + psd.frame_layer_by_height( + layer=wm, ref=self.textbox_reference.dims, scale=75 + ) - # Generate the watermark - wm = psd.import_svg( - path=self.layout.watermark_basic, - ref=self.text_group, - placement=ElementPlacement.PlaceAfter, - docref=self.docref) - psd.frame_layer_by_height( - layer=wm, - ref=self.textbox_reference.dims, - scale=75) - - # Add effects - psd.apply_fx(wm, self.basic_watermark_fx) + # Add effects + psd.apply_fx(wm, self.basic_watermark_fx) - # Add snow effects - if self.is_snow: - self.add_basic_watermark_snow_effects(wm) + # Add snow effects + if self.is_snow: + self.add_basic_watermark_snow_effects(wm) - # Remove rules text step - self.rules_text_and_pt_layers = lambda: None - self.layout.oracle_text = '' - self.layout.flavor_text = '' + # Remove rules text step + self.rules_text_and_pt_layers = lambda: None + self.layout.oracle_text = "" + self.layout.flavor_text = "" def add_basic_watermark_snow_effects(self, wm: ArtLayer): """Adds optional snow effects for 'Snow' Basic Land watermarks. @@ -1164,13 +1199,16 @@ def border_color(self) -> str: """Use 'black' unless an alternate color and a valid border group is provided.""" if CFG.border_color != BorderColor.Black and self.border_group: return CFG.border_color - return 'black' + return "black" @try_photoshop def color_border(self) -> None: """Color this card's border based on given setting.""" - if self.border_color != BorderColor.Black: - psd.apply_fx(self.border_group, [EffectColorOverlay(color=psd.get_color(self.border_color))]) + if self.border_group and self.border_color != BorderColor.Black: + psd.apply_fx( + self.border_group, + [EffectColorOverlay(color=psd.get_color(self.border_color))], + ) """ * Formatted Text Layers @@ -1182,7 +1220,7 @@ def text(self) -> list[FormattedTextLayer]: return self._text @text.setter - def text(self, value): + def text(self, value: list[FormattedTextLayer]): """Add text layer to execute.""" self._text = value @@ -1209,8 +1247,9 @@ def check_photoshop(self) -> None: # Connection with Photoshop couldn't be established, try again? if not self.console.await_choice( - self.event, get_photoshop_error_message(check), - end="Hit Continue to try again, or Cancel to end the operation.\n\n" + self.event, + get_photoshop_error_message(check), + end="Hit Continue to try again, or Cancel to end the operation.\n\n", ): # Cancel the operation raise OSError(check) @@ -1229,7 +1268,7 @@ def reset(self) -> None: * Tasks and Logging """ - def log(self, text: str, e: Optional[Exception] = None) -> None: + def log(self, text: str, e: Exception | None = None) -> None: """Writes a message to console if test mode isn't enabled, logs an exception if provided. Args: @@ -1242,12 +1281,10 @@ def log(self, text: str, e: Optional[Exception] = None) -> None: self.console.update(text) def run_tasks( - self, - funcs: list[Callable], - message: str, - warning: bool = False, - args: Union[Iterable[Any], None] = None, - kwargs: Optional[dict] = None, + self, + funcs: list[Callable[[], Any]], + message: str, + warning: bool = False, ) -> bool: """Run a list of functions, checking for thread cancellation and exceptions on each. @@ -1255,17 +1292,11 @@ def run_tasks( funcs: List of functions to perform. message: Error message to raise if exception occurs. warning: Warn the user if True, otherwise raise error. - args: Optional arguments to pass to the func. Empty tuple if not provided. - kwargs: Optional keyword arguments to pass to the func. Empty dict if not provided. Returns: True if tasks completed, False if exception occurs or thread is cancelled. """ - # Default args and kwargs - args = args or () - kwargs = kwargs or {} - # Execute each function for func in funcs: # Check if thread was cancelled @@ -1273,7 +1304,7 @@ def run_tasks( return False try: # Run the task - func(*args, **kwargs) + func() except Exception as e: # Raise error or warning if not warning: @@ -1285,7 +1316,7 @@ def run_tasks( return False return True - def raise_error(self, message: str, error: Optional[Exception] = None) -> None: + def raise_error(self, message: str, error: Exception | None = None) -> None: """Raise an error on the console display. Args: @@ -1295,13 +1326,13 @@ def raise_error(self, message: str, error: Optional[Exception] = None) -> None: self.console.log_error( thr=self.event, card=self.layout.name, - template=self.layout.template_file, - msg=f'{msg_error(message)}\n' - f'Check [b]/logs/error.txt[/b] for details.', - e=error) + template=str(self.layout.template_file), + msg=f"{msg_error(message)}\nCheck [b]/logs/error.txt[/b] for details.", + e=error, + ) self.reset() - def raise_warning(self, message: str, error: Exception = None) -> None: + def raise_warning(self, message: str, error: Exception | None = None) -> None: """Raise a warning on the console display. Args: @@ -1320,11 +1351,11 @@ def raise_warning(self, message: str, error: Exception = None) -> None: """ def create_blended_layer( - self, - group: LayerSet, - colors: Union[None, str, list[str]] = None, - masks: Optional[list[ArtLayer]] = None, - **kwargs + self, + group: LayerSet, + colors: None | str | list[str] = None, + masks: list[ArtLayer] | None = None, + blend_mode: BlendMode | None = None, ): """Either enable a single frame layer or create a multicolor layer using a gradient mask. @@ -1351,33 +1382,34 @@ def create_blended_layer( # Single layer if len(colors) == 1: layer = psd.getLayer(colors[0], group) - layer.visible = True + if layer: + layer.visible = True return # Enable each layer color layers: list[ArtLayer] = [] for i, color in enumerate(colors): - # Make layer visible layer = psd.getLayer(color, group) - if 'blend_mode' in kwargs: - layer.blendMode = kwargs['blend_mode'] - layer.visible = True + if layer: + if blend_mode is not None: + layer.blendMode = blend_mode + layer.visible = True - # Position the new layer and add a mask to previous, if previous layer exists - if layers and len(masks) >= i: - layer.move(layers[i - 1], ElementPlacement.PlaceAfter) - psd.copy_layer_mask(masks[i - 1], layers[i - 1]) + # Position the new layer and add a mask to previous, if previous layer exists + if layers and len(masks) >= i: + layer.move(layers[i - 1], ElementPlacement.PlaceAfter) + psd.copy_layer_mask(masks[i - 1], layers[i - 1]) - # Add to the layer list - layers.append(layer) + # Add to the layer list + layers.append(layer) @staticmethod def create_blended_solid_color( - group: LayerSet, - colors: list[ColorObject], - masks: Optional[list[Union[ArtLayer, LayerSet]]] = None, - **kwargs + group: LayerSet, + colors: Sequence[ColorObject], + masks: Sequence[ArtLayer | LayerSet] | None = None, + blend_mode: BlendMode | None = None, ) -> None: """Either enable a single frame layer or create a multicolor layer using a gradient mask. @@ -1396,8 +1428,8 @@ def create_blended_solid_color( layers: list[ArtLayer] = [] for i, color in enumerate(colors): layer = psd.smart_layer(psd.create_color_layer(color, group)) - if 'blend_mode' in kwargs: - layer.blendMode = kwargs['blend_mode'] + if blend_mode is not None: + layer.blendMode = blend_mode # Position the new layer and add a mask to previous, if previous layer exists if layers and len(masks) >= i: @@ -1406,11 +1438,12 @@ def create_blended_solid_color( layers.append(layer) def generate_layer( - self, group: Union[ArtLayer, LayerSet], - colors: Union[str, ColorObject, list[ColorObject], list[dict]], - masks: Optional[list[ArtLayer]] = None, - **kwargs - ) -> Optional[ArtLayer]: + self, + group: ArtLayer | LayerSet, + colors: ColorObject | Sequence[ColorObject] | Sequence[GradientConfig], + masks: list[ArtLayer] | None = None, + **kwargs: Unpack[CreateColorLayerKwargs], + ) -> ArtLayer | None: """Takes information about a frame layer group and routes it to the correct generation function which blends rasterized layers, blends solid color layers, or generates a solid color/gradient adjustment layer. @@ -1419,11 +1452,11 @@ def generate_layer( The result for a given 'colors' schema: - str: Enable and/or blend one or more texture layers, unless string is a hex color, in which case create a solid color adjustment layer. - - list[str]: Blend multiple texture layers. - - list[int]: Create a solid color adjustment layer. - - list[dict]: Create a gradient adjustment layer. - - list[list[int]]: Blend multiple solid color adjustment layers. - - list[SolidColor]: Blend multiple solid color adjustment layers. + - Sequence[str]: Blend multiple texture layers. + - tuple[float, ...]: Create a solid color adjustment layer. + - Sequence[GradientConfig]: Create a gradient adjustment layer. + - Sequence[tuple[float, ...]]: Blend multiple solid color adjustment layers. + - Sequence[SolidColor]: Blend multiple solid color adjustment layers. Args: group: Layer or group containing layers. @@ -1434,70 +1467,76 @@ def generate_layer( if isinstance(colors, str): # Example: '#FFFFFF' # Single adjustment layer - if colors.startswith('#'): + if colors.startswith("#"): return psd.create_color_layer( - color=colors, - layer=group, - docref=self.docref, - **kwargs) + color=colors, layer=group, docref=self.docref, **kwargs + ) # Example: 'Land' # Single or blended texture layers - return self.create_blended_layer( - group=group, - colors=colors, - masks=masks, - **kwargs) + if isinstance(group, LayerSet): + return self.create_blended_layer( + group=group, + colors=colors, + masks=masks, + blend_mode=kwargs.get("blend_mode", None), + ) elif isinstance(colors, SolidColor): # Example: SolidColor # Single adjustment layer return psd.create_color_layer( - color=colors, - layer=group, - docref=self.docref, - **kwargs + color=colors, layer=group, docref=self.docref, **kwargs ) - elif isinstance(colors, list): - if all(isinstance(c, str) for c in colors): + elif is_rgb_or_cmyk_tuple(colors): + # Example: [r, g, b] + # RGB/CMYK adjustment layer + return psd.create_color_layer( + color=colors, layer=group, docref=self.docref, **kwargs + ) + elif len(colors) > 0: + if all(isinstance(c, str) for c in colors) and isinstance(group, LayerSet): + str_colors = [color for color in colors if isinstance(color, str)] # Example: ['#000000', '#FFFFFF', ...] # Blended RGB/CMYK adjustment layers - if colors[0].startswith('#'): # noqa + if isinstance(colors[0], str) and colors[0].startswith("#"): return self.create_blended_solid_color( group=group, - colors=colors, + colors=str_colors, masks=masks, - **kwargs) + blend_mode=kwargs.get("blend_mode", None), + ) # Example: ['W', 'U'] # Blended texture layers return self.create_blended_layer( group=group, - colors=colors, + colors=str_colors, masks=masks, - **kwargs) - elif all(isinstance(c, int) for c in colors): - # Example: [r, g, b] - # RGB/CMYK adjustment layer - return psd.create_color_layer( - color=colors, - layer=group, - docref=self.docref, - **kwargs) + blend_mode=kwargs.get("blend_mode", None), + ) elif all(isinstance(c, dict) for c in colors): # Example: [GradientColor, GradientColor, ...] # Gradient adjustment layer return psd.create_gradient_layer( - colors=colors, + colors=[color for color in colors if isinstance(color, dict)], layer=group, docref=self.docref, - **kwargs) - elif all(isinstance(c, (list, SolidColor)) for c in colors): + **kwargs, + ) + elif all(isinstance(c, tuple | SolidColor) for c in colors) and isinstance( + group, LayerSet + ): # Example 1: [[r, g, b], [r, g, b], ...] # Example 2: [SolidColor, SolidColor, ...] # Blended RGB/CMYK adjustment layers return self.create_blended_solid_color( group=group, - colors=colors, + colors=[ + color + for color in colors + if isinstance(color, SolidColor | tuple | str) + ], masks=masks, - **kwargs) + blend_mode=kwargs.get("blend_mode", None), + ) # Failed to match a recognized color notation if group: @@ -1542,8 +1581,7 @@ def execute(self) -> bool: """ # Preliminary Photoshop check if not self.run_tasks( - funcs=[self.check_photoshop], - message="Unable to reach Photoshop!" + funcs=[self.check_photoshop], message="Unable to reach Photoshop!" ): return False @@ -1552,23 +1590,20 @@ def execute(self) -> bool: # Pre-process layout data if not self.run_tasks( - funcs=self.pre_render_methods, - message="Pre-processing layout data failed!" + funcs=self.pre_render_methods, message="Pre-processing layout data failed!" ): return False # Load in the PSD template if not self.run_tasks( - funcs=[self.app.load], + funcs=[lambda: self.app.load(self.layout.template_file)], message="PSD template failed to load!", - args=[str(self.layout.template_file)] ): return False # Load in artwork and frame it if not self.run_tasks( - funcs=[self.load_artwork], - message="Unable to load artwork!" + funcs=[self.load_artwork], message="Unable to load artwork!" ): return False @@ -1577,34 +1612,34 @@ def execute(self) -> bool: self.run_tasks( funcs=[self.paste_scryfall_scan], message="Couldn't import Scryfall scan, continuing without it!", - warning=True) + warning=True, + ) # Add expansion symbol self.run_tasks( funcs=[self.load_expansion_symbol], message="Unable to generate expansion symbol!", - warning=True) + warning=True, + ) # Add watermark if CFG.enable_basic_watermark and self.is_basic_land: # Basic land watermark if not self.run_tasks( funcs=[self.create_basic_watermark], - message="Unable to generate basic land watermark!" + message="Unable to generate basic land watermark!", ): return False elif CFG.watermark_mode is not WatermarkMode.Disabled: # Normal watermark if not self.run_tasks( - funcs=[self.create_watermark], - message="Unable to generate watermark!" + funcs=[self.create_watermark], message="Unable to generate watermark!" ): return False # Enable layers to build our frame if not self.run_tasks( - funcs=self.frame_layer_methods, - message="Enabling layers failed!" + funcs=self.frame_layer_methods, message="Enabling layers failed!" ): return False @@ -1613,16 +1648,16 @@ def execute(self) -> bool: funcs=[ *self.text_layer_methods, self.format_text_layers, - *self.post_text_methods + *self.post_text_methods, ], - message="Formatting text layers failed!" + message="Formatting text layers failed!", ): return False # Specific hooks if not self.run_tasks( funcs=self.hooks, - message="Encountered an error during triggered hooks step!" + message="Encountered an error during triggered hooks step!", ): return False @@ -1632,22 +1667,23 @@ def execute(self) -> bool: # Save the document if not self.run_tasks( - funcs=[self.save_mode], + funcs=[lambda: self.save_mode(self.output_file_name, self.docref)], message="Error during file save process!", - kwargs={'path': self.output_file_name, 'docref': self.docref} ): return False # Post save methods if not self.run_tasks( funcs=self.post_save_methods, - message="Image saved, but an error was encountered during the post-save step!" + message="Image saved, but an error was encountered during the post-save step!", ): return False # Reset document, return success if not ENV.TEST_MODE: - self.console.update(f"[b]{self.output_file_name.stem}[/b] rendered successfully!") + self.console.update( + f"[b]{self.output_file_name.stem}[/b] rendered successfully!" + ) self.reset() return True @@ -1663,21 +1699,28 @@ class StarterTemplate(BaseTemplate): def basic_text_layers(self) -> None: """Add essential text layers: Mana cost, Card name, Typeline.""" - self.text.extend([ - FormattedTextField( - layer=self.text_layer_mana, - contents=self.layout.mana_cost - ), - ScaledTextField( - layer=self.text_layer_name, - contents=self.layout.name, - reference=self.name_reference - ), - ScaledTextField( - layer=self.text_layer_type, - contents=self.layout.type_line, - reference=self.type_reference - )]) + if self.text_layer_mana: + self.text.append( + FormattedTextField( + layer=self.text_layer_mana, contents=self.layout.mana_cost + ) + ) + if self.text_layer_name: + self.text.append( + ScaledTextField( + layer=self.text_layer_name, + contents=self.layout.name, + reference=self.name_reference, + ) + ) + if self.text_layer_type: + self.text.append( + ScaledTextField( + layer=self.text_layer_type, + contents=self.layout.type_line, + reference=self.type_reference, + ) + ) class NormalTemplate(StarterTemplate): @@ -1702,21 +1745,25 @@ def is_fullart(self) -> bool: def rules_text_and_pt_layers(self) -> None: """Add rules and power/toughness text.""" - self.text.extend([ - FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - flavor=self.layout.flavor_text, - reference=self.textbox_reference, - divider=self.divider_layer, - pt_reference=self.pt_reference, - centered=self.is_centered - ), - TextField( - layer=self.text_layer_pt, - contents=f"{self.layout.power}/{self.layout.toughness}" - ) if self.is_creature else None - ]) + if self.text_layer_rules: + self.text.append( + FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + flavor=self.layout.flavor_text, + reference=self.textbox_reference, + divider=self.divider_layer, + pt_reference=self.pt_reference, + centered=self.is_centered, + ) + ) + if self.text_layer_pt and self.is_creature: + self.text.append( + TextField( + layer=self.text_layer_pt, + contents=f"{self.layout.power}/{self.layout.toughness}", + ) + ) """ * Frame Layer Methods @@ -1753,27 +1800,29 @@ def enable_crown(self) -> None: """Enable layers which make-up the Legendary crown.""" # Enable crown and legendary border - self.crown_layer.visible = True - if self.border_group and isinstance(self.border_group, LayerContainer): - psd.getLayer(LAYERS.NORMAL_BORDER, self.border_group).visible = False - psd.getLayer(LAYERS.LEGENDARY_BORDER, self.border_group).visible = True + if self.crown_layer: + self.crown_layer.visible = True + if self.border_group: + if layer := psd.getLayer(LAYERS.NORMAL_BORDER, self.border_group): + layer.visible = False + if layer := psd.getLayer(LAYERS.LEGENDARY_BORDER, self.border_group): + layer.visible = True # Call hollow crown step if self.is_hollow_crown: - self.enable_hollow_crown( - psd.getLayer(LAYERS.SHADOWS)) + self.enable_hollow_crown() - def enable_hollow_crown(self, shadows: Optional[ArtLayer] = None) -> None: + def enable_hollow_crown(self) -> None: """Enable the hollow legendary crown.""" - if shadows: + if shadows := psd.getLayer(LAYERS.SHADOWS): psd.enable_mask(shadows) - psd.enable_mask(self.crown_layer.parent) - psd.enable_mask(self.pinlines_layer.parent) - self.crown_shadow_layer.visible = True - - -class NormalEssentialsTemplate(NormalTemplate): - """ - * Original extendable class for creating an M15 Style template without Nyx or Companion layers. - * DEPRECATED, left here for backwards compatibility. - """ + if self.crown_layer and isinstance( + (parent := self.crown_layer.parent), LayerSet + ): + psd.enable_mask(parent) + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_mask(parent) + if self.crown_shadow_layer: + self.crown_shadow_layer.visible = True diff --git a/src/templates/_cosmetic.py b/src/templates/_cosmetic.py index f50f6445..9cc32dc5 100644 --- a/src/templates/_cosmetic.py +++ b/src/templates/_cosmetic.py @@ -1,9 +1,10 @@ """ * Cosmetic Template Class Modifiers """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Callable, Union +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -12,6 +13,7 @@ # Local Imports from src.enums.layers import LAYERS import src.helpers as psd +from src.helpers.layers import select_layer from src.templates._core import BaseTemplate from src.templates._vector import VectorTemplate from src.text_layers import ScaledWidthTextField @@ -22,29 +24,37 @@ """ -class ExtendedMod (BaseTemplate): +class ExtendedMod(BaseTemplate): """ Modifier for Extended templates. Modifies: - 'is_content_aware_enabled': Enabled """ - frame_suffix = 'Extended' - is_content_aware_enabled = True + + frame_suffix = "Extended" + + @cached_property + def is_content_aware_enabled(self) -> bool: + return True -class FullartMod (BaseTemplate): +class FullartMod(BaseTemplate): """ Modifier for Fullart templates. Modifies: - 'is_fullart': Enabled """ - frame_suffix = 'Fullart' - is_fullart = True + frame_suffix = "Fullart" -class BorderlessMod (BaseTemplate): + @cached_property + def is_fullart(self) -> bool: + return True + + +class BorderlessMod(BaseTemplate): """ Modifier for Borderless templates. @@ -53,21 +63,28 @@ class BorderlessMod (BaseTemplate): - 'is_content_aware_enabled': Enabled - 'background_layer': None """ - frame_suffix = 'Borderless' - is_fullart = True - is_content_aware_enabled = True + + frame_suffix = "Borderless" + + @cached_property + def is_fullart(self) -> bool: + return True + + @cached_property + def is_content_aware_enabled(self) -> bool: + return True """ * Layers """ - @property - def background_layer(self) -> Optional[ArtLayer]: + @cached_property + def background_layer(self) -> ArtLayer | None: """Borderless cards have no 'Background' layer.""" return -class VectorBorderlessMod (BorderlessMod): +class VectorBorderlessMod(BorderlessMod): """ Modifier for vectorized Borderless templates. @@ -81,8 +98,8 @@ class VectorBorderlessMod (BorderlessMod): * Groups """ - @property - def background_group(self) -> Optional[LayerSet]: + @cached_property + def background_group(self) -> LayerSet | None: """Borderless cards have no 'Background' group.""" return @@ -92,7 +109,7 @@ def background_group(self) -> Optional[LayerSet]: """ -class NyxMod (BaseTemplate): +class NyxMod(BaseTemplate): """ Modifier for 'Nyxtouched' supported templates. @@ -117,7 +134,7 @@ def is_hollow_crown(self) -> bool: """ @cached_property - def background_layer(self) -> Optional[ArtLayer]: + def background_layer(self) -> ArtLayer | None: """Try finding a Nyx background layer if the card is a 'Nyxtouched' frame.""" if self.is_nyx: if layer := psd.getLayer(self.background, LAYERS.NYX): @@ -125,7 +142,7 @@ def background_layer(self) -> Optional[ArtLayer]: return super().background_layer -class VectorNyxMod (NyxMod, VectorTemplate): +class VectorNyxMod(NyxMod, VectorTemplate): """ Modifier for vectorized 'Nyxtouched' supported templates. @@ -137,7 +154,7 @@ class VectorNyxMod (NyxMod, VectorTemplate): """ @cached_property - def background_group(self) -> Optional[LayerSet]: + def background_group(self) -> LayerSet | None: """Optional[LayerSet]: Try finding a Nyx background group if the card is a 'Nyxtouched' frame.""" if self.is_nyx: if layer := psd.getLayerSet(LAYERS.NYX): @@ -145,7 +162,7 @@ def background_group(self) -> Optional[LayerSet]: return super().background_group -class CompanionMod (BaseTemplate): +class CompanionMod(BaseTemplate): """ Modifier for 'Companion' supported templates. @@ -158,7 +175,7 @@ class CompanionMod (BaseTemplate): """ @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add companion layers step.""" funcs = [self.enable_companion_layers] if self.is_companion else [] return [*super().frame_layer_methods, *funcs] @@ -179,7 +196,7 @@ def is_hollow_crown(self) -> bool: """ @cached_property - def companion_layer(self) -> Optional[ArtLayer]: + def companion_layer(self) -> ArtLayer | None: """Companion inner crown layer.""" return psd.getLayer(self.pinlines, LAYERS.COMPANION) @@ -193,7 +210,7 @@ def enable_companion_layers(self) -> None: self.companion_layer.visible = True -class VectorCompanionMod (CompanionMod, VectorTemplate): +class VectorCompanionMod(CompanionMod, VectorTemplate): """ Modifier for vectorized 'Companion' supported templates. @@ -208,7 +225,7 @@ class VectorCompanionMod (CompanionMod, VectorTemplate): """ @cached_property - def companion_group(self) -> Optional[LayerSet]: + def companion_group(self) -> LayerSet | None: """Group containing Companion inner crown textures.""" return psd.getLayerSet(LAYERS.COMPANION) @@ -221,8 +238,13 @@ def enable_companion_layers(self) -> None: if self.is_legendary and self.companion_group: self.create_blended_layer( group=self.companion_group, - colors=self.crown_colors, - masks=self.crown_masks) + colors=self.crown_colors + if isinstance(self.crown_colors, str) + else [color for color in self.crown_colors if isinstance(color, str)] + if isinstance(self.crown_colors, list) + else "", + masks=self.crown_masks, + ) """ @@ -250,7 +272,7 @@ class NicknameMod(VectorTemplate): """ @cached_property - def post_text_methods(self) -> list[Callable]: + def post_text_methods(self) -> list[Callable[[], None]]: """Add nickname text layers.""" funcs = [self.format_nickname_text] if self.is_nickname else [] return [*super().post_text_methods, *funcs] @@ -262,7 +284,7 @@ def post_text_methods(self) -> list[Callable]: @cached_property def is_nickname(self) -> bool: """Toggles nickname behavior. Can be overwritten to implement - conditional logic.""" + conditional logic.""" return True """ @@ -270,18 +292,18 @@ def is_nickname(self) -> bool: """ @cached_property - def text_layer_nickname(self) -> Optional[ArtLayer]: + def text_layer_nickname(self) -> ArtLayer | None: """Alternate text layer to use for original card name when a nickname is used.""" - _layer = psd.getLayer(LAYERS.NICKNAME, self.text_group) - _layer.visible = True - return _layer + if layer := psd.getLayer(LAYERS.NICKNAME, self.text_group): + layer.visible = True + return layer """ * Layer Groups """ @cached_property - def nickname_group(self) -> Optional[LayerSet]: + def nickname_group(self) -> LayerSet | None: """Nickname frame element group.""" return psd.getLayerSet(LAYERS.NICKNAME) @@ -290,9 +312,9 @@ def nickname_group(self) -> Optional[LayerSet]: """ @cached_property - def nickname_shape(self) -> Optional[ReferenceLayer]: + def nickname_shape(self) -> ReferenceLayer | None: """Shape layer behind the original card name on the nickname frame element. Also used - to position the original card name as a reference.""" + to position the original card name as a reference.""" _shape_group = psd.getLayerSet(LAYERS.SHAPE, self.nickname_group) # Check for a legendary-specific nickname shape @@ -302,7 +324,7 @@ def nickname_shape(self) -> Optional[ReferenceLayer]: return psd.get_reference_layer(LAYERS.NORMAL, _shape_group) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """Add Nickname shape if needed.""" _shapes = super().enabled_shapes if self.is_nickname: @@ -321,13 +343,16 @@ def basic_text_layers(self) -> None: # Ask user to input the nickname if not provided self.prompt_nickname_text() - # Add original name - self.text.append( - ScaledWidthTextField( - layer=self.text_layer_nickname, - contents=str(self.layout.name), - reference=self.nickname_shape)) - self.layout.name = self.layout.nickname_text + if self.text_layer_nickname: + # Add original name + self.text.append( + ScaledWidthTextField( + layer=self.text_layer_nickname, + contents=str(self.layout.name), + reference=self.nickname_shape, + ) + ) + self.layout.name = self.layout.nickname return super().basic_text_layers() """ @@ -335,7 +360,6 @@ def basic_text_layers(self) -> None: """ def format_nickname_text(self) -> None: - # Center the card name on the nickname plate psd.align_all(self.text_layer_nickname, self.nickname_shape) @@ -345,9 +369,11 @@ def format_nickname_text(self) -> None: def prompt_nickname_text(self) -> None: """Check if nickname text is already defined. If not, prompt the user.""" - if not self.layout.nickname: + if not self.layout.nickname and self.text_layer_name: _textItem = self.text_layer_name.textItem - _textItem.contents = 'ENTER NICKNAME' + _textItem.contents = "ENTER NICKNAME" + select_layer(self.text_layer_name) self.console.await_choice( - self.event, msg='Enter nickname text, then hit continue ...') + self.event, msg="Enter nickname text, then hit continue ..." + ) self.layout.nickname = _textItem.contents diff --git a/src/templates/_vector.py b/src/templates/_vector.py index 7600a664..83999d8e 100644 --- a/src/templates/_vector.py +++ b/src/templates/_vector.py @@ -7,27 +7,43 @@ - Architecture for mask and shape enabling based on card archetype * Vector templates can be challenging for beginners, but have huge benefits. """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Union +from typing import NotRequired, TypedDict +from collections.abc import Callable, Sequence # Third Party Imports -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import crown_color_map, indicator_color_map, pinlines_color_map +from src.schema.colors import GradientConfig +from src.schema.colors import ( + ColorObject, + crown_color_map, + indicator_color_map, + pinlines_color_map, +) from src.templates import NormalTemplate -from src.utils.adobe import LayerObject, LayerObjectTypes + + +class MaskAction(TypedDict): + layer: NotRequired[ArtLayer | LayerSet] + mask: ArtLayer | LayerSet + vector: NotRequired[bool] + funcs: NotRequired[Sequence[Callable[[ArtLayer | LayerSet], None]]] + """The layer is passed to the callback functions.""" + """ * Template Classes """ -class VectorTemplate (NormalTemplate): +class VectorTemplate(NormalTemplate): """Next generation template using vector shape layers, automatic pinlines, and blended multicolor textures.""" """ @@ -53,45 +69,51 @@ def is_within_color_limit(self) -> bool: """ @cached_property - def pinlines_group(self) -> Optional[LayerSet]: + def pinlines_group(self) -> LayerSet | None: """Group containing pinlines colors, textures, or other groups.""" return psd.getLayerSet(LAYERS.PINLINES, self.docref) @cached_property def pinlines_groups(self) -> list[LayerSet]: """Groups where pinline colors will be generated.""" - return [self.pinlines_group] + if self.pinlines_group: + return [self.pinlines_group] + return [] @cached_property - def twins_group(self) -> Optional[LayerSet]: + def twins_group(self) -> LayerSet | None: """Group containing twins texture layers.""" return psd.getLayerSet(LAYERS.TWINS, self.docref) @cached_property - def textbox_group(self) -> Optional[LayerSet]: + def textbox_group(self) -> LayerSet | None: """Group containing textbox texture layers.""" return psd.getLayerSet(LAYERS.TEXTBOX, self.docref) @cached_property - def background_group(self) -> Optional[LayerSet]: + def background_group(self) -> LayerSet | None: """Optional[LayerSet]: Group containing background texture layers.""" return psd.getLayerSet(LAYERS.BACKGROUND, self.docref) @cached_property - def crown_group(self) -> Optional[LayerSet]: + def crown_group(self) -> LayerSet | None: """Group containing Legendary Crown texture layers.""" return psd.getLayerSet(LAYERS.LEGENDARY_CROWN, self.docref) @cached_property - def pt_group(self) -> Optional[LayerSet]: + def pt_group(self) -> LayerSet | None: """Group containing PT Box texture layers.""" return psd.getLayerSet(LAYERS.PT_BOX, self.docref) @cached_property - def indicator_group(self) -> Optional[LayerSet]: + def indicator_group(self) -> LayerSet | None: """Group where Color Indicator colors will be generated.""" - if group := psd.getLayerSet(LAYERS.SHAPE, [self.docref, LAYERS.COLOR_INDICATOR]): - group.parent.visible = True + if ( + group := psd.getLayerSet( + LAYERS.SHAPE, [self.docref, LAYERS.COLOR_INDICATOR] + ) + ) and isinstance((parent := group.parent), LayerSet): + parent.visible = True return group """ @@ -99,64 +121,94 @@ def indicator_group(self) -> Optional[LayerSet]: """ @cached_property - def pinlines_color_map(self) -> dict: + def pinlines_color_map(self) -> dict[str, ColorObject]: """Maps color values for the Pinlines.""" - return pinlines_color_map.copy() + return {**pinlines_color_map} @cached_property - def crown_color_map(self) -> dict: + def crown_color_map(self) -> dict[str, ColorObject]: """Maps color values for the Legendary Crown.""" - return crown_color_map.copy() + return {**crown_color_map} @cached_property - def indicator_color_map(self) -> dict: + def indicator_color_map( + self, + ) -> dict[str, tuple[float, float, float] | tuple[float, float, float, float]]: """Maps color values for the Color Indicator.""" - return indicator_color_map.copy() + return {**indicator_color_map} """ * Colors """ @cached_property - def pinlines_colors(self) -> Union[list[int], list[dict]]: + def pinlines_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as SolidColor or gradient notation.""" return psd.get_pinline_gradient( - self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines, - color_map=self.pinlines_color_map + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines, + color_map=self.pinlines_color_map, ) @cached_property - def indicator_colors(self) -> list[list[int]]: - """list[list[int]]: Must be returned as list of RGB/CMYK color notations.""" - return [ - self.indicator_color_map.get(c, [0, 0, 0]) - for c in self.layout.color_indicator[::-1] - ] if self.layout.color_indicator else [] + def indicator_colors( + self, + ) -> list[tuple[float, float, float] | tuple[float, float, float, float]]: + """Must be returned as list of RGB/CMYK color notations.""" + return ( + [ + self.indicator_color_map.get(c, (0, 0, 0)) + for c in self.layout.color_indicator[::-1] + ] + if self.layout.color_indicator + else [] + ) @cached_property - def textbox_colors(self) -> str: + def textbox_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as color combination or layer name, e.g. WU or Artifact.""" - return self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines + return ( + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines + ) @cached_property - def crown_colors(self) -> str: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as color combination or layer name, e.g. WU or Artifact.""" - return self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines + return ( + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines + ) @cached_property - def twins_colors(self) -> str: + def twins_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as color combination or layer name, e.g. WU or Artifact.""" return self.twins @cached_property - def background_colors(self) -> str: + def background_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as color combination or layer name, e.g. WU or Artifact.""" return self.background @cached_property - def pt_colors(self) -> str: - """Optional[str]: returned as a color combination or layer name, e.g. WU or Artifact.""" - if self.is_vehicle and self.background == LAYERS.VEHICLE: + def pt_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: + """Must be returned as a color combination or layer name, e.g. WU or Artifact.""" + if self.is_vehicle and self.background == LAYERS.VEHICLE and self.text_layer_pt: # Typically use white text for Vehicle PT self.text_layer_pt.textItem.color = self.RGB_WHITE return LAYERS.VEHICLE @@ -171,48 +223,55 @@ def pt_colors(self) -> str: """ @cached_property - def border_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def border_shape(self) -> ArtLayer | None: """Vector shape representing the card border.""" - if self.is_legendary: - return psd.getLayer(LAYERS.LEGENDARY, self.border_group) - return psd.getLayer(LAYERS.NORMAL, self.border_group) + if isinstance(self.border_group, LayerSet): + if self.is_legendary: + return psd.getLayer(LAYERS.LEGENDARY, self.border_group) + return psd.getLayer(LAYERS.NORMAL, self.border_group) @cached_property - def crown_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def crown_shape(self) -> ArtLayer | None: """Vector shape representing the legendary crown. This isn't typically used, so by default - it just returns empty.""" + it just returns empty.""" return None @cached_property - def pinlines_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def pinlines_shape(self) -> ArtLayer | None: """Vector shape representing the card pinlines.""" return psd.getLayer( (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) - if self.is_transform else LAYERS.NORMAL, - [self.pinlines_group, LAYERS.SHAPE]) + if self.is_transform + else LAYERS.NORMAL, + [self.pinlines_group, LAYERS.SHAPE], + ) @cached_property - def textbox_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def textbox_shape(self) -> ArtLayer | None: """Vector shape representing the card textbox.""" - name = LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL + name = ( + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL + ) return psd.getLayer(name, [self.textbox_group, LAYERS.SHAPE]) @cached_property - def twins_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def twins_shape(self) -> ArtLayer | None: """Vector shape representing the card name and title boxes.""" name = LAYERS.TRANSFORM if self.is_transform or self.is_mdfc else LAYERS.NORMAL return psd.getLayer(name, [self.twins_group, LAYERS.SHAPE]) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """Vector shapes that should be enabled during the enable_shape_layers step. Should be - a list of layer, layer group, or None objects.""" + a list of layer, layer group, or None objects.""" return [ self.border_shape, self.crown_shape, self.pinlines_shape, self.twins_shape, - self.textbox_shape + self.textbox_shape, ] """ @@ -222,16 +281,30 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: @cached_property def indicator_masks(self) -> list[ArtLayer]: """List of layers containing masks used to build the Color Indicator.""" - if len(self.layout.color_indicator) == 2: + if len(self.layout.color_indicator) == 2 and self.indicator_group: # 2 colors -> Enable 2 outline - psd.getLayer('2', self.indicator_group.parent).visible = True - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.COLOR_INDICATOR])] - if len(self.layout.color_indicator) == 3: + if layer := psd.getLayer("2", self.indicator_group.parent): + layer.visible = True + if layer := psd.getLayer( + LAYERS.HALF, [self.mask_group, LAYERS.COLOR_INDICATOR] + ): + return [layer] + return [] + if len(self.layout.color_indicator) == 3 and self.indicator_group: # 3 colors -> Enable 3 outline - psd.getLayer('3', self.indicator_group.parent).visible = True + if layer := psd.getLayer("3", self.indicator_group.parent): + layer.visible = True return [ - psd.getLayer(LAYERS.THIRD, [self.mask_group, LAYERS.COLOR_INDICATOR]), - psd.getLayer(LAYERS.TWO_THIRDS, [self.mask_group, LAYERS.COLOR_INDICATOR]) + layer + for layer in ( + psd.getLayer( + LAYERS.THIRD, [self.mask_group, LAYERS.COLOR_INDICATOR] + ), + psd.getLayer( + LAYERS.TWO_THIRDS, [self.mask_group, LAYERS.COLOR_INDICATOR] + ), + ) + if layer ] return [] @@ -270,13 +343,21 @@ def pt_masks(self) -> list[ArtLayer]: """ @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: """ Masks that should be copied or enabled during the `enable_layer_masks` step. Not utilized by default. Returns: - dict: Advanced mask notation, contains "from" and "to" layers and other optional parameters. - - list: Contains layer to copy from, layer to copy to. + - tuple: Contains layer to copy from, layer to copy to. - ArtLayer | LayerSet: Layer object to enable a mask on. - None: Skip this mask. """ @@ -299,45 +380,45 @@ def enable_frame_layers(self) -> None: if self.is_creature and self.pt_group: self.pt_group.visible = True self.generate_layer( - group=self.pt_group, - colors=self.pt_colors, - masks=self.pt_masks) + group=self.pt_group, colors=self.pt_colors, masks=self.pt_masks + ) # Color Indicator -> Blended solid color layers if self.is_type_shifted and self.indicator_group: self.generate_layer( group=self.indicator_group, colors=self.indicator_colors, - masks=self.indicator_masks) + masks=self.indicator_masks, + ) # Pinlines -> Solid color or gradient layers for group in [g for g in self.pinlines_groups if g]: group.visible = True self.generate_layer( - group=group, - colors=self.pinlines_colors, - masks=self.pinlines_masks) + group=group, colors=self.pinlines_colors, masks=self.pinlines_masks + ) # Twins -> Blended texture layers if self.twins_group: self.generate_layer( - group=self.twins_group, - colors=self.twins_colors, - masks=self.twins_masks) + group=self.twins_group, colors=self.twins_colors, masks=self.twins_masks + ) # Textbox -> Blended texture layers if self.textbox_group: self.generate_layer( group=self.textbox_group, colors=self.textbox_colors, - masks=self.textbox_masks) + masks=self.textbox_masks, + ) # Background layer -> Blended texture layers if self.background_group: self.generate_layer( group=self.background_group, colors=self.background_colors, - masks=self.background_masks) + masks=self.background_masks, + ) # Legendary crown if self.is_legendary: @@ -345,14 +426,16 @@ def enable_frame_layers(self) -> None: def enable_shape_layers(self) -> None: """Enable required vector shape layers provided by `enabled_shapes`.""" - def _enable_shape(_shapes: Union[LayerObjectTypes, list[LayerObjectTypes], None]) -> None: - for x in _shapes: + + def _enable_shape( + shapes: list[ArtLayer | LayerSet | None], + ) -> None: + for x in shapes: if not x: continue - if isinstance(x, list): - _enable_shape(x) else: x.visible = True + _enable_shape(self.enabled_shapes) def enable_layer_masks(self) -> None: @@ -360,49 +443,53 @@ def enable_layer_masks(self) -> None: # For each mask enabled, apply it based on given notation for mask in [m for m in self.enabled_masks if m]: - # Dict notation, complex mask behavior if isinstance(mask, dict): - # Copy to a layer? - if layer := mask.get('layer'): + if layer := mask.get("layer"): # Copy normal or vector mask to layer - func = psd.copy_vector_mask if mask.get('vector') else psd.copy_layer_mask - func(mask.get('mask'), layer) + func = ( + psd.copy_vector_mask + if mask.get("vector") + else psd.copy_layer_mask + ) + func(mask.get("mask"), layer) else: # Enable normal or vector mask - layer = mask.get('mask') - func = psd.enable_vector_mask if mask.get('vector') else psd.enable_mask + layer = mask.get("mask") + func = ( + psd.enable_vector_mask + if mask.get("vector") + else psd.enable_mask + ) func(layer) # Apply extra functions - [f(layer) for f in mask.get('funcs', [])] + [f(layer) for f in mask.get("funcs", [])] - # List notation, copy from one layer to another - elif isinstance(mask, list): + # Tuple notation, copy from one layer to another + elif isinstance(mask, tuple): psd.copy_layer_mask(*mask) # Single layer to enable mask on - elif isinstance(mask, LayerObject): + else: psd.enable_mask(mask) def enable_crown(self) -> None: """Enable the Legendary crown, only called if card is Legendary.""" # Enable Legendary Crown group and layers - self.crown_group.visible = True - self.generate_layer( - group=self.crown_group, - colors=self.crown_colors, - masks=self.crown_masks) - - # Enable Hollow Crown - if self.is_hollow_crown: - self.enable_hollow_crown( - masks=[self.crown_group], - vector_masks=[self.pinlines_group]) - - def enable_hollow_crown(self, **kwargs) -> None: + if self.crown_group: + self.crown_group.visible = True + self.generate_layer( + group=self.crown_group, colors=self.crown_colors, masks=self.crown_masks + ) + + # Enable Hollow Crown + if self.is_hollow_crown: + self.enable_hollow_crown() + + def enable_hollow_crown(self) -> None: """Enable the Hollow Crown within the Legendary Crown, only called if card is Legendary Nyx or Companion. Keyword Args: @@ -411,12 +498,12 @@ def enable_hollow_crown(self, **kwargs) -> None: """ # Layer masks to enable - for m in kwargs.get('masks', []): - psd.enable_mask(m) + if self.crown_group: + psd.enable_mask(self.crown_group) # Vector masks to enable - for m in kwargs.get('vector_masks', []): - psd.enable_vector_mask(m) + if self.pinlines_group: + psd.enable_vector_mask(self.pinlines_group) # Enable shadow if self.crown_shadow_layer: diff --git a/src/templates/adventure.py b/src/templates/adventure.py index f43d8c18..2fdb35c7 100644 --- a/src/templates/adventure.py +++ b/src/templates/adventure.py @@ -1,9 +1,10 @@ """ * Adventure Templates """ + # Standard Library from functools import cached_property -from typing import Callable, Optional, Union +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -12,7 +13,9 @@ # Local Imports from src.enums.layers import LAYERS import src.helpers as psd -from src.layouts import AdventureLayout +from src.schema.colors import GradientConfig +from src.layouts import AdventureLayout, NormalLayout +from src.schema.colors import ColorObject from src.templates._vector import VectorTemplate from src.templates._core import NormalTemplate import src.text_layers as text_classes @@ -29,17 +32,21 @@ class AdventureMod(NormalTemplate): * Adventure side text layers (Mana cost, name, typeline, and oracle text) and textbox reference. """ - def __init__(self, layout: AdventureLayout, **kwargs): - super().__init__(layout, **kwargs) + def __init__(self, layout: NormalLayout): + super().__init__(layout) """ * Mixin Methods """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Adventure text layers step.""" - funcs = [self.text_layers_adventure] if isinstance(self.layout, AdventureLayout) else [] + funcs = ( + [self.text_layers_adventure] + if isinstance(self.layout, AdventureLayout) + else [] + ) return [*super().text_layer_methods, *funcs] """ @@ -47,27 +54,27 @@ def text_layer_methods(self) -> list[Callable]: """ @cached_property - def text_layer_name_adventure(self) -> Optional[ArtLayer]: + def text_layer_name_adventure(self) -> ArtLayer | None: """Name for the adventure side.""" return psd.getLayer(LAYERS.NAME_ADVENTURE, self.text_group) @cached_property - def text_layer_mana_adventure(self) -> Optional[ArtLayer]: + def text_layer_mana_adventure(self) -> ArtLayer | None: """Mana cost for the adventure side.""" return psd.getLayer(LAYERS.MANA_COST_ADVENTURE, self.text_group) @cached_property - def text_layer_type_adventure(self) -> Optional[ArtLayer]: + def text_layer_type_adventure(self) -> ArtLayer | None: """Type line for the adventure side.""" return psd.getLayer(LAYERS.TYPE_LINE_ADVENTURE, self.text_group) @cached_property - def text_layer_rules_adventure(self) -> Optional[ArtLayer]: + def text_layer_rules_adventure(self) -> ArtLayer | None: """Rules text for the adventure side.""" return psd.getLayer(LAYERS.RULES_TEXT_ADVENTURE, self.text_group) @cached_property - def divider_layer_adventure(self) -> Optional[ArtLayer]: + def divider_layer_adventure(self) -> ArtLayer | None: """Flavor divider for the adventure side.""" return psd.getLayer(LAYERS.DIVIDER_ADVENTURE, self.text_group) @@ -76,37 +83,51 @@ def divider_layer_adventure(self) -> Optional[ArtLayer]: """ @cached_property - def textbox_reference_adventure(self) -> Optional[ArtLayer]: - return psd.get_reference_layer(LAYERS.TEXTBOX_REFERENCE_ADVENTURE, self.text_group) + def textbox_reference_adventure(self) -> ArtLayer | None: + return psd.get_reference_layer( + LAYERS.TEXTBOX_REFERENCE_ADVENTURE, self.text_group + ) """ * Adventure Methods """ def text_layers_adventure(self): - # Add adventure text layers - self.text.extend([ - text_classes.FormattedTextField( - layer=self.text_layer_mana_adventure, - contents=self.layout.mana_adventure - ), - text_classes.ScaledTextField( - layer=self.text_layer_name_adventure, - contents=self.layout.name_adventure, - reference=self.text_layer_mana_adventure, - ), - text_classes.FormattedTextArea( - layer=self.text_layer_rules_adventure, - contents=self.layout.oracle_text_adventure, - reference=self.textbox_reference_adventure, - flavor=self.layout.flavor_text_adventure, - centered=False - ), - text_classes.TextField( - layer=self.text_layer_type_adventure, - contents=self.layout.type_line_adventure - ) - ]) + if isinstance(self.layout, AdventureLayout): + # Add adventure text layers + + if self.text_layer_mana_adventure: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_mana_adventure, + contents=self.layout.mana_adventure, + ) + ) + if self.text_layer_name_adventure: + self.text.append( + text_classes.ScaledTextField( + layer=self.text_layer_name_adventure, + contents=self.layout.name_adventure, + reference=self.text_layer_mana_adventure, + ) + ) + if self.text_layer_rules_adventure: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules_adventure, + contents=self.layout.oracle_text_adventure, + reference=self.textbox_reference_adventure, + flavor=self.layout.flavor_text_adventure, + centered=False, + ) + ) + if self.text_layer_type_adventure: + self.text.append( + text_classes.TextField( + layer=self.text_layer_type_adventure, + contents=self.layout.type_line_adventure, + ) + ) class AdventureVectorMod(AdventureMod, VectorTemplate): @@ -121,47 +142,48 @@ class AdventureVectorMod(AdventureMod, VectorTemplate): # Color Maps """Maps color values to adventure name box.""" adventure_name_color_map = { - 'W': [179, 172, 156], - 'U': [43, 126, 167], - 'B': [104, 103, 102], - 'R': [159, 83, 59], - 'G': [68, 96, 63], - 'Colorless': [], - 'Gold': [166, 145, 80], - 'Land': [177, 166, 169] + "W": (179, 172, 156), + "U": (43, 126, 167), + "B": (104, 103, 102), + "R": (159, 83, 59), + "G": (68, 96, 63), + # There are no colorless adventure cards as of now + "Colorless": (-1, -1, -1), + "Gold": (166, 145, 80), + "Land": (177, 166, 169), } """Maps color values to adventure typeline box.""" adventure_typeline_color_map = { - 'W': [129, 120, 103], - 'U': [3, 94, 127], - 'B': [44, 41, 40], - 'R': [124, 51, 33], - 'G': [11, 53, 30], - 'Colorless': [], - 'Gold': [117, 90, 40], - 'Land': [154, 137, 130] + "W": (129, 120, 103), + "U": (3, 94, 127), + "B": (44, 41, 40), + "R": (124, 51, 33), + "G": (11, 53, 30), + "Colorless": (-1, -1, -1), + "Gold": (117, 90, 40), + "Land": (154, 137, 130), } """Maps color values to adventure typeline accent box.""" adventure_typeline_accent_color_map = { - 'W': [90, 82, 71], - 'U': [2, 67, 96], - 'B': [20, 17, 19], - 'R': [81, 34, 22], - 'G': [2, 34, 16], - 'Colorless': [], - 'Gold': [75, 62, 37], - 'Land': [115, 98, 89] + "W": (90, 82, 71), + "U": (2, 67, 96), + "B": (20, 17, 19), + "R": (81, 34, 22), + "G": (2, 34, 16), + "Colorless": (-1, -1, -1), + "Gold": (75, 62, 37), + "Land": (115, 98, 89), } """Maps color values to adventure wings.""" adventure_wings_color_map = { - 'W': [213, 203, 181], - 'U': [181, 198, 213], - 'B': [162, 155, 152], - 'R': [192, 142, 115], - 'G': [174, 174, 155], - 'Colorless': [], - 'Gold': [196, 172, 131], - 'Land': [194, 178, 177] + "W": (213, 203, 181), + "U": (181, 198, 213), + "B": (162, 155, 152), + "R": (192, 142, 115), + "G": (174, 174, 155), + "Colorless": (-1, -1, -1), + "Gold": (196, 172, 131), + "Land": (194, 178, 177), } """ @@ -169,9 +191,13 @@ class AdventureVectorMod(AdventureMod, VectorTemplate): """ @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add Adventure frame layers step.""" - funcs = [self.enable_adventure_layers] if isinstance(self.layout, AdventureLayout) else [] + funcs = ( + [self.enable_adventure_layers] + if isinstance(self.layout, AdventureLayout) + else [] + ) return [*super().frame_layer_methods, *funcs] """ @@ -179,7 +205,7 @@ def frame_layer_methods(self) -> list[Callable]: """ @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """LayerSet: Use right page of storybook as main textbox group.""" return psd.getLayerSet(LAYERS.RIGHT, self.adventure_group) @@ -188,37 +214,37 @@ def textbox_group(self) -> LayerSet: """ @cached_property - def adventure_group(self) -> LayerSet: + def adventure_group(self) -> LayerSet | None: """Adventure storybook group.""" return psd.getLayerSet(LAYERS.STORYBOOK) @cached_property - def adventure_pinlines_group(self) -> LayerSet: + def adventure_pinlines_group(self) -> LayerSet | None: """Pinline at the bottom of adventure storybook.""" return psd.getLayerSet(LAYERS.PINLINES, self.adventure_group) @cached_property - def adventure_textbox_group(self) -> LayerSet: + def adventure_textbox_group(self) -> LayerSet | None: """Left side storybook page, contains adventure text.""" return psd.getLayerSet(LAYERS.LEFT, self.adventure_group) @cached_property - def adventure_name_group(self) -> LayerSet: + def adventure_name_group(self) -> LayerSet | None: """Plate to color for adventure name.""" return psd.getLayerSet(LAYERS.ADVENTURE_NAME, self.adventure_group) @cached_property - def adventure_typeline_group(self) -> LayerSet: + def adventure_typeline_group(self) -> LayerSet | None: """Plate to color for adventure typeline.""" return psd.getLayerSet(LAYERS.ADVENTURE_TYPELINE, self.adventure_group) @cached_property - def adventure_typeline_accent_group(self) -> LayerSet: + def adventure_typeline_accent_group(self) -> LayerSet | None: """Plate to color for adventure typeline accent.""" return psd.getLayerSet(LAYERS.ADVENTURE_TYPELINE_ACCENT, self.adventure_group) @cached_property - def adventure_wings_group(self) -> LayerSet: + def adventure_wings_group(self) -> LayerSet | None: """Group containing wings on each side of adventure storybook.""" return psd.getLayerSet(LAYERS.WINGS, self.adventure_group) @@ -229,29 +255,46 @@ def adventure_wings_group(self) -> LayerSet: @cached_property def adventure_textbox_colors(self) -> str: """Colors to use for adventure textbox textures.""" - return self.layout.adventure_colors + if isinstance(self.layout, AdventureLayout): + return self.layout.adventure_colors + return "" @cached_property - def adventure_name_colors(self) -> list[int]: + def adventure_name_colors(self) -> tuple[float, float, float]: """Colors to use for adventure name box.""" - return self.adventure_name_color_map.get(self.layout.adventure_colors) + if isinstance(self.layout, AdventureLayout): + return self.adventure_name_color_map.get( + self.layout.adventure_colors, (0, 0, 0) + ) + return (0, 0, 0) @cached_property - def adventure_typeline_colors(self) -> list[int]: + def adventure_typeline_colors(self) -> tuple[float, float, float]: """Colors to use for adventure typeline box.""" - return self.adventure_typeline_color_map.get(self.layout.adventure_colors) + if isinstance(self.layout, AdventureLayout): + return self.adventure_typeline_color_map.get( + self.layout.adventure_colors, (0, 0, 0) + ) + return (0, 0, 0) @cached_property - def adventure_typeline_accent_colors(self) -> list[int]: + def adventure_typeline_accent_colors(self) -> tuple[float, float, float]: """Colors to use for adventure typeline accent box.""" - return self.adventure_typeline_accent_color_map.get(self.layout.adventure_colors) + if isinstance(self.layout, AdventureLayout): + return self.adventure_typeline_accent_color_map.get( + self.layout.adventure_colors, (0, 0, 0) + ) + return (0, 0, 0) @cached_property - def adventure_wings_colors(self) -> Union[list[int], list[dict]]: + def adventure_wings_colors(self) -> ColorObject | list[GradientConfig]: """Colors to use for adventure wings.""" return psd.get_pinline_gradient( - self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines, - color_map=self.adventure_wings_color_map) + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines, + color_map=self.adventure_wings_color_map, + ) """ * Adventure Blending Masks @@ -264,7 +307,9 @@ def adventure_textbox_masks(self) -> list[ArtLayer]: @cached_property def textbox_masks(self) -> list[ArtLayer]: - return [psd.getLayer(LAYERS.HALF, [LAYERS.MASKS, LAYERS.RIGHT])] + if layer := psd.getLayer(LAYERS.HALF, [LAYERS.MASKS, LAYERS.RIGHT]): + return [layer] + return [] """ * Adventure Frame Methods @@ -273,36 +318,45 @@ def textbox_masks(self) -> list[ArtLayer]: def enable_adventure_layers(self) -> None: """Add and modify layers required for Adventure cards.""" - # Pinlines - self.generate_layer( - group=self.adventure_pinlines_group, - colors=self.pinlines_colors) + if self.adventure_pinlines_group: + # Pinlines + self.generate_layer( + group=self.adventure_pinlines_group, colors=self.pinlines_colors + ) - # Wings - self.generate_layer( - group=self.adventure_wings_group, - colors=self.adventure_wings_colors) + if self.adventure_wings_group: + # Wings + self.generate_layer( + group=self.adventure_wings_group, colors=self.adventure_wings_colors + ) - # textbox - self.generate_layer( - group=self.adventure_textbox_group, - colors=self.adventure_textbox_colors, - masks=self.adventure_textbox_masks) + if self.adventure_textbox_group: + # textbox + self.generate_layer( + group=self.adventure_textbox_group, + colors=self.adventure_textbox_colors, + masks=self.adventure_textbox_masks, + ) - # Adventure Name - self.generate_layer( - group=self.adventure_name_group, - colors=self.adventure_name_colors) + if self.adventure_name_group: + # Adventure Name + self.generate_layer( + group=self.adventure_name_group, colors=self.adventure_name_colors + ) - # Adventure Typeline - self.generate_layer( - group=self.adventure_typeline_group, - colors=self.adventure_typeline_colors) + if self.adventure_typeline_group: + # Adventure Typeline + self.generate_layer( + group=self.adventure_typeline_group, + colors=self.adventure_typeline_colors, + ) - # Adventure Accent - self.generate_layer( - group=self.adventure_typeline_accent_group, - colors=self.adventure_typeline_accent_colors) + if self.adventure_typeline_accent_group: + # Adventure Accent + self.generate_layer( + group=self.adventure_typeline_accent_group, + colors=self.adventure_typeline_accent_colors, + ) """ @@ -322,11 +376,11 @@ class AdventureVectorTemplate(AdventureVectorMod, VectorTemplate): """ @cached_property - def crown_group(self) -> LayerSet: + def crown_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.LEGENDARY_CROWN, LAYERS.LEGENDARY_CROWN) @cached_property - def pinlines_group(self) -> LayerSet: + def pinlines_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.PINLINES, LAYERS.PINLINES) """ @@ -334,19 +388,19 @@ def pinlines_group(self) -> LayerSet: """ @cached_property - def pinlines_legendary_shape(self) -> Optional[ArtLayer]: + def pinlines_legendary_shape(self) -> ArtLayer | None: if self.is_legendary: return psd.getLayer(LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE]) return @cached_property - def pt_shape(self) -> Optional[ArtLayer]: + def pt_shape(self) -> ArtLayer | None: if self.is_creature: return psd.getLayer(LAYERS.SHAPE, LAYERS.PT_BOX) return @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: return [self.border_shape, self.pinlines_legendary_shape, self.pt_shape] """ @@ -354,5 +408,8 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: """ def enable_crown(self) -> None: - self.crown_group.parent.visible = True + if self.crown_group and isinstance( + (parent := self.crown_group.parent), LayerSet + ): + parent.visible = True super().enable_crown() diff --git a/src/templates/basic_land.py b/src/templates/basic_land.py deleted file mode 100644 index 0c6e4663..00000000 --- a/src/templates/basic_land.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -* Basic Land Templates -* Deprecated in v1.13.0 -""" -# Standard Library Imports -from functools import cached_property -from typing import Optional - -# Third Party Imports -from photoshop.api._layerSet import LayerSet - -# Local Imports -from src.templates._cosmetic import BorderlessMod, FullartMod -from src.templates._core import BaseTemplate -import src.helpers as psd - - -class BasicLandUnstableTemplate (BorderlessMod, BaseTemplate): - """Basic land template for the borderless basics from Unstable. Doesn't support expansion symbol. - - Todo: - Transition to 'Normal' type. - """ - template_suffix = 'Unstable' - - """ - * Layer Groups - """ - - @cached_property - def text_group(self) -> Optional[LayerSet]: - return self.docref - - """ - * Expansion Symbol - """ - - def load_expansion_symbol(self): - """Does not support expansion symbol.""" - pass - - """ - * Frame Layer Methods - """ - - def enable_frame_layers(self): - """Only one layer, named according to basic land name.""" - psd.getLayer(self.layout.name_raw).visible = True - - -class BasicLandTherosTemplate (FullartMod, BaseTemplate): - """Fullart basic land template introduced in Theros: Beyond Death. - - Todo: - Transition to 'Normal' type. - """ - template_suffix = 'Theros' - - @cached_property - def text_group(self) -> Optional[LayerSet]: - """Text layers are in the document root.""" - return self.docref - - """ - * Frame Layer Methods - """ - - def enable_frame_layers(self): - """Only one layer, named according to basic land name.""" - psd.getLayer(self.layout.name_raw).visible = True diff --git a/src/templates/battle.py b/src/templates/battle.py index fa3a8e29..bd785387 100644 --- a/src/templates/battle.py +++ b/src/templates/battle.py @@ -1,23 +1,26 @@ """ * BATTLE TEMPLATES """ + # Standard Library from functools import cached_property -from typing import Callable, Optional, Union +from collections.abc import Callable, Sequence # Third Party Imports -from photoshop.api import SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports from src.enums.layers import LAYERS import src.helpers as psd -from src.layouts import BattleLayout -from src.schema.colors import pinlines_color_map +from src.schema.colors import GradientConfig +from src.helpers.layers import get_reference_layer +from src.layouts import BattleLayout, NormalLayout +from src.schema.colors import ColorObject, pinlines_color_map from src.templates._core import BaseTemplate from src.templates._vector import VectorTemplate from src.text_layers import FormattedTextArea, TextField +from src.utils.adobe import ReferenceLayer """ * Modifier Classes @@ -34,8 +37,8 @@ class BattleMod(BaseTemplate): * Might add support for Transform icon in the future, if other symbols are used. """ - def __init__(self, layout: BattleLayout, **kwargs): - super().__init__(layout, **kwargs) + def __init__(self, layout: NormalLayout): + super().__init__(layout) """ * Layout Check @@ -51,13 +54,13 @@ def is_layout_battle(self) -> bool: """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Class text layers.""" funcs = [self.text_layers_battle] if self.is_layout_battle else [] return [*super().text_layer_methods, *funcs] @cached_property - def post_text_methods(self) -> list[Callable]: + def post_text_methods(self) -> list[Callable[[], None]]: """Rotate card sideways.""" funcs = [psd.rotate_counter_clockwise] if self.is_layout_battle else [] return [*super().post_text_methods, *funcs] @@ -67,24 +70,24 @@ def post_text_methods(self) -> list[Callable]: """ @cached_property - def text_layer_name(self) -> Optional[ArtLayer]: + def text_layer_name(self) -> ArtLayer | None: """Doesn't need to be shifted.""" return psd.getLayer(LAYERS.NAME, self.text_group) @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """Supports noncreature and creature, with or without flipside PT.""" if self.is_transform and self.is_front and self.is_flipside_creature: return psd.getLayer(LAYERS.RULES_TEXT_FLIP, self.text_group) return psd.getLayer(LAYERS.RULES_TEXT, self.text_group) @cached_property - def text_layer_flipside_pt(self) -> Optional[ArtLayer]: + def text_layer_flipside_pt(self) -> ArtLayer | None: """Flipside power/toughness layer for front face Transform cards.""" return psd.getLayer(LAYERS.FLIPSIDE_POWER_TOUGHNESS, self.text_group) @cached_property - def text_layer_defense(self) -> Optional[ArtLayer]: + def text_layer_defense(self) -> ArtLayer | None: """Battle defense number in bottom right corner.""" return psd.getLayer(LAYERS.DEFENSE, self.text_group) @@ -93,11 +96,14 @@ def text_layer_defense(self) -> Optional[ArtLayer]: """ @cached_property - def defense_reference(self) -> Optional[ArtLayer]: + def defense_reference(self) -> ReferenceLayer | None: """Optional[ArtLayer]: Reference used to detect collision with the PT box.""" - return psd.getLayer( - f"{LAYERS.DEFENSE_REFERENCE} Flip" if self.is_flipside_creature else LAYERS.DEFENSE_REFERENCE, - self.text_group) + return get_reference_layer( + f"{LAYERS.DEFENSE_REFERENCE} Flip" + if self.is_flipside_creature + else LAYERS.DEFENSE_REFERENCE, + self.text_group, + ) """ * Methods @@ -110,17 +116,19 @@ def rules_text_and_pt_layers(self) -> None: if not self.is_layout_battle: return super().rules_text_and_pt_layers() - # Rules Text and Power / Toughness - self.text.extend([ - FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - flavor=self.layout.flavor_text, - reference=self.textbox_reference, - divider=self.divider_layer, - pt_reference=self.defense_reference, - centered=self.is_centered - )]) + if self.text_layer_rules: + # Rules Text and Power / Toughness + self.text.append( + FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + flavor=self.layout.flavor_text, + reference=self.textbox_reference, + divider=self.divider_layer, + pt_reference=self.defense_reference, + centered=self.is_centered, + ) + ) """ * Battle Methods @@ -129,18 +137,20 @@ def rules_text_and_pt_layers(self) -> None: def text_layers_battle(self) -> None: """Add and modify text layers required by Battle cards.""" - # Add defense text - self.text.append( - TextField( - layer=self.text_layer_defense, - contents=self.layout.defense)) + if isinstance(self.layout, BattleLayout) and self.text_layer_defense: + # Add defense text + self.text.append( + TextField(layer=self.text_layer_defense, contents=self.layout.defense) + ) # Add flipside Power/Toughness - if self.is_flipside_creature: + if self.is_flipside_creature and self.text_layer_flipside_pt: self.text.append( TextField( layer=self.text_layer_flipside_pt, - contents=str(self.layout.other_face_power) + "/" + str(self.layout.other_face_toughness) + contents=str(self.layout.other_face_power) + + "/" + + str(self.layout.other_face_toughness), ) ) @@ -157,7 +167,7 @@ class BattleTemplate(BattleMod, VectorTemplate): * Bool Properties """ - @property + @cached_property def is_legendary(self) -> bool: return False @@ -165,8 +175,8 @@ def is_legendary(self) -> bool: * Groups """ - @property - def background_group(self) -> Optional[LayerSet]: + @cached_property + def background_group(self) -> LayerSet | None: return """ @@ -174,12 +184,16 @@ def background_group(self) -> Optional[LayerSet]: """ @cached_property - def pinlines_colors(self) -> Union[SolidColor, list[dict]]: + def pinlines_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as SolidColor or gradient notation.""" return psd.get_pinline_gradient( - self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines, + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines, color_map=self.pinlines_color_map, - location_map={2: [.4543, .5886]} + location_map={2: [0.4543, 0.5886]}, ) """ @@ -187,11 +201,11 @@ def pinlines_colors(self) -> Union[SolidColor, list[dict]]: """ @cached_property - def textbox_shape(self) -> Optional[ArtLayer]: + def textbox_shape(self) -> ArtLayer | None: return psd.getLayer(LAYERS.NORMAL, [self.textbox_group, LAYERS.SHAPE]) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: return [self.textbox_shape] @@ -203,23 +217,22 @@ class UniversesBeyondBattleTemplate(BattleTemplate): """ @cached_property - def pinlines_color_map(self) -> dict: - colors = pinlines_color_map.copy() - colors.update({ - 'W': [246, 247, 241], - 'U': [0, 131, 193], - 'B': [44, 40, 33], - 'R': [237, 66, 31], - 'G': [5, 129, 64], - 'Gold': [239, 209, 107], - 'Land': [165, 150, 132], - 'Artifact': [227, 228, 230], - 'Colorless': [227, 228, 230] - }) - return colors + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (246, 247, 241), + "U": (0, 131, 193), + "B": (44, 40, 33), + "R": (237, 66, 31), + "G": (5, 129, 64), + "Gold": (239, 209, 107), + "Land": (165, 150, 132), + "Artifact": (227, 228, 230), + "Colorless": (227, 228, 230), + } @cached_property - def twins_colors(self) -> Optional[str]: + def twins_colors(self) -> str: return f"{self.twins} Beyond" """ @@ -227,7 +240,7 @@ def twins_colors(self) -> Optional[str]: """ @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """Textbox Beyond group.""" return psd.getLayerSet(f"{LAYERS.TEXTBOX} Beyond") @@ -236,5 +249,5 @@ def textbox_group(self) -> LayerSet: """ @cached_property - def textbox_shape(self) -> Optional[ArtLayer]: + def textbox_shape(self) -> ArtLayer | None: return psd.getLayer(LAYERS.NORMAL, [self.textbox_group, LAYERS.SHAPE]) diff --git a/src/templates/case.py b/src/templates/case.py index f607a513..bff38da5 100644 --- a/src/templates/case.py +++ b/src/templates/case.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Callable +from collections.abc import Callable from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -9,7 +9,7 @@ from src.helpers.layers import getLayer, getLayerSet from src.helpers.position import position_dividers, spread_layers_over_reference from src.helpers.text import scale_text_layers_to_height -from src.layouts import CaseLayout +from src.layouts import CaseLayout, NormalLayout from src.templates._core import NormalTemplate from src.text_layers import FormattedTextField @@ -22,10 +22,18 @@ class CaseMod(NormalTemplate): * Evenly spaced ability sections and dividers. """ - def __init__(self, layout: CaseLayout, **kwargs: None): - self.line_layers: list[ArtLayer] = [] - self.divider_layers: list[ArtLayer] = [] - super().__init__(layout, **kwargs) + def __init__(self, layout: NormalLayout): + self._case_line_layers: list[ArtLayer] = [] + self.case_divider_layers: list[ArtLayer | LayerSet] = [] + super().__init__(layout) + + @property + def case_line_layers(self) -> list[ArtLayer]: + return self._case_line_layers + + @case_line_layers.setter + def case_line_layers(self, value: list[ArtLayer]) -> None: + self._case_line_layers = value """ * Checks @@ -93,25 +101,27 @@ def rules_text_and_pt_layers(self) -> None: def text_layers_case(self) -> None: """Add and modify text layers relating to Case type cards.""" - - skip_divider_for = len(self.layout.case_lines) - 1 - - # Add text fields for each line - for i, line in enumerate(self.layout.case_lines): - # Create a new ability line - if layer := self.text_layer_ability: - line_layer: ArtLayer = layer if i == 0 else layer.duplicate() - self.line_layers.append(line_layer) - self.text.append(FormattedTextField(layer=line_layer, contents=line)) - - # Use existing ability divider or create a new one - if i != skip_divider_for and (layer := self.case_ability_divider): - divider: ArtLayer = ( - self.case_ability_divider - if i == 0 - else self.case_ability_divider.duplicate() - ) - self.divider_layers.append(divider) + if isinstance(self.layout, CaseLayout): + skip_divider_for = len(self.layout.case_lines) - 1 + + # Add text fields for each line + for i, line in enumerate(self.layout.case_lines): + # Create a new ability line + if layer := self.text_layer_ability: + line_layer: ArtLayer = layer if i == 0 else layer.duplicate() + self.case_line_layers.append(line_layer) + self.text.append( + FormattedTextField(layer=line_layer, contents=line) + ) + + # Use existing ability divider or create a new one + if i != skip_divider_for and (layer := self.case_ability_divider): + divider: ArtLayer = ( + self.case_ability_divider + if i == 0 + else self.case_ability_divider.duplicate() + ) + self.case_divider_layers.append(divider) """ * Frame Layer Methods @@ -127,43 +137,43 @@ def frame_layers_case(self) -> None: def layer_positioning_case(self) -> None: """Positions and sizes Case ability layers and dividers.""" + if self.textbox_reference: + # Core vars + spacing = self.app.scale_by_dpi(80) + spaces = len(self.case_line_layers) + divider_height = ( + get_layer_height(self.case_divider_layers[0]) + if len(self.case_divider_layers) > 0 + else 0 + ) + ref_height: float | int = self.textbox_reference.dims["height"] + spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) + total_height = ref_height - spacing_total - # Core vars - spacing = self.app.scale_by_dpi(80) - spaces = len(self.line_layers) - divider_height = ( - get_layer_height(self.divider_layers[0]) - if len(self.divider_layers) > 0 - else 0 - ) - ref_height: float | int = self.textbox_reference.dims["height"] - spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) - total_height = ref_height - spacing_total - - # Resize text items till they fit in the available space - scale_text_layers_to_height( - text_layers=self.line_layers, ref_height=total_height - ) - - # Get the exact gap between each layer left over - layer_heights = sum([get_layer_height(lyr) for lyr in self.line_layers]) - gap = (ref_height - layer_heights) * (spacing / spacing_total) - inside_gap = (ref_height - layer_heights) * ( - (spacing + divider_height) / spacing_total - ) - - # Space lines evenly apart - spread_layers_over_reference( - layers=self.line_layers, - ref=self.textbox_reference, - gap=gap, - inside_gap=inside_gap, - ) - - # Position a divider between each ability line - if len(self.divider_layers) == len(self.line_layers) - 1: - position_dividers( - dividers=self.divider_layers, - layers=self.line_layers, - docref=self.docref, + # Resize text items till they fit in the available space + scale_text_layers_to_height( + text_layers=self.case_line_layers, ref_height=total_height ) + + # Get the exact gap between each layer left over + layer_heights = sum([get_layer_height(lyr) for lyr in self.case_line_layers]) + gap = (ref_height - layer_heights) * (spacing / spacing_total) + inside_gap = (ref_height - layer_heights) * ( + (spacing + divider_height) / spacing_total + ) + + # Space lines evenly apart + spread_layers_over_reference( + layers=self.case_line_layers, + ref=self.textbox_reference, + gap=gap, + inside_gap=inside_gap, + ) + + # Position a divider between each ability line + if len(self.case_divider_layers) == len(self.case_line_layers) - 1: + position_dividers( + dividers=self.case_divider_layers, + layers=self.case_line_layers, + docref=self.docref, + ) diff --git a/src/templates/classes.py b/src/templates/classes.py index 129a5647..5c9fd279 100644 --- a/src/templates/classes.py +++ b/src/templates/classes.py @@ -1,30 +1,34 @@ """ * CLASS TEMPLATES """ + # Standard Library Imports from functools import cached_property -from typing import Union, Optional, Callable +from collections.abc import Sequence, Callable # Third Party Imports -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports from src.enums.layers import LAYERS import src.helpers as psd -from src.layouts import ClassLayout -from src.schema.colors import pinlines_color_map +from src.schema.colors import GradientConfig +from src.helpers.layers import get_reference_layer +from src.layouts import ClassLayout, NormalLayout +from src.schema.colors import ColorObject, pinlines_color_map from src.templates._core import NormalTemplate from src.templates._cosmetic import VectorNyxMod -from src.templates._vector import VectorTemplate +from src.templates._vector import MaskAction, VectorTemplate from src.text_layers import FormattedTextField, TextField +from src.utils.adobe import ReferenceLayer """ * Modifier Classes """ -class ClassMod (NormalTemplate): +class ClassMod(NormalTemplate): """ * A template modifier for Class cards introduced in Adventures in the Forgotten Realms. * Utilizes similar automated positioning techniques as Planeswalker templates. @@ -35,10 +39,10 @@ class ClassMod (NormalTemplate): * A positioning step to evenly space the abilities and stage dividers. """ - def __init__(self, layout: ClassLayout, **kwargs): - self._line_layers: list[ArtLayer] = [] - self._stage_layers: list[LayerSet] = [] - super().__init__(layout, **kwargs) + def __init__(self, layout: NormalLayout): + self._class_line_layers: list[ArtLayer] = [] + self._class_stage_layers: list[LayerSet] = [] + super().__init__(layout) """ * Checks @@ -54,19 +58,19 @@ def is_class_layout(self) -> bool: """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Class text layers.""" funcs = [self.text_layers_classes] if self.is_class_layout else [] return [*super().text_layer_methods, *funcs] @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add Class text layers.""" funcs = [self.frame_layers_classes] if self.is_class_layout else [] return [*super().frame_layer_methods, *funcs] @cached_property - def post_text_methods(self) -> list[Callable]: + def post_text_methods(self) -> list[Callable[[], None]]: """Position Class abilities and stage dividers.""" funcs = [self.layer_positioning_classes] if self.is_class_layout else [] return [*super().post_text_methods, *funcs] @@ -76,11 +80,11 @@ def post_text_methods(self) -> list[Callable]: """ @cached_property - def class_group(self) -> LayerSet: + def class_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.CLASS) @cached_property - def stage_group(self) -> LayerSet: + def stage_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.STAGE, self.class_group) """ @@ -88,7 +92,7 @@ def stage_group(self) -> LayerSet: """ @cached_property - def text_layer_ability(self) -> ArtLayer: + def text_layer_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.TEXT, self.class_group) """ @@ -96,24 +100,24 @@ def text_layer_ability(self) -> ArtLayer: """ @property - def line_layers(self) -> list[ArtLayer]: - return self._line_layers + def class_line_layers(self) -> list[ArtLayer]: + return self._class_line_layers - @line_layers.setter - def line_layers(self, value): - self._line_layers = value + @class_line_layers.setter + def class_line_layers(self, value: list[ArtLayer]): + self._class_line_layers = value """ * Class Stage Dividers """ @property - def stage_layers(self) -> list[LayerSet]: - return self._stage_layers + def class_stage_layers(self) -> list[LayerSet]: + return self._class_stage_layers - @stage_layers.setter - def stage_layers(self, value): - self._stage_layers = value + @class_stage_layers.setter + def class_stage_layers(self, value: list[LayerSet]): + self._class_stage_layers = value """ * Text Layer Methods @@ -129,33 +133,39 @@ def rules_text_and_pt_layers(self) -> None: def text_layers_classes(self) -> None: """Add and modify text layers relating to Class type cards.""" - - # Add first static line - self.line_layers.append(self.text_layer_ability) - self.text.append( - FormattedTextField( - layer=self.text_layer_ability, - contents=self.layout.class_lines[0]['text'] - )) - - # Add text fields for each line and class stage - for i, line in enumerate(self.layout.class_lines[1:]): - - # Create a new ability line - line_layer = self.text_layer_ability.duplicate() - self.line_layers.append(line_layer) - - # Use existing stage divider or create new one - stage = self.stage_group if i == 0 else self.stage_group.duplicate() - cost, level = [*stage.artLayers][:2] - self.stage_layers.append(stage) - - # Add text layers to be formatted - self.text.extend([ - FormattedTextField(layer=line_layer, contents=line['text']), - FormattedTextField(layer=cost, contents=f"{line['cost']}:"), - TextField(layer=level, contents=f"Level {line['level']}") - ]) + if isinstance(self.layout, ClassLayout) and self.text_layer_ability: + # Add first static line + self.class_line_layers.append(self.text_layer_ability) + self.text.append( + FormattedTextField( + layer=self.text_layer_ability, + contents=self.layout.class_lines[0]["text"], + ) + ) + + # Add text fields for each line and class stage + for i, line in enumerate(self.layout.class_lines[1:]): + # Create a new ability line + line_layer = self.text_layer_ability.duplicate() + self.class_line_layers.append(line_layer) + + self.text.append( + FormattedTextField(layer=line_layer, contents=line["text"]) + ) + + if self.stage_group: + # Use existing stage divider or create new one + stage = self.stage_group if i == 0 else self.stage_group.duplicate() + cost, level = [*stage.artLayers][:2] + self.class_stage_layers.append(stage) + + # Add text layers to be formatted + self.text.extend( + [ + FormattedTextField(layer=cost, contents=f"{line['cost']}:"), + TextField(layer=level, contents=f"Level {line['level']}"), + ] + ) """ * Class Frame Layer Methods @@ -171,37 +181,39 @@ def frame_layers_classes(self) -> None: def layer_positioning_classes(self) -> None: """Positions and sizes class ability layers and stage dividers.""" - - # Core vars - spacing = self.app.scale_by_dpi(80) - spaces = len(self.line_layers) - 1 - divider_height = psd.get_layer_height(self.stage_layers[0]) - ref_height = self.textbox_reference.dims['height'] - spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) - total_height = ref_height - spacing_total - - # Resize text items till they fit in the available space - psd.scale_text_layers_to_height( - text_layers=self.line_layers, - ref_height=total_height) - - # Get the exact gap between each layer left over - layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.line_layers]) - gap = (ref_height - layer_heights) * (spacing / spacing_total) - inside_gap = (ref_height - layer_heights) * ((spacing + divider_height) / spacing_total) - - # Space Class lines evenly apart - psd.spread_layers_over_reference( - layers=self.line_layers, - ref=self.textbox_reference, - gap=gap, - inside_gap=inside_gap) - - # Position a class stage between each ability line - psd.position_dividers( - dividers=self.stage_layers, - layers=self.line_layers, - docref=self.docref) + if self.textbox_reference: + # Core vars + spacing = self.app.scale_by_dpi(80) + spaces = len(self.class_line_layers) - 1 + divider_height = psd.get_layer_height(self.class_stage_layers[0]) + ref_height = self.textbox_reference.dims["height"] + spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2) + total_height = ref_height - spacing_total + + # Resize text items till they fit in the available space + psd.scale_text_layers_to_height( + text_layers=self.class_line_layers, ref_height=total_height + ) + + # Get the exact gap between each layer left over + layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.class_line_layers]) + gap = (ref_height - layer_heights) * (spacing / spacing_total) + inside_gap = (ref_height - layer_heights) * ( + (spacing + divider_height) / spacing_total + ) + + # Space Class lines evenly apart + psd.spread_layers_over_reference( + layers=self.class_line_layers, + ref=self.textbox_reference, + gap=gap, + inside_gap=inside_gap, + ) + + # Position a class stage between each ability line + psd.position_dividers( + dividers=self.class_stage_layers, layers=self.class_line_layers, docref=self.docref + ) """ @@ -209,7 +221,7 @@ def layer_positioning_classes(self) -> None: """ -class ClassVectorTemplate (VectorNyxMod, ClassMod, VectorTemplate): +class ClassVectorTemplate(VectorNyxMod, ClassMod, VectorTemplate): """Class template using vector shape layers and automatic pinlines / multicolor generation.""" """ @@ -226,31 +238,36 @@ def is_name_shifted(self) -> bool: """ @cached_property - def textbox_colors(self) -> list[str]: + def textbox_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """list[str]: Support back side texture names.""" colors = list(self.identity) if self.is_within_color_limit else [self.pinlines] # Is this card a back face transform? if self.is_transform and not self.is_front: - return [f'{n} {LAYERS.BACK}' for n in colors] + return [f"{n} {LAYERS.BACK}" for n in colors] return colors @cached_property - def crown_colors(self) -> Union[list[int], list[dict]]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Return RGB notation or Gradient dict notation for adjustment layers.""" return psd.get_pinline_gradient( - colors=self.pinlines, color_map=self.crown_color_map) + colors=self.pinlines, color_map=self.crown_color_map + ) """ * Groups """ @cached_property - def crown_group(self) -> LayerSet: + def crown_group(self) -> LayerSet | None: """Use inner shape group for Legendary Crown.""" return psd.getLayerSet(LAYERS.SHAPE, [self.docref, LAYERS.LEGENDARY_CROWN]) @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """Must enable textbox group.""" if group := psd.getLayerSet(LAYERS.TEXTBOX, self.docref): group.visible = True @@ -261,30 +278,33 @@ def textbox_group(self) -> LayerSet: """ @cached_property - def twins_layer(self) -> Optional[ArtLayer]: + def twins_layer(self) -> ArtLayer | None: # Use Back face versions for back side Transform return psd.getLayer( - f"{self.twins} {LAYERS.BACK}" if self.is_transform and not self.is_front else self.twins, - self.twins_group) + f"{self.twins} {LAYERS.BACK}" + if self.is_transform and not self.is_front + else self.twins, + self.twins_group, + ) """ * References """ @cached_property - def art_reference(self) -> ArtLayer: - return psd.getLayer(LAYERS.ART_FRAME + " Left") + def art_reference(self) -> ReferenceLayer | None: + return get_reference_layer(LAYERS.ART_FRAME + " Left") @cached_property - def textbox_reference(self) -> Optional[ArtLayer]: + def textbox_reference(self) -> ReferenceLayer | None: if self.is_front and self.is_flipside_creature: return psd.get_reference_layer( - f'{LAYERS.TEXTBOX_REFERENCE} {LAYERS.TRANSFORM_FRONT}', - self.class_group) + f"{LAYERS.TEXTBOX_REFERENCE} {LAYERS.TRANSFORM_FRONT}", self.class_group + ) return psd.get_reference_layer(LAYERS.TEXTBOX_REFERENCE, self.class_group) @cached_property - def textbox_position_reference(self) -> Optional[ArtLayer]: + def textbox_position_reference(self) -> ArtLayer | None: return psd.getLayer(LAYERS.ART_FRAME + " Right") """ @@ -294,62 +314,78 @@ def textbox_position_reference(self) -> Optional[ArtLayer]: @cached_property def textbox_masks(self) -> list[ArtLayer]: """Blends the textbox colors.""" - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.TEXTBOX])] + if layer := psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.TEXTBOX]): + return [layer] + return [] @cached_property def background_masks(self) -> list[ArtLayer]: """Blends the background colors.""" - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BACKGROUND])] + if layer := psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BACKGROUND]): + return [layer] + return [] """ * Shapes """ @cached_property - def border_shape(self) -> Optional[ArtLayer]: + def border_shape(self) -> ArtLayer | None: """Support a Normal and Legendary border for front-face Transform.""" if self.is_transform and self.is_front: return psd.getLayer( f"{LAYERS.LEGENDARY if self.is_legendary else LAYERS.NORMAL} {LAYERS.TRANSFORM_FRONT}", - self.border_group) + self.border_group, + ) return super().border_shape @cached_property def pinlines_shapes(self) -> list[LayerSet]: """Support front and back face Transform pinlines, and optional Legendary pinline shape.""" - shapes = [psd.getLayerSet(LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE])] if self.is_legendary else [] - return [ - # Normal or Transform pinline - psd.getLayerSet( - (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) - if self.is_transform else LAYERS.NORMAL, - [self.pinlines_group, LAYERS.SHAPE] - ), *shapes - ] + shapes: list[LayerSet] = [] + if self.is_legendary and ( + group := psd.getLayerSet( + LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE] + ) + ): + shapes.append(group) + if group := psd.getLayerSet( + (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) + if self.is_transform + else LAYERS.NORMAL, + [self.pinlines_group, LAYERS.SHAPE], + ): + shapes.append(group) + return shapes @cached_property - def twins_shape(self) -> ArtLayer: + def twins_shape(self) -> ArtLayer | None: """Support both front and back face Transform shapes.""" return psd.getLayer( (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) - if self.is_transform else LAYERS.NORMAL, - [self.twins_group, LAYERS.SHAPE]) + if self.is_transform + else LAYERS.NORMAL, + [self.twins_group, LAYERS.SHAPE], + ) @cached_property - def outline_shape(self): + def outline_shape(self) -> ArtLayer | None: """Outline for the textbox and art.""" return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - LAYERS.OUTLINE) + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + LAYERS.OUTLINE, + ) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """Add support for outline shape and multiple pinlines shapes.""" return [ *self.pinlines_shapes, self.outline_shape, self.border_shape, - self.twins_shape + self.twins_shape, ] """ @@ -357,17 +393,26 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: """ @cached_property - def pinlines_mask(self) -> list[Union[ArtLayer, LayerSet]]: + def pinlines_mask( + self, + ) -> tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | None: """Mask hiding pinlines effects inside textbox and art frame.""" - return [ - psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.mask_group, LAYERS.PINLINES]), - self.pinlines_group - ] + if ( + layer := psd.getLayer( + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.mask_group, LAYERS.PINLINES], + ) + ) and self.pinlines_group: + return (layer, self.pinlines_group) @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + def enabled_masks( + self, + ) -> list[ + MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None + ]: """Support a pinlines mask.""" return [self.pinlines_mask] @@ -381,8 +426,8 @@ def enable_frame_layers(self) -> None: # Merge the textbox and shift it to right half psd.merge_group(self.textbox_group) psd.align_horizontal( - layer=self.active_layer, - ref=self.textbox_position_reference) + layer=self.active_layer, ref=self.textbox_position_reference + ) """ * Class Frame Layer Methods @@ -391,28 +436,35 @@ def enable_frame_layers(self) -> None: def frame_layers_classes(self): """Enable layers relating to Class type cards.""" - # Enable class group, disable saga banner - self.class_group.visible = True - psd.getLayerSet("Banner Top").visible = False + # Enable class group + if self.class_group: + self.class_group.visible = True + + # Disable Saga banner + if layer := psd.getLayerSet("Banner Top"): + layer.visible = False class UniversesBeyondClassTemplate(ClassVectorTemplate): """Saga Vector template with Universes Beyond frame treatment.""" - template_suffix = 'Universes Beyond' + + template_suffix = "Universes Beyond" # Color Maps - pinlines_color_map = { - **pinlines_color_map.copy(), - 'W': [246, 247, 241], - 'U': [0, 131, 193], - 'B': [44, 40, 33], - 'R': [237, 66, 31], - 'G': [5, 129, 64], - 'Gold': [239, 209, 107], - 'Land': [165, 150, 132], - 'Artifact': [227, 228, 230], - 'Colorless': [227, 228, 230] - } + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (246, 247, 241), + "U": (0, 131, 193), + "B": (44, 40, 33), + "R": (237, 66, 31), + "G": (5, 129, 64), + "Gold": (239, 209, 107), + "Land": (165, 150, 132), + "Artifact": (227, 228, 230), + "Colorless": (227, 228, 230), + } """ * Colors @@ -428,20 +480,20 @@ def textbox_colors(self) -> list[str]: @cached_property def twins_colors(self) -> str: """str: Universes Beyond variant texture name.""" - return f'{self.twins} Beyond' + return f"{self.twins} Beyond" """ * Groups """ @cached_property - def background_group(self) -> LayerSet: + def background_group(self) -> LayerSet | None: """LayerSet: Universes Beyond variant group.""" - return psd.getLayerSet(f'{LAYERS.BACKGROUND} Beyond') + return psd.getLayerSet(f"{LAYERS.BACKGROUND} Beyond") @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """LayerSet: Universes Beyond variant group. Must be enabled.""" - group = psd.getLayerSet(f"{LAYERS.TEXTBOX} Beyond") - group.visible = True - return group + if group := psd.getLayerSet(f"{LAYERS.TEXTBOX} Beyond"): + group.visible = True + return group diff --git a/src/templates/leveler.py b/src/templates/leveler.py index b8dab13c..0db7d2cb 100644 --- a/src/templates/leveler.py +++ b/src/templates/leveler.py @@ -1,9 +1,10 @@ """ * LEVELER TEMPLATES """ + # Standard Library from functools import cached_property -from typing import Optional, Callable +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -45,7 +46,7 @@ def is_leveler(self) -> bool: """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Adventure text layers.""" funcs = [self.text_layers_leveler] if self.is_leveler else [] return [*super().text_layer_methods, *funcs] @@ -55,7 +56,7 @@ def text_layer_methods(self) -> list[Callable]: """ @cached_property - def leveler_group(self) -> Optional[LayerSet]: + def leveler_group(self) -> LayerSet | None: """Group containing Leveler text layers.""" return psd.getLayerSet("Leveler Text", self.text_group) @@ -64,7 +65,7 @@ def leveler_group(self) -> Optional[LayerSet]: """ @cached_property - def pt_layer(self) -> Optional[ArtLayer]: + def pt_layer(self) -> ArtLayer | None: if self.is_leveler: return psd.getLayer(self.twins, LAYERS.PT_AND_LEVEL_BOXES) return super().pt_layer @@ -74,13 +75,13 @@ def pt_layer(self) -> Optional[ArtLayer]: """ @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: if self.is_leveler: return psd.getLayer("Rules Text - Level Up", self.leveler_group) return super().text_layer_rules @cached_property - def text_layer_pt(self) -> Optional[ArtLayer]: + def text_layer_pt(self) -> ArtLayer | None: if self.is_leveler: return psd.getLayer("Top Power / Toughness", self.leveler_group) return super().text_layer_pt @@ -90,27 +91,27 @@ def text_layer_pt(self) -> Optional[ArtLayer]: """ @cached_property - def text_layer_rules_x_y(self) -> Optional[ArtLayer]: + def text_layer_rules_x_y(self) -> ArtLayer | None: return psd.getLayer("Rules Text - Levels X-Y", self.leveler_group) @cached_property - def text_layer_rules_z(self) -> Optional[ArtLayer]: + def text_layer_rules_z(self) -> ArtLayer | None: return psd.getLayer("Rules Text - Levels Z+", self.leveler_group) @cached_property - def text_layer_level_middle(self) -> Optional[ArtLayer]: + def text_layer_level_middle(self) -> ArtLayer | None: return psd.getLayer("Middle Level", self.leveler_group) @cached_property - def text_layer_level_bottom(self) -> Optional[ArtLayer]: + def text_layer_level_bottom(self) -> ArtLayer | None: return psd.getLayer("Bottom Level", self.leveler_group) @cached_property - def text_layer_pt_middle(self) -> Optional[ArtLayer]: + def text_layer_pt_middle(self) -> ArtLayer | None: return psd.getLayer("Middle Power / Toughness", self.leveler_group) @cached_property - def text_layer_pt_bottom(self) -> Optional[ArtLayer]: + def text_layer_pt_bottom(self) -> ArtLayer | None: return psd.getLayer("Bottom Power / Toughness", self.leveler_group) """ @@ -118,9 +119,11 @@ def text_layer_pt_bottom(self) -> Optional[ArtLayer]: """ @cached_property - def textbox_reference(self) -> Optional[ReferenceLayer]: + def textbox_reference(self) -> ReferenceLayer | None: if self.is_leveler: - return psd.get_reference_layer(f'{LAYERS.TEXTBOX_REFERENCE} - Level Text', self.leveler_group) + return psd.get_reference_layer( + f"{LAYERS.TEXTBOX_REFERENCE} - Level Text", self.leveler_group + ) return super().textbox_reference """ @@ -128,12 +131,16 @@ def textbox_reference(self) -> Optional[ReferenceLayer]: """ @cached_property - def textbox_reference_x_y(self) -> Optional[ArtLayer]: - return psd.get_reference_layer(f'{LAYERS.TEXTBOX_REFERENCE} - Level X-Y', self.leveler_group) + def textbox_reference_x_y(self) -> ArtLayer | None: + return psd.get_reference_layer( + f"{LAYERS.TEXTBOX_REFERENCE} - Level X-Y", self.leveler_group + ) @cached_property - def textbox_reference_z(self) -> Optional[ArtLayer]: - return psd.get_reference_layer(f'{LAYERS.TEXTBOX_REFERENCE} - Levels Z+', self.leveler_group) + def textbox_reference_z(self) -> ArtLayer | None: + return psd.get_reference_layer( + f"{LAYERS.TEXTBOX_REFERENCE} - Levels Z+", self.leveler_group + ) """ * Leveler Text Methods @@ -141,57 +148,81 @@ def textbox_reference_z(self) -> Optional[ArtLayer]: def rules_text_and_pt_layers(self) -> None: """Add rules and power/toughness text.""" - - if self.is_leveler: + + if isinstance(self.layout, LevelerLayout): # Level-Up text and starting P/T - self.text.extend([ - text_classes.FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.level_up_text, - reference=self.textbox_reference - ), - text_classes.TextField( - layer=self.text_layer_pt, - contents=str(self.layout.power) + "/" + str(self.layout.toughness) + if self.text_layer_rules: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.level_up_text, + reference=self.textbox_reference, + ) + ) + if self.text_layer_pt: + self.text.append( + text_classes.TextField( + layer=self.text_layer_pt, + contents=str(self.layout.power) + + "/" + + str(self.layout.toughness), + ) ) - ]) else: return super().rules_text_and_pt_layers() def text_layers_leveler(self): """Add and modify text layers required by Leveler cards.""" + if isinstance(self.layout, LevelerLayout): + # Add Leveler sections - # Add Leveler sections - self.text.extend([ # Level 2 - text_classes.TextField( - layer=self.text_layer_level_middle, - contents=self.layout.middle_level - ), - text_classes.TextField( - layer=self.text_layer_pt_middle, - contents=self.layout.middle_power_toughness - ), - text_classes.FormattedTextArea( - layer=self.text_layer_rules_x_y, - contents=self.layout.middle_text, - reference=self.textbox_reference_x_y - ), + if self.text_layer_level_middle: + self.text.append( + text_classes.TextField( + layer=self.text_layer_level_middle, + contents=self.layout.middle_level, + ) + ) + if self.text_layer_pt_middle: + self.text.append( + text_classes.TextField( + layer=self.text_layer_pt_middle, + contents=self.layout.middle_power_toughness, + ) + ) + if self.text_layer_rules_x_y: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules_x_y, + contents=self.layout.middle_text, + reference=self.textbox_reference_x_y, + ) + ) + # Level 3 - text_classes.TextField( - layer=self.text_layer_level_bottom, - contents=self.layout.bottom_level - ), - text_classes.TextField( - layer=self.text_layer_pt_bottom, - contents=self.layout.bottom_power_toughness - ), - text_classes.FormattedTextArea( - layer=self.text_layer_rules_z, - contents=self.layout.bottom_text, - reference=self.textbox_reference_z - ) - ]) + if self.text_layer_level_bottom: + self.text.append( + text_classes.TextField( + layer=self.text_layer_level_bottom, + contents=self.layout.bottom_level, + ) + ) + if self.text_layer_pt_bottom: + self.text.append( + text_classes.TextField( + layer=self.text_layer_pt_bottom, + contents=self.layout.bottom_power_toughness, + ) + ) + if self.text_layer_rules_z: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules_z, + contents=self.layout.bottom_text, + reference=self.textbox_reference_z, + ) + ) """ diff --git a/src/templates/mdfc.py b/src/templates/mdfc.py index 4b9a9960..052470de 100644 --- a/src/templates/mdfc.py +++ b/src/templates/mdfc.py @@ -3,7 +3,7 @@ """ # Standard Library from functools import cached_property -from typing import Optional, Callable +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -35,13 +35,13 @@ class MDFCMod(BaseTemplate): """ @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[],None]]: """Add MDFC frame layers step.""" parent_funcs = super().frame_layer_methods return [*parent_funcs, self.enable_mdfc_layers] if self.is_mdfc else parent_funcs @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[],None]]: """Add MDFC text layers step.""" parent_funcs = super().text_layer_methods return [*parent_funcs, self.text_layers_mdfc] if self.is_mdfc else parent_funcs @@ -69,12 +69,12 @@ def mdfc_bar_color(self) -> str: """ @cached_property - def text_layer_mdfc_left(self) -> Optional[ArtLayer]: + def text_layer_mdfc_left(self) -> ArtLayer | None: """The back face card type.""" return psd.getLayer(LAYERS.LEFT, self.dfc_group) @cached_property - def text_layer_mdfc_right(self) -> Optional[ArtLayer]: + def text_layer_mdfc_right(self) -> ArtLayer | None: """The back face mana cost or land tap ability.""" return psd.getLayer(LAYERS.RIGHT, self.dfc_group) @@ -86,8 +86,10 @@ def enable_mdfc_layers(self) -> None: """Enable layers that are required by modal double faced cards.""" # MDFC elements at the top and bottom of the card - psd.getLayer(self.mdfc_icon_color, [self.dfc_group, LAYERS.TOP]).visible = True - psd.getLayer(self.mdfc_bar_color, [self.dfc_group, LAYERS.BOTTOM]).visible = True + if layer := psd.getLayer(self.mdfc_icon_color, [self.dfc_group, LAYERS.TOP]): + layer.visible = True + if layer := psd.getLayer(self.mdfc_bar_color, [self.dfc_group, LAYERS.BOTTOM]): + layer.visible = True # Front and back side layers if self.is_front: @@ -110,14 +112,16 @@ def text_layers_mdfc(self) -> None: """Adds and modifies text layers required by modal double faced cards.""" # Add mdfc text layers - self.text.extend([ - FormattedTextField( + if self.text_layer_mdfc_right: + self.text.append(FormattedTextField( layer=self.text_layer_mdfc_right, - contents=self.layout.other_face_right), - ScaledTextField( - layer=self.text_layer_mdfc_left, - contents=self.layout.other_face_left, - reference=self.text_layer_mdfc_right)]) + contents=self.layout.other_face_right)) + if self.text_layer_mdfc_left: + self.text.append( + ScaledTextField( + layer=self.text_layer_mdfc_left, + contents=self.layout.other_face_left, + reference=self.text_layer_mdfc_right)) # Front and back side layers if self.is_front: @@ -142,7 +146,8 @@ class VectorMDFCMod(MDFCMod, VectorTemplate): def enable_mdfc_layers(self) -> None: """Enable group containing MDFC layers.""" - self.dfc_group.visible = True + if self.dfc_group: + self.dfc_group.visible = True super().enable_mdfc_layers() diff --git a/src/templates/mutate.py b/src/templates/mutate.py index da3dc9b5..0d754811 100644 --- a/src/templates/mutate.py +++ b/src/templates/mutate.py @@ -3,10 +3,10 @@ """ # Standard Library from functools import cached_property -from typing import Optional, Callable +from collections.abc import Callable # Third Party Imports -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer # Local Imports from src import CFG @@ -36,7 +36,7 @@ class MutateMod(NormalTemplate): """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[],None]]: """Add Mutate text layers.""" funcs = [self.text_layers_mutate] if isinstance(self.layout, MutateLayout) else [] return [*super().text_layer_methods, *funcs] @@ -46,7 +46,7 @@ def text_layer_methods(self) -> list[Callable]: """ @cached_property - def text_layer_mutate(self) -> Optional[ArtLayer]: + def text_layer_mutate(self) -> ArtLayer | None: """Text layer containing the mutate text.""" return psd.getLayer(LAYERS.MUTATE, self.text_group) @@ -55,7 +55,7 @@ def text_layer_mutate(self) -> Optional[ArtLayer]: """ @cached_property - def mutate_reference(self) -> Optional[ReferenceLayer]: + def mutate_reference(self) -> ReferenceLayer | None: """Mutate textbox reference.""" return psd.get_reference_layer(LAYERS.MUTATE_REFERENCE, self.text_group) @@ -65,7 +65,7 @@ def mutate_reference(self) -> Optional[ReferenceLayer]: def process_layout_data(self) -> None: """Remove reminder text for mutate text if required.""" - if CFG.remove_reminder: + if CFG.remove_reminder and isinstance(self.layout, MutateLayout): self.layout.mutate_text = strip_reminder_text( self.layout.mutate_text) super().process_layout_data() @@ -78,11 +78,12 @@ def text_layers_mutate(self): """Add text layers required by Mutate cards.""" # Add mutate text - self.text.append( - text_classes.FormattedTextArea( - layer=self.text_layer_mutate, - contents=self.layout.mutate_text, - reference=self.mutate_reference)) + if isinstance(self.layout, MutateLayout) and self.text_layer_mutate: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_mutate, + contents=self.layout.mutate_text, + reference=self.mutate_reference)) """ diff --git a/src/templates/normal.py b/src/templates/normal.py index 6a8fbef2..1fc1b246 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1,9 +1,10 @@ """ * Normal Templates """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Union +from collections.abc import Callable, Iterable, Sequence # Third Party Imports from photoshop.api import AnchorPosition, SolidColor @@ -12,24 +13,32 @@ # Local Imports from src import CFG, CON +from src.schema.colors import GradientConfig +from src.helpers.layers import get_reference_layer from src.schema.adobe import EffectColorOverlay, EffectBevel from src.templates import NicknameMod -from src.utils.adobe import ReferenceLayer, LayerObjectTypes +from src.utils.adobe import ReferenceLayer from src.enums.mtg import MagicIcons -from src.enums.adobe import Dimensions from src.enums.layers import LAYERS from src.enums.settings import ( BorderlessColorMode, BorderlessTextbox, ModernClassicCrown, - CollectorMode) + CollectorMode, +) from src.frame_logic import is_multicolor_string from src.helpers import get_line_count, LayerEffects import src.helpers as psd -from src.schema.colors import pinlines_color_map +from src.schema.colors import ColorObject, pinlines_color_map from src.templates._core import NormalTemplate -from src.templates._cosmetic import ExtendedMod, FullartMod, NyxMod, VectorBorderlessMod, CompanionMod -from src.templates._vector import VectorTemplate +from src.templates._cosmetic import ( + ExtendedMod, + FullartMod, + NyxMod, + VectorBorderlessMod, + CompanionMod, +) +from src.templates._vector import MaskAction, VectorTemplate from src.templates.transform import VectorTransformMod from src.templates.mdfc import VectorMDFCMod from src.text_layers import ( @@ -37,7 +46,8 @@ ScaledTextField, FormattedTextField, FormattedTextArea, - ScaledWidthTextField) + ScaledWidthTextField, +) """ * Extendable Templates @@ -71,7 +81,7 @@ class FullartTemplate(FullartMod, M15Template): """ @cached_property - def overlay_group(self) -> LayerSet: + def overlay_group(self) -> LayerSet | None: """Glass overlay that replaces textbox and twins.""" return psd.getLayerSet("Overlay") @@ -82,21 +92,30 @@ def overlay_group(self) -> LayerSet: def enable_frame_layers(self) -> None: """Mask textbox and type bar, add glass overlay.""" super().enable_frame_layers() - psd.enable_vector_mask(self.pinlines_layer.parent) - psd.enable_mask(self.twins_layer.parent) - self.overlay_group.visible = True + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) + if self.twins_layer and isinstance( + (parent := self.twins_layer.parent), LayerSet + ): + psd.enable_mask(parent) + if self.overlay_group: + self.overlay_group.visible = True def basic_text_layers(self) -> None: """White typeline.""" super().basic_text_layers() psd.enable_layer_fx(self.text_layer_type) - self.text_layer_type.textItem.color = psd.rgb_white() + if self.text_layer_type: + self.text_layer_type.textItem.color = psd.rgb_white() def rules_text_and_pt_layers(self) -> None: """White rules text and divider.""" super().rules_text_and_pt_layers() psd.enable_layer_fx(self.text_layer_rules) - self.text_layer_rules.textItem.color = psd.rgb_white() + if self.text_layer_rules: + self.text_layer_rules.textItem.color = psd.rgb_white() # Make the divider white if self.layout.flavor_text and self.layout.oracle_text and CFG.flavor_divider: @@ -105,23 +124,29 @@ def rules_text_and_pt_layers(self) -> None: class StargazingTemplate(FullartTemplate): """Stargazing template from 'Theros: Beyond Death' showcase cards. Always uses nyx backgrounds.""" - template_suffix = 'Stargazing' + + template_suffix = "Stargazing" # Static Properties - is_nyx = True - twins_layer = None + @cached_property + def is_nyx(self) -> bool: + return True + + @cached_property + def twins_layer(self) -> ArtLayer | None: + return None """ * Layers """ - @property - def pt_layer(self) -> Optional[ArtLayer]: + @cached_property + def pt_layer(self) -> ArtLayer | None: # Use darker PT boxes except for Vehicle if self.is_vehicle: if layer := psd.getLayer(LAYERS.VEHICLE, LAYERS.PT_BOX): return layer - return psd.getLayer(self.twins, LAYERS.PT_BOX + ' Dark') + return psd.getLayer(self.twins, LAYERS.PT_BOX + " Dark") """ * Methods @@ -132,25 +157,38 @@ def enable_frame_layers(self) -> None: super(FullartTemplate, self).enable_frame_layers() # Glass cutouts - psd.enable_vector_mask(self.background_layer.parent) - psd.enable_vector_mask(self.pinlines_layer.parent) + if self.background_layer and isinstance( + (parent := self.background_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) # Glass overlays - self.overlay_group.visible = True - psd.getLayer("Name", self.overlay_group).visible = True + if self.overlay_group: + self.overlay_group.visible = True + if layer := psd.getLayer("Name", self.overlay_group): + layer.visible = True def enable_crown(self) -> None: super().enable_crown() - psd.enable_vector_mask(self.crown_layer.parent) + if self.crown_layer and isinstance( + (parent := self.crown_layer.parent), LayerSet + ): + psd.enable_vector_mask(parent) def basic_text_layers(self) -> None: super().basic_text_layers() - psd.enable_layer_fx(self.text_layer_name) - self.text_layer_name.textItem.color = psd.rgb_white() + + if self.text_layer_name: + psd.enable_layer_fx(self.text_layer_name) + self.text_layer_name.textItem.color = psd.rgb_white() def rules_text_and_pt_layers(self) -> None: super().rules_text_and_pt_layers() - if self.is_creature: + if self.is_creature and self.text_layer_pt: self.text_layer_pt.textItem.color = psd.rgb_white() @@ -168,7 +206,7 @@ class ExtendedTemplate(ExtendedMod, M15Template): @cached_property def template_suffix(self) -> str: - return 'Dark Mode' if self.is_dark_mode else '' + return "Dark Mode" if self.is_dark_mode else "" """ * Bool @@ -177,7 +215,7 @@ def template_suffix(self) -> str: @cached_property def is_dark_mode(self) -> bool: """Governs whether Dark Mode is enabled.""" - return bool(CFG.get_setting('FRAME', 'Dark.Mode', default=False)) + return bool(CFG.get_bool_setting("FRAME", "Dark.Mode", default=False)) """ * Methods @@ -188,7 +226,7 @@ def rules_text_and_pt_layers(self) -> None: super().rules_text_and_pt_layers() # White rules text for dark mode - if self.is_dark_mode: + if self.is_dark_mode and self.text_layer_rules: self.text_layer_rules.textItem.color = psd.rgb_white() def enable_frame_layers(self) -> None: @@ -197,19 +235,27 @@ def enable_frame_layers(self) -> None: # Dark mode changes if self.is_dark_mode: - psd.getLayer("Dark", "Overlays").visible = True + if layer := psd.getLayer("Dark", "Overlays"): + layer.visible = True # White divider - if self.layout.flavor_text and self.layout.oracle_text and CFG.flavor_divider: + if ( + self.layout.flavor_text + and self.layout.oracle_text + and CFG.flavor_divider + ): psd.enable_layer_fx(self.divider_layer) class InventionTemplate(FullartMod, NormalTemplate): """Kaladesh Invention template. Uses either Bronze or Silver frame layers depending on setting.""" - template_suffix = 'Masterpiece' + + template_suffix = "Masterpiece" # Static Properties - is_land = False + @cached_property + def is_land(self) -> bool: + return False """ * Frame Details @@ -218,12 +264,11 @@ class InventionTemplate(FullartMod, NormalTemplate): @cached_property def twins(self) -> str: """str: Pull twins color from settings. Silver or Bronze.""" - return str(CFG.get_setting( - section="FRAME", - key="Accent", - default="Silver", - is_bool=False - )) + return str( + CFG.get_setting( + section="FRAME", key="Accent", default="Silver", is_bool=False + ) + ) @cached_property def background(self) -> str: @@ -235,18 +280,24 @@ class ExpeditionTemplate(FullartMod, NormalTemplate): """Zendikar Rising Expedition template. Masks pinlines for legendary cards, has a single static background layer, doesn't support color indicator, companion, or nyx layers. """ - template_suffix = 'Expedition' + + template_suffix = "Expedition" # Static Properties - is_land = False - background_layer = None + @cached_property + def is_land(self) -> bool: + return False + + @cached_property + def background_layer(self) -> ArtLayer | None: + return None """ * Layers """ @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """No separate creature text layer.""" return psd.getLayer(LAYERS.RULES_TEXT, self.text_group) @@ -258,22 +309,26 @@ def rules_text_and_pt_layers(self) -> None: """Add rules and power/toughness text.""" # No Creature-specific rules text - self.text.append( - FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - flavor=self.layout.flavor_text, - reference=self.textbox_reference, - divider=self.divider_layer, - centered=self.is_centered)) + if self.text_layer_rules: + self.text.append( + FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + flavor=self.layout.flavor_text, + reference=self.textbox_reference, + divider=self.divider_layer, + centered=self.is_centered, + ) + ) # Add PT for Creature - if self.is_creature: + if self.is_creature and self.text_layer_pt: self.text.append( TextField( layer=self.text_layer_pt, - contents=f'{self.layout.power}/' - f'{self.layout.toughness}')) + contents=f"{self.layout.power}/{self.layout.toughness}", + ) + ) """ * Frame Layer Methods @@ -281,31 +336,44 @@ def rules_text_and_pt_layers(self) -> None: def enable_crown(self): # No hollow crown - self.crown_layer.visible = True - psd.getLayer(LAYERS.NORMAL_BORDER, self.border_group).visible = False - psd.getLayer(LAYERS.LEGENDARY_BORDER, self.border_group).visible = True + if self.crown_layer: + self.crown_layer.visible = True + if layer := psd.getLayer(LAYERS.NORMAL_BORDER, self.border_group): + layer.visible = False + if layer := psd.getLayer(LAYERS.LEGENDARY_BORDER, self.border_group): + layer.visible = True # Enable legendary cutout on background and pinlines - psd.enable_mask(psd.getLayer('Background')) - psd.enable_mask(self.pinlines_layer.parent) + psd.enable_mask(psd.getLayer("Background")) + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_mask(parent) class SnowTemplate(NormalTemplate): """A snow template with textures from Kaldheim's snow cards.""" - template_suffix = 'Snow' + + template_suffix = "Snow" class MiracleTemplate(NyxMod, NormalTemplate): """A template for miracle cards introduced in Avacyn Restored.""" # Static Properties - is_legendary = False - is_vehicle = False + @cached_property + def is_legendary(self) -> bool: + return False + + @cached_property + def is_vehicle(self) -> bool: + return False class ClassicTemplate(NormalTemplate): """A template for 7th Edition frame. Lacks some of the Normal Template features.""" - frame_suffix = 'Classic' + + frame_suffix = "Classic" """ * Frame Details @@ -314,7 +382,7 @@ class ClassicTemplate(NormalTemplate): @cached_property def template_suffix(self) -> str: """Add Promo if promo star enabled.""" - return 'Promo' if self.is_promo_star else '' + return "Promo" if self.is_promo_star else "" """ * Settings @@ -323,26 +391,19 @@ def template_suffix(self) -> str: @cached_property def is_promo_star(self) -> bool: """bool: Whether to enable the Promo Star overlay.""" - return CFG.get_setting( - section='FRAME', - key='Promo.Star', - default=False) + return CFG.get_bool_setting(section="FRAME", key="Promo.Star", default=False) @cached_property def is_extended(self) -> bool: """bool: Whether to render using Extended Art framing.""" - return CFG.get_setting( - section='FRAME', - key='Extended.Art', - default=False) + return CFG.get_bool_setting(section="FRAME", key="Extended.Art", default=False) @cached_property def is_align_collector_left(self) -> bool: """CollectorAlign: Which collector alignment to use.""" - return CFG.get_setting( - section='TEXT', - key='Collector.Align.Left', - default=False) + return CFG.get_bool_setting( + section="TEXT", key="Collector.Align.Left", default=False + ) """ * Bool Properties @@ -358,23 +419,26 @@ def is_content_aware_enabled(self) -> bool: """ @cached_property - def pinlines_layer(self) -> ArtLayer: + def pinlines_layer(self) -> ArtLayer | None: """Only use split colors for land and hybrid cards, otherwise gold.""" return psd.getLayer( - self.background if len(self.pinlines) == 2 and not self.is_hybrid and not self.is_land else self.pinlines, - LAYERS.LAND if self.is_land else LAYERS.NONLAND) + self.background + if len(self.pinlines) == 2 and not self.is_hybrid and not self.is_land + else self.pinlines, + LAYERS.LAND if self.is_land else LAYERS.NONLAND, + ) """ * Text Layers """ @cached_property - def text_layer_rules(self) -> ArtLayer: + def text_layer_rules(self) -> ArtLayer | None: """ArtLayer: Does not change depending on Creature/Noncreature.""" return psd.getLayer(LAYERS.RULES_TEXT, self.text_group) @cached_property - def text_layer_type(self) -> ArtLayer: + def text_layer_type(self) -> ArtLayer | None: """ArtLayer: Card typeline text layer, used shifted version when color indicator is present.""" if self.is_type_shifted: return psd.getLayer(LAYERS.TYPE_LINE_SHIFT, self.text_group) @@ -385,32 +449,34 @@ def text_layer_type(self) -> ArtLayer: """ @cached_property - def collector_reference(self) -> ReferenceLayer: + def collector_reference(self) -> ReferenceLayer | None: """ReferenceLayer: Reference used to position the collector info.""" return psd.get_reference_layer(LAYERS.COLLECTOR_REFERENCE, self.legal_group) @cached_property - def textbox_reference(self) -> ArtLayer: + def textbox_reference(self) -> ReferenceLayer | None: """ArtLayer: Separately positioned box for 'Land' cards.""" return psd.get_reference_layer( - LAYERS.TEXTBOX_REFERENCE_LAND if self.is_land - else LAYERS.TEXTBOX_REFERENCE, - self.text_group) + LAYERS.TEXTBOX_REFERENCE_LAND if self.is_land else LAYERS.TEXTBOX_REFERENCE, + self.text_group, + ) @cached_property - def expansion_reference(self) -> ArtLayer: + def expansion_reference(self) -> ArtLayer | None: """ArtLayer: Separately positioned box for 'Land' cards.""" return psd.getLayer( - f"{LAYERS.EXPANSION_REFERENCE} {LAYERS.LAND}" if self.is_land + f"{LAYERS.EXPANSION_REFERENCE} {LAYERS.LAND}" + if self.is_land else LAYERS.EXPANSION_REFERENCE, - self.text_group) + self.text_group, + ) """ * Masks """ @cached_property - def border_mask(self) -> ArtLayer: + def border_mask(self) -> ArtLayer | None: """ArtLayer: Contains a mask used to change the border to an Extended Art border.""" return psd.getLayer(LAYERS.EXTENDED, LAYERS.MASKS) @@ -421,32 +487,36 @@ def border_mask(self) -> ArtLayer: def process_layout_data(self) -> None: """Remove rarity letter from collector data.""" super().process_layout_data() - self.layout.collector_data = self.layout.collector_data[:-2] if ( - '/' in self.layout.collector_data - ) else self.layout.collector_data[2:] + self.layout.collector_data = ( + self.layout.collector_data[:-2] + if ("/" in self.layout.collector_data) + else self.layout.collector_data[2:] + ) """ * Collector Info Methods """ + def align_collector_layers(self, layers: Iterable[ArtLayer | None]) -> None: + # Shift collector text + if self.is_align_collector_left and self.collector_reference: + [psd.align_left(n, ref=self.collector_reference.dims) for n in layers] + def collector_info(self) -> None: """Format and add the collector info at the bottom.""" # Which collector info mode? - if CFG.collector_mode in [ - CollectorMode.Default, CollectorMode.Modern - ] and self.layout.collector_data: - layers = self.collector_info_authentic() + if ( + CFG.collector_mode in [CollectorMode.Default, CollectorMode.Modern] + and self.layout.collector_data + ): + self.collector_info_authentic() elif CFG.collector_mode == CollectorMode.ArtistOnly: - layers = self.collector_info_artist_only() + self.collector_info_artist_only() else: - layers = self.collector_info_basic() + self.collector_info_basic() - # Shift collector text - if self.is_align_collector_left: - [psd.align_left(n, ref=self.collector_reference.dims) for n in layers] - - def collector_info_basic(self) -> list[ArtLayer]: + def collector_info_basic(self) -> None: """Called to generate basic collector info.""" # Get artist and info layers @@ -454,47 +524,58 @@ def collector_info_basic(self) -> list[ArtLayer]: info = psd.getLayer(LAYERS.SET, self.legal_group) # Fill optional promo star - if self.is_collector_promo: + if self.is_collector_promo and info: psd.replace_text(info, "•", MagicIcons.COLLECTOR_STAR) # Apply the collector info - if self.layout.lang != 'en': - psd.replace_text(info, 'EN', self.layout.lang.upper()) - psd.replace_text(artist, "Artist", self.layout.artist) - psd.replace_text(info, 'SET', self.layout.set) - return [artist, info] + if self.layout.lang != "en" and info: + psd.replace_text(info, "EN", self.layout.lang.upper()) + if artist: + psd.replace_text(artist, "Artist", self.layout.artist) + if info: + psd.replace_text(info, "SET", self.layout.set) + + self.align_collector_layers((artist, info)) - def collector_info_authentic(self) -> list[ArtLayer]: + def collector_info_authentic(self) -> None: """Classic presents authentic collector info differently.""" # Hide basic 'Set' layer - psd.getLayer(LAYERS.SET, self.legal_group).visible = False + if layer := psd.getLayer(LAYERS.SET, self.legal_group): + layer.visible = False # Get artist and info layers, reveal info layer artist = psd.getLayer(LAYERS.ARTIST, self.legal_group) info = psd.getLayer(LAYERS.COLLECTOR, self.legal_group) - info.visible = True + + if info: + info.visible = True # Fill optional promo star - if self.is_collector_promo: + if self.is_collector_promo and info: psd.replace_text(info, "•", MagicIcons.COLLECTOR_STAR) # Apply the collector info - psd.replace_text(artist, 'Artist', self.layout.artist) - psd.replace_text(info, 'SET', self.layout.set) - psd.replace_text(info, 'NUM', self.layout.collector_data) - return [artist, info] + if artist: + psd.replace_text(artist, "Artist", self.layout.artist) + if info: + psd.replace_text(info, "SET", self.layout.set) + psd.replace_text(info, "NUM", self.layout.collector_data) + + self.align_collector_layers((artist, info)) - def collector_info_artist_only(self) -> list[ArtLayer]: + def collector_info_artist_only(self) -> None: """Called to generate 'Artist Only' collector info.""" # Collector layers artist = psd.getLayer(LAYERS.ARTIST, self.legal_group) - psd.getLayer(LAYERS.SET, self.legal_group).visible = False + if layer := psd.getLayer(LAYERS.SET, self.legal_group): + layer.visible = False # Apply the collector info - psd.replace_text(artist, "Artist", self.layout.artist) - return [artist] + if artist: + psd.replace_text(artist, "Artist", self.layout.artist) + self.align_collector_layers((artist,)) """ * Frame Layer Methods @@ -510,20 +591,27 @@ def enable_frame_layers(self): self.expansion_symbol_layer.translate(0, 8) # Simple one image background - self.pinlines_layer.visible = True + if self.pinlines_layer: + self.pinlines_layer.visible = True # Add the promo star - if self.is_promo_star: - psd.getLayerSet("Promo Star", LAYERS.TEXT_AND_ICONS).visible = True + if self.is_promo_star and ( + layer := psd.getLayerSet("Promo Star", LAYERS.TEXT_AND_ICONS) + ): + layer.visible = True # Make Extended Art modifications if self.is_extended: # Copy extended mask to Border - psd.copy_layer_mask(self.border_mask, self.border_group) + if self.border_mask and self.border_group: + psd.copy_layer_mask(self.border_mask, self.border_group) # Enable extended mask on Pinlines - psd.enable_mask(self.pinlines_layer.parent) - psd.enable_vector_mask(self.pinlines_layer.parent) + if self.pinlines_layer and isinstance( + (parent := self.pinlines_layer.parent), LayerSet + ): + psd.enable_mask(parent) + psd.enable_vector_mask(parent) """ * Text Layer Methods @@ -533,22 +621,26 @@ def rules_text_and_pt_layers(self): """Does not require a separate text area definition for Creature cards.""" # Add rules text - self.text.append( - FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - flavor=self.layout.flavor_text, - centered=self.is_centered, - reference=self.textbox_reference, - divider=self.divider_layer)) + if self.text_layer_rules: + self.text.append( + FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + flavor=self.layout.flavor_text, + centered=self.is_centered, + reference=self.textbox_reference, + divider=self.divider_layer, + ) + ) # Add Power / Toughness - if self.is_creature: + if self.is_creature and self.text_layer_pt: self.text.append( TextField( layer=self.text_layer_pt, - contents=f'{self.layout.power}/' - f'{self.layout.toughness}')) + contents=f"{self.layout.power}/{self.layout.toughness}", + ) + ) """ * Hook Methods @@ -556,7 +648,8 @@ def rules_text_and_pt_layers(self): def hook_large_mana(self) -> None: """Adjust mana cost position for large symbols.""" - self.text_layer_mana.translate(0, -5) + if self.text_layer_mana: + self.text_layer_mana.translate(0, -5) """ @@ -571,39 +664,55 @@ class EtchedTemplate(VectorTemplate): except for Artifact cards. Uses pinline colors for the textbox always. No hollow crown, no companion or nyx layers. """ - template_suffix = 'Etched' + + template_suffix = "Etched" # Static properties - background_group = None + @cached_property + def background_group(self) -> LayerSet | None: + return None # Color Maps - pinlines_color_map = { - **pinlines_color_map.copy(), - 'W': [252, 254, 255], - 'Land': [136, 120, 98], - 'Artifact': [194, 210, 221], - 'Colorless': [194, 210, 221]} + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (252, 254, 255), + "Land": (136, 120, 98), + "Artifact": (194, 210, 221), + "Colorless": (194, 210, 221), + } """ * Colors """ @cached_property - def pinlines_colors(self) -> Union[SolidColor, list[dict]]: + def pinlines_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Use Artifact color even for colored artifacts if self.is_artifact and not self.is_land: - return psd.get_pinline_gradient(LAYERS.ARTIFACT, color_map=self.pinlines_color_map) - return psd.get_pinline_gradient(self.pinlines, color_map=self.pinlines_color_map) + return psd.get_pinline_gradient( + LAYERS.ARTIFACT, color_map=self.pinlines_color_map + ) + return psd.get_pinline_gradient( + self.pinlines, color_map=self.pinlines_color_map + ) @cached_property - def crown_colors(self) -> Optional[str]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Use Artifact color even for colored artifacts if self.is_artifact and not self.is_land: return LAYERS.ARTIFACT return self.pinlines @cached_property - def textbox_colors(self) -> Optional[str]: + def textbox_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Normal pinline coloring rules return self.pinlines @@ -612,7 +721,7 @@ def textbox_colors(self) -> Optional[str]: """ @cached_property - def divider_layer(self) -> Optional[ArtLayer]: + def divider_layer(self) -> ArtLayer | LayerSet | None: # Divider is grouped return psd.getLayerSet(LAYERS.DIVIDER, self.text_group) @@ -621,7 +730,7 @@ def divider_layer(self) -> Optional[ArtLayer]: """ @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """Enable Legendary pinlines shape if card is Legendary.""" if self.is_legendary: return [psd.getLayer(LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE])] @@ -632,9 +741,21 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: """ @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: """Enable pinlines mask if card is Legendary.""" - return [psd.getLayer(LAYERS.NORMAL, [self.pinlines_group, LAYERS.SHAPE])] if self.is_legendary else [] + return ( + [psd.getLayer(LAYERS.NORMAL, [self.pinlines_group, LAYERS.SHAPE])] + if self.is_legendary + else [] + ) """ * Methods @@ -642,7 +763,8 @@ def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: def enable_crown(self) -> None: """Enable Legendary Crown shape.""" - psd.getLayer(f"{LAYERS.LEGENDARY} {LAYERS.SHADOWS}").visible = True + if layer := psd.getLayer(f"{LAYERS.LEGENDARY} {LAYERS.SHADOWS}"): + layer.visible = True class ClassicRemasteredTemplate(VectorTransformMod, VectorTemplate): @@ -650,13 +772,22 @@ class ClassicRemasteredTemplate(VectorTransformMod, VectorTemplate): Based on iDerp's Classic Remastered template, modified to work with Proxyshop, colored pinlines added for land generation. PT box added for creatures. Does not support Nyx or Companion layers. """ - frame_suffix = 'Classic' - template_suffix = 'Remastered' + + frame_suffix = "Classic" + template_suffix = "Remastered" # Static properties - is_vehicle = False - is_name_shifted = False - twins_group = None + @cached_property + def is_vehicle(self) -> bool: + return False + + @cached_property + def is_name_shifted(self) -> bool: + return False + + @cached_property + def twins_group(self) -> LayerSet | None: + return None """ * Frame Details @@ -665,12 +796,12 @@ class ClassicRemasteredTemplate(VectorTransformMod, VectorTemplate): @cached_property def color_limit(self) -> int: """Supports 2 and 3 color limit setting.""" - return int(CFG.get_setting("FRAME", "Max.Colors", "3", is_bool=False)) + 1 + return int(CFG.get_setting("FRAME", "Max.Colors", "3")) + 1 @cached_property def gold_pt(self) -> bool: """Returns True if PT for multicolored cards should be gold.""" - return bool(CFG.get_setting("FRAME", "Gold.PT", False)) + return CFG.get_bool_setting("FRAME", "Gold.PT", False) """ * Bool @@ -686,7 +817,9 @@ def has_pinlines(self): """ @cached_property - def pinlines_colors(self) -> Union[list[int], list[dict]]: + def pinlines_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Union[list[int], list[dict]]: Allow pinlines on Land and Artifact cards.""" if self.has_pinlines: if len(self.identity) >= self.color_limit: @@ -712,10 +845,10 @@ def textbox_colors(self) -> list[str]: if self.is_land: # Within color limit if 1 < len(self.identity) < self.color_limit: - return [f'{n} {LAYERS.LAND}' for n in self.identity] + return [f"{n} {LAYERS.LAND}" for n in self.identity] # 1 Color limit if len(self.identity) == 2: - return [f'{LAYERS.GOLD} {LAYERS.LAND}'] + return [f"{LAYERS.GOLD} {LAYERS.LAND}"] # Plain land background if self.pinlines == LAYERS.LAND: return [LAYERS.LAND] @@ -752,7 +885,7 @@ def crown_colors(self) -> list[str]: """ @cached_property - def pinlines_group(self) -> LayerSet: + def pinlines_group(self) -> LayerSet | None: """LayerSet: Pinlines and textbox combined.""" return psd.getLayerSet(LAYERS.PINLINES_TEXTBOX) @@ -764,22 +897,31 @@ def pinlines_groups(self) -> list[LayerSet]: # Two main pinlines groups groups: list[LayerSet] = [ - psd.getLayerSet("Pinlines Top", self.pinlines_group), - psd.getLayerSet("Pinlines Bottom", self.pinlines_group) + group + for group in ( + psd.getLayerSet("Pinlines Top", self.pinlines_group), + psd.getLayerSet("Pinlines Bottom", self.pinlines_group), + ) + if group ] # Add the crown pinlines if needed - if self.is_legendary: - groups.append(psd.getLayerSet(LAYERS.PINLINES, LAYERS.LEGENDARY_CROWN)) + if self.is_legendary and ( + group := psd.getLayerSet(LAYERS.PINLINES, LAYERS.LEGENDARY_CROWN) + ): + groups.append(group) return groups @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """LayerSet: Must apply correct layer effects.""" group = psd.getLayerSet(LAYERS.TEXTBOX, self.pinlines_group) - psd.copy_layer_fx( - psd.getLayer("EFFECTS LAND" if self.pinlines_colors else "EFFECTS", group), - group) + if ( + layer := psd.getLayer( + "EFFECTS LAND" if self.pinlines_colors else "EFFECTS", group + ) + ) and group: + psd.copy_layer_fx(layer, group) return group """ @@ -790,7 +932,14 @@ def textbox_group(self) -> LayerSet: def mask_layers(self) -> list[ArtLayer]: """list[ArtLayer]: A list of layers containing masks based on how many color splits.""" if 1 < len(self.identity) < self.color_limit: - return [psd.getLayer(n, self.mask_group) for n in CON.masks[len(self.identity)]] + return [ + layer + for layer in ( + psd.getLayer(n, self.mask_group) + for n in CON.masks[len(self.identity)] + ) + if layer + ] return [] """ @@ -798,17 +947,19 @@ def mask_layers(self) -> list[ArtLayer]: """ @cached_property - def pinlines_shape(self) -> Optional[LayerSet]: + def pinlines_shape(self) -> ArtLayer | None: """Pinlines are only provided for Land and Artifact cards.""" if not self.pinlines_groups: return return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.pinlines_groups[0], LAYERS.SHAPE] + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.pinlines_groups[0], LAYERS.SHAPE], ) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """No need for Twins or Border shape.""" return [self.pinlines_shape, self.textbox_shape] @@ -817,20 +968,24 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: """ @cached_property - def pt_reference(self) -> Optional[ArtLayer]: + def pt_reference(self) -> ReferenceLayer | None: """Support alternate reference for flipside PT.""" - return psd.getLayer( - f'{LAYERS.PT_REFERENCE} Flip' if ( - self.is_transform and self.is_front and self.is_flipside_creature - ) else LAYERS.PT_REFERENCE, self.text_group) + return get_reference_layer( + f"{LAYERS.PT_REFERENCE} Flip" + if (self.is_transform and self.is_front and self.is_flipside_creature) + else LAYERS.PT_REFERENCE, + self.text_group, + ) @cached_property - def textbox_reference(self) -> Optional[ArtLayer]: + def textbox_reference(self) -> ReferenceLayer | None: """Use smaller Textbox Reference if Pinlines are added.""" return psd.get_reference_layer( f"{LAYERS.TEXTBOX_REFERENCE} {LAYERS.PINLINES}" - if self.pinlines_colors else LAYERS.TEXTBOX_REFERENCE, - self.text_group) + if self.pinlines_colors + else LAYERS.TEXTBOX_REFERENCE, + self.text_group, + ) """ * Hooks @@ -838,14 +993,14 @@ def textbox_reference(self) -> Optional[ArtLayer]: def hook_large_mana(self) -> None: """Adjust mana cost position for large symbols.""" - if not self.is_legendary: + if not self.is_legendary and self.text_layer_mana: self.text_layer_mana.translate(0, -10) """ * Methods """ - def enable_hollow_crown(self, **kwargs) -> None: + def enable_hollow_crown(self) -> None: """No hollow crown.""" pass @@ -870,20 +1025,24 @@ class UniversesBeyondTemplate(VectorTransformMod, VectorTemplate): an alternative way to build a highly complex template which can work for multiple card types. Credit to Kyle of Card Conjurer, WarpDandy, SilvanMTG, Chilli and MrTeferi. """ - template_suffix = 'Universes Beyond' + + template_suffix = "Universes Beyond" # Color Maps - pinlines_color_map = { - **pinlines_color_map.copy(), - 'W': [246, 247, 241], - 'U': [0, 131, 193], - 'B': [44, 40, 33], - 'R': [237, 66, 31], - 'G': [5, 129, 64], - 'Gold': [239, 209, 107], - 'Land': [165, 150, 132], - 'Artifact': [227, 228, 230], - 'Colorless': [227, 228, 230]} + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (246, 247, 241), + "U": (0, 131, 193), + "B": (44, 40, 33), + "R": (237, 66, 31), + "G": (5, 129, 64), + "Gold": (239, 209, 107), + "Land": (165, 150, 132), + "Artifact": (227, 228, 230), + "Colorless": (227, 228, 230), + } """ * Colors @@ -892,10 +1051,12 @@ class UniversesBeyondTemplate(VectorTransformMod, VectorTemplate): @cached_property def pt_colors(self) -> str: """str: Layer texture name, supports Vehicle PT but doesn't need white text.""" - return LAYERS.VEHICLE if self.is_vehicle == LAYERS.VEHICLE else self.twins + return LAYERS.VEHICLE if self.is_vehicle else self.twins @cached_property - def crown_colors(self) -> Union[list[int], list[dict]]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """list[int] | list[dict]: Solid color or gradient adjustment layer notation.""" return psd.get_pinline_gradient(self.pinlines, color_map=self.crown_color_map) @@ -904,7 +1065,7 @@ def crown_colors(self) -> Union[list[int], list[dict]]: """ @cached_property - def background_group(self) -> Optional[LayerSet]: + def background_group(self) -> LayerSet | None: """Optional[LayerSet]: No background for 'Colorless' cards.""" if self.is_colorless: return @@ -915,26 +1076,45 @@ def background_group(self) -> Optional[LayerSet]: """ @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: """Masks enabled or copied.""" - return [ - # Pinlines mask, supports Transform Front - [psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.mask_group, LAYERS.PINLINES] - ), self.pinlines_group] - ] + if self.pinlines_group and ( + layer := psd.getLayer( + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.mask_group, LAYERS.PINLINES], + ) + ): + return [ + ( + # Pinlines mask, supports Transform Front + layer, + self.pinlines_group, + ) + ] + return [] """ * Shapes """ @cached_property - def pinlines_shape(self) -> Optional[LayerSet]: + def pinlines_shape_group(self) -> LayerSet | None: # Support Normal and Transform Front name = ( - LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK - ) if self.is_transform else LAYERS.NORMAL + (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) + if self.is_transform + else LAYERS.NORMAL + ) return psd.getLayerSet(name, [self.pinlines_group, LAYERS.SHAPE]) """ @@ -944,24 +1124,25 @@ def pinlines_shape(self) -> Optional[LayerSet]: def enable_frame_layers(self) -> None: super().enable_frame_layers() + if self.pinlines_shape_group: + self.pinlines_shape_group.visible = True + # Translucent colorless shapes if self.is_colorless and self.twins_shape and self.textbox_shape: psd.set_fill_opacity(60, self.twins_group) psd.set_fill_opacity(60, self.textbox_group) def enable_crown(self) -> None: - # Generate a solid color or gradient layer for the crown group - self.crown_group.visible = True - self.generate_layer( - group=self.crown_group, - colors=self.crown_colors) + if self.crown_group: + self.crown_group.visible = True + self.generate_layer(group=self.crown_group, colors=self.crown_colors) # Enable Legendary pinline connector - psd.getLayerSet( - LAYERS.LEGENDARY, - self.pinlines_shape.parent - ).visible = True + if self.pinlines_shape_group and ( + layer := psd.getLayerSet(LAYERS.LEGENDARY, self.pinlines_shape_group.parent) + ): + layer.visible = True """ * Transform Methods @@ -971,9 +1152,13 @@ def enable_transform_layers_back(self) -> None: super().enable_transform_layers_back() # Enable brightness shift on back face layers - psd.getLayerSet(LAYERS.BACK, self.pt_group).visible = True - psd.getLayerSet(LAYERS.BACK, self.twins_group).visible = True - psd.getLayer(LAYERS.BACK, self.textbox_group).visible = True + for layer in ( + psd.getLayerSet(LAYERS.BACK, self.pt_group), + psd.getLayerSet(LAYERS.BACK, self.twins_group), + psd.getLayer(LAYERS.BACK, self.textbox_group), + ): + if layer: + layer.visible = True class LOTRTemplate(VectorTemplate): @@ -982,60 +1167,80 @@ class LOTRTemplate(VectorTemplate): * Credit to Tupinambá (Pedro Neves) for the master template * With additional support from CompC and MrTeferi """ - template_suffix = 'Lord of the Rings' + + template_suffix = "Lord of the Rings" # Static Properties - enabled_shapes = [] + @cached_property + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: + return [] # Color Maps - pinlines_color_map = { - **pinlines_color_map.copy(), - 'W': [230, 220, 185], - 'U': [72, 142, 191], - 'B': [126, 128, 127], - 'R': [217, 94, 76], - 'G': [97, 143, 102], - 'Gold': [246, 213, 125], - 'Land': [181, 162, 149], - 'Artifact': [210, 219, 227], - 'Colorless': [210, 219, 227]} - dark_color_map = { - **pinlines_color_map.copy(), - 'W': [134, 123, 105], - 'U': [44, 51, 103], - 'B': [44, 43, 39], - 'R': [118, 34, 34], - 'G': [13, 68, 45], - 'Gold': [158, 124, 78], - 'Land': [103, 90, 74], - 'Artifact': [61, 88, 109], - 'Colorless': [78, 91, 101], - 'Vehicle': [76, 51, 20]} + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (230, 220, 185), + "U": (72, 142, 191), + "B": (126, 128, 127), + "R": (217, 94, 76), + "G": (97, 143, 102), + "Gold": (246, 213, 125), + "Land": (181, 162, 149), + "Artifact": (210, 219, 227), + "Colorless": (210, 219, 227), + } + + @cached_property + def dark_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (134, 123, 105), + "U": (44, 51, 103), + "B": (44, 43, 39), + "R": (118, 34, 34), + "G": (13, 68, 45), + "Gold": (158, 124, 78), + "Land": (103, 90, 74), + "Artifact": (61, 88, 109), + "Colorless": (78, 91, 101), + "Vehicle": (76, 51, 20), + } """ * Colors """ @cached_property - def crown_colors(self) -> Union[SolidColor, list[dict]]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as SolidColor or gradient notation.""" return psd.get_pinline_gradient( - self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines, - color_map=self.pinlines_color_map) + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines, + color_map=self.pinlines_color_map, + ) @cached_property - def twins_colors(self) -> Union[SolidColor, list[dict]]: + def twins_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as SolidColor or gradient notation.""" return psd.get_pinline_gradient( - colors=self.twins, - color_map=self.dark_color_map) + colors=self.twins, color_map=self.dark_color_map + ) @cached_property - def pt_colors(self) -> Union[SolidColor, list[dict]]: + def pt_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Must be returned as SolidColor or gradient notation.""" return psd.get_pinline_gradient( colors=LAYERS.VEHICLE if self.is_vehicle else self.twins, - color_map=self.dark_color_map) + color_map=self.dark_color_map, + ) @cached_property def textbox_colors(self) -> str: @@ -1054,8 +1259,10 @@ def background_colors(self) -> str: @cached_property def pinlines_groups(self) -> list[LayerSet]: """Pinlines and crown pinlines addon.""" - groups = [self.pinlines_group] - if self.is_legendary: + groups: list[LayerSet] = [] + if self.pinlines_group: + groups.append(self.pinlines_group) + if self.is_legendary and self.crown_group: groups.append(self.crown_group) return groups @@ -1064,7 +1271,15 @@ def pinlines_groups(self) -> list[LayerSet]: """ @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: """Mask Pinlines if Legendary.""" if self.is_legendary: return [self.pinlines_group] @@ -1076,59 +1291,72 @@ def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: def enable_crown(self) -> None: """Enable the Legendary crown group only.""" - self.crown_group.visible = True + if self.crown_group: + self.crown_group.visible = True class BorderlessVectorTemplate( - NicknameMod, - VectorBorderlessMod, - VectorMDFCMod, - VectorTransformMod, - VectorTemplate + NicknameMod, VectorBorderlessMod, VectorMDFCMod, VectorTransformMod, VectorTemplate ): """Borderless template first used in the Womens Day Secret Lair, redone with vector shapes.""" # Color Maps - light_color_map = { - 'W': "#faf8f2", - 'U': "#d2edfa", - 'B': "#c9c2be", - 'R': "#f8c7b0", - 'G': "#dbfadc", - 'Gold': "#f5e5a4", - 'Land': "#f0ddce", - 'Hybrid': "f#0ddce", - 'Artifact': "#cde0e9", - 'Colorless': "#e2d8d4", - 'Vehicle': "#4c3314"} - crown_color_map = { - 'W': "#f8f4f0", - 'U': "#006dae", - 'B': "#393431", - 'R': "#de3c23", - 'G': "#006d42", - 'Gold': "#efd16b", - 'Land': "#a59684", - 'Artifact': "#b5c5cd", - 'Colorless': "#d6d6dc"} - gradient_location_map = { - 2: [.40, .60], - 3: [.29, .40, .60, .71], - 4: [.20, .30, .45, .55, .70, .80], - 5: [.20, .25, .35, .45, .55, .65, .75, .80]} + @cached_property + def light_color_map(self) -> dict[str, ColorObject]: + return { + "W": "#faf8f2", + "U": "#d2edfa", + "B": "#c9c2be", + "R": "#f8c7b0", + "G": "#dbfadc", + "Gold": "#f5e5a4", + "Land": "#f0ddce", + "Hybrid": "f#0ddce", + "Artifact": "#cde0e9", + "Colorless": "#e2d8d4", + "Vehicle": "#4c3314", + } + + @cached_property + def crown_color_map(self) -> dict[str, ColorObject]: + return { + "W": "#f8f4f0", + "U": "#006dae", + "B": "#393431", + "R": "#de3c23", + "G": "#006d42", + "Gold": "#efd16b", + "Land": "#a59684", + "Artifact": "#b5c5cd", + "Colorless": "#d6d6dc", + } + + @cached_property + def gradient_location_map(self) -> dict[int, list[float]]: + return { + 2: [0.40, 0.60], + 3: [0.29, 0.40, 0.60, 0.71], + 4: [0.20, 0.30, 0.45, 0.55, 0.70, 0.80], + 5: [0.20, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.80], + } # Static Properties - mask_layers = [] - background_group = None + @cached_property + def mask_layers(self) -> list[ArtLayer]: + return [] + + @cached_property + def background_group(self) -> LayerSet | None: + return None """ * Mixin Methods """ - @property - def post_text_methods(self): + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: """Add post-text adjustments method.""" - funcs = [] + funcs: list[Callable[[], None]] = [] if self.size != BorderlessTextbox.Tall: funcs.append(self.textbox_positioning) if self.is_token: @@ -1150,128 +1378,126 @@ def size(self) -> str: return BorderlessTextbox.Textless # Get the user's preferred setting - size = str(CFG.get_option( - section="FRAME", - key="Textbox.Size", - enum_class=BorderlessTextbox, - default=BorderlessTextbox.Automatic - )) + size = str( + CFG.get_option( + section="FRAME", + key="Textbox.Size", + enum_class=BorderlessTextbox, + default=BorderlessTextbox.Automatic, + ) + ) # Determine the automatic size if size == BorderlessTextbox.Automatic: - # Check for basic land if self.is_basic_land: return BorderlessTextbox.Short # Set up our test text layer - test_layer = psd.getLayer(self.text_layer_rules_name, [self.text_group, "Tall"]) + test_layer = psd.getLayer( + self.text_layer_rules_name, [self.text_group, "Tall"] + ) test_text = self.layout.oracle_text - if self.layout.flavor_text: - test_text += f'\r{self.layout.flavor_text}' - test_layer.textItem.contents = test_text.replace('\n', '\r') - - # Get the number of lines in our test text and decide what size - num = get_line_count(test_layer, self.docref) - if self.layout.flavor_text: - num += 1 - if num < 5: - return BorderlessTextbox.Short - if num < 6: - return BorderlessTextbox.Medium - if num < 7: - return BorderlessTextbox.Normal + if test_layer: + if self.layout.flavor_text: + test_text += f"\r{self.layout.flavor_text}" + test_layer.textItem.contents = test_text.replace("\n", "\r") + + # Get the number of lines in our test text and decide what size + num = get_line_count(test_layer, self.docref) + if self.layout.flavor_text: + num += 1 + if num < 5: + return BorderlessTextbox.Short + if num < 6: + return BorderlessTextbox.Medium + if num < 7: + return BorderlessTextbox.Normal return BorderlessTextbox.Tall return size @cached_property def color_limit(self) -> int: # Built in setting, dual and triple color split - return int(CFG.get_setting( - section="COLORS", - key="Max.Colors", - default="2", - is_bool=False)) + 1 + return int(CFG.get_setting(section="COLORS", key="Max.Colors", default="2")) + 1 @cached_property def drop_shadow_enabled(self) -> bool: """Returns True if Drop Shadow text setting is enabled.""" - return bool(CFG.get_setting( - section="TEXT", - key="Drop.Shadow", - default=True)) + return bool( + CFG.get_bool_setting(section="TEXT", key="Drop.Shadow", default=True) + ) @cached_property def crown_texture_enabled(self) -> bool: """Returns True if Legendary crown clipping texture should be enabled.""" - return bool(CFG.get_setting( - section="FRAME", - key="Crown.Texture", - default=True)) + return bool( + CFG.get_bool_setting(section="FRAME", key="Crown.Texture", default=True) + ) @cached_property def multicolor_textbox(self) -> bool: """Returns True if Textbox for multicolored cards should use blended colors.""" - return bool(CFG.get_setting( - section="COLORS", - key="Multicolor.Textbox", - default=True)) + return bool( + CFG.get_bool_setting( + section="COLORS", key="Multicolor.Textbox", default=True + ) + ) @cached_property def multicolor_pinlines(self) -> bool: """Returns True if Pinlines and Crown for multicolored cards should use blended colors.""" - return bool(CFG.get_setting( - section="COLORS", - key="Multicolor.Pinlines", - default=True)) + return bool( + CFG.get_bool_setting( + section="COLORS", key="Multicolor.Pinlines", default=True + ) + ) @cached_property def multicolor_twins(self) -> bool: """Returns True if Twins for multicolored cards should use blended colors.""" - return bool(CFG.get_setting( - section="COLORS", - key="Multicolor.Twins", - default=False)) + return bool( + CFG.get_bool_setting( + section="COLORS", key="Multicolor.Twins", default=False + ) + ) @cached_property def multicolor_pt(self) -> bool: """Returns True if PT Box for multicolored cards should use the last color.""" - return bool(CFG.get_setting( - section="COLORS", - key="Multicolor.PT", - default=False)) + return bool( + CFG.get_bool_setting(section="COLORS", key="Multicolor.PT", default=False) + ) @cached_property def hybrid_colored(self) -> bool: """Returns True if Twins and PT should be colored on Hybrid cards.""" - return bool(CFG.get_setting( - section="COLORS", - key="Hybrid.Colored", - default=True)) + return bool( + CFG.get_bool_setting(section="COLORS", key="Hybrid.Colored", default=True) + ) @cached_property def front_face_colors(self) -> bool: """Returns True if lighter color map should be used on front face DFC cards.""" - return bool(CFG.get_setting( - section="COLORS", - key="Front.Face.Colors", - default=True)) + return bool( + CFG.get_bool_setting( + section="COLORS", key="Front.Face.Colors", default=True + ) + ) @cached_property def land_colorshift(self) -> bool: """Returns True if Land cards should use the darker brown color.""" - return bool(CFG.get_setting( - section="COLORS", - key="Land.Colorshift", - default=False)) + return bool( + CFG.get_bool_setting(section="COLORS", key="Land.Colorshift", default=False) + ) @cached_property def artifact_color_mode(self) -> str: """Setting determining what elements to color for colored artifacts..""" return CFG.get_option( - section="COLORS", - key="Artifact.Color.Mode", - enum_class=BorderlessColorMode) + section="COLORS", key="Artifact.Color.Mode", enum_class=BorderlessColorMode + ) """ * Frame Details @@ -1282,9 +1508,11 @@ def frame_type(self) -> str: """Layer name associated with the holistic frame type.""" if self.is_textless: # Textless / Textless Transform - return f"{self.size} {LAYERS.TRANSFORM}" if ( - self.is_transform or self.is_mdfc - ) else self.size + return ( + f"{self.size} {LAYERS.TRANSFORM}" + if (self.is_transform or self.is_mdfc) + else self.size + ) if self.is_transform and self.is_front: # Size TF Front return f"{self.size} {LAYERS.TRANSFORM_FRONT}" @@ -1302,31 +1530,33 @@ def art_frame(self) -> str: * Bool """ - @property - def is_basic_land(self): + @cached_property + def is_basic_land(self) -> bool: """Disable basic land watermark if Textless is enabled.""" - if bool(CFG.get_setting(section="FRAME", key="Textless", default=False)): + if bool(CFG.get_bool_setting(section="FRAME", key="Textless", default=False)): return False return super().is_basic_land @cached_property def is_textless(self) -> bool: """Return True if this a textless render.""" - if not any([self.layout.oracle_text, self.layout.flavor_text, self.is_basic_land]): + if not any( + [self.layout.oracle_text, self.layout.flavor_text, self.is_basic_land] + ): return True - return bool(CFG.get_setting(section="FRAME", key="Textless", default=False)) + return CFG.get_bool_setting(section="FRAME", key="Textless", default=False) @cached_property def is_nickname(self) -> bool: """Return True if this a nickname render.""" if self.layout.nickname: return True - return CFG.get_setting(section="TEXT", key="Nickname", default=False) + return CFG.get_bool_setting(section="TEXT", key="Nickname", default=False) @cached_property def is_colored_nickname(self) -> bool: """Return True if nickname plate should be colored.""" - return CFG.get_setting(section="COLORS", key="Nickname", default=False) + return CFG.get_bool_setting(section="COLORS", key="Nickname", default=False) @cached_property def is_multicolor(self) -> bool: @@ -1342,11 +1572,16 @@ def is_centered(self) -> bool: and "\n" not in self.layout.oracle_text and not ( # Not centered if using a small textbox with Flipside PT - self.is_flipside_creature and self.is_front and self.size in [ + self.is_flipside_creature + and self.is_front + and self.size + in [ BorderlessTextbox.Automatic, BorderlessTextbox.Medium, - BorderlessTextbox.Short - ])) + BorderlessTextbox.Short, + ] + ) + ) @cached_property def is_pt_enabled(self) -> bool: @@ -1357,32 +1592,41 @@ def is_pt_enabled(self) -> bool: def is_drop_shadow(self) -> bool: """Return True if drop shadow setting is enabled.""" return bool( - self.drop_shadow_enabled and not - ((self.is_mdfc or self.is_transform) and self.is_front and self.front_face_colors)) + self.drop_shadow_enabled + and not ( + (self.is_mdfc or self.is_transform) + and self.is_front + and self.front_face_colors + ) + ) @cached_property def is_authentic_front(self) -> bool: """Return True if rendering a front face DFC card with authentic lighter colors.""" - return bool((self.is_mdfc or self.is_transform) and self.is_front and self.front_face_colors) + return bool( + (self.is_mdfc or self.is_transform) + and self.is_front + and self.front_face_colors + ) """ * Color Maps """ @cached_property - def dark_color_map(self) -> dict: + def dark_color_map(self) -> dict[str, ColorObject]: return { - 'W': "#958676", - 'U': "#045482", - 'B': "#282523", - 'R': "#93362a", - 'G': "#134f23", - 'Gold': "#9a883f", - 'Land': "#684e30" if self.land_colorshift else "#a79c8e", - 'Hybrid': "#a79c8e", - 'Artifact': "#48555c", - 'Colorless': "#74726b", - 'Vehicle': "#4c3314" + "W": "#958676", + "U": "#045482", + "B": "#282523", + "R": "#93362a", + "G": "#134f23", + "Gold": "#9a883f", + "Land": "#684e30" if self.land_colorshift else "#a79c8e", + "Hybrid": "#a79c8e", + "Artifact": "#48555c", + "Colorless": "#74726b", + "Vehicle": "#4c3314", } """ @@ -1390,51 +1634,70 @@ def dark_color_map(self) -> dict: """ @cached_property - def twins_colors(self) -> Union[list[int], list[dict]]: - + def twins_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Default to twins colors = self.twins # Color enabled hybrid OR color enabled multicolor - if (self.is_hybrid and self.hybrid_colored) or (self.is_multicolor and self.multicolor_twins): + if (self.is_hybrid and self.hybrid_colored) or ( + self.is_multicolor and self.multicolor_twins + ): colors = self.identity # Color disabled hybrid cards elif self.is_hybrid: colors = LAYERS.HYBRID # Use artifact twins if artifact mode isn't colored - if self.is_artifact and not self.is_land and self.artifact_color_mode not in [ - BorderlessColorMode.Twins_And_PT, - BorderlessColorMode.Twins, - BorderlessColorMode.All - ]: + if ( + self.is_artifact + and not self.is_land + and self.artifact_color_mode + not in [ + BorderlessColorMode.Twins_And_PT, + BorderlessColorMode.Twins, + BorderlessColorMode.All, + ] + ): colors = LAYERS.ARTIFACT # Return Solid Color or Gradient notation return psd.get_pinline_gradient( colors=colors, - color_map=self.light_color_map if self.is_authentic_front else self.dark_color_map, - location_map=self.gradient_location_map) + color_map=self.light_color_map + if self.is_authentic_front + else self.dark_color_map, + location_map=self.gradient_location_map, + ) @cached_property - def pt_colors(self) -> Union[list[int], list[dict]]: - + def pt_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Default to twins, or Vehicle for non-colored vehicle artifacts colors = self.twins # Color enabled hybrid OR color enabled multicolor - if (self.is_hybrid and self.hybrid_colored) or (self.is_multicolor and self.multicolor_pt): + if (self.is_hybrid and self.hybrid_colored) or ( + self.is_multicolor and self.multicolor_pt + ): colors = self.identity[-1] # Use Hybrid color for color-disabled hybrid cards elif self.is_hybrid: colors = LAYERS.HYBRID # Use artifact twins color if artifact mode isn't colored - if self.is_artifact and not self.is_land and self.artifact_color_mode not in [ - BorderlessColorMode.Twins_And_PT, - BorderlessColorMode.All, - BorderlessColorMode.PT - ]: + if ( + self.is_artifact + and not self.is_land + and self.artifact_color_mode + not in [ + BorderlessColorMode.Twins_And_PT, + BorderlessColorMode.All, + BorderlessColorMode.PT, + ] + ): colors = LAYERS.ARTIFACT # Use Vehicle for non-colored artifacts @@ -1444,11 +1707,15 @@ def pt_colors(self) -> Union[list[int], list[dict]]: # Return Solid Color or Gradient notation return psd.get_pinline_gradient( colors=colors, - color_map=self.light_color_map if self.is_authentic_front else self.dark_color_map) + color_map=self.light_color_map + if self.is_authentic_front + else self.dark_color_map, + ) @cached_property - def textbox_colors(self) -> Union[list[int], list[dict]]: - + def textbox_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Default to twins colors = self.twins @@ -1459,58 +1726,77 @@ def textbox_colors(self) -> Union[list[int], list[dict]]: # Use artifact textbox color if artifact mod isn't colored if self.is_artifact and self.artifact_color_mode not in [ BorderlessColorMode.Textbox, - BorderlessColorMode.All + BorderlessColorMode.All, ]: colors = LAYERS.ARTIFACT # Return Solid Color or Gradient notation return psd.get_pinline_gradient( colors=colors, - color_map=self.light_color_map if self.is_authentic_front else self.dark_color_map, - location_map=self.gradient_location_map) + color_map=self.light_color_map + if self.is_authentic_front + else self.dark_color_map, + location_map=self.gradient_location_map, + ) @cached_property - def crown_colors(self) -> Union[list[int], list[dict]]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Use Solid Color or Gradient adjustment layer for Crown colors return psd.get_pinline_gradient( # Use identity for hybrid OR color enabled multicolor - colors=self.identity if self.is_hybrid or (self.is_multicolor and self.multicolor_pinlines) else ( - # Use pinlines if not a color code - self.pinlines if not is_multicolor_string(self.pinlines) else LAYERS.GOLD), + colors=self.identity + if self.is_hybrid or (self.is_multicolor and self.multicolor_pinlines) + # Use pinlines if not a color code + else ( + self.pinlines + if not is_multicolor_string(self.pinlines) + else LAYERS.GOLD + ), color_map=self.crown_color_map, - location_map=self.gradient_location_map) + location_map=self.gradient_location_map, + ) @cached_property - def pinlines_colors(self) -> Union[list[int], list[dict]]: + def pinlines_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: # Use alternate gradient location map return psd.get_pinline_gradient( # Use identity for hybrid OR color enabled multicolor - colors=self.identity if self.is_hybrid or (self.is_multicolor and self.multicolor_pinlines) else ( - # Use pinlines if not a color code - self.pinlines if not is_multicolor_string(self.pinlines) else LAYERS.GOLD), + colors=self.identity + if self.is_hybrid or (self.is_multicolor and self.multicolor_pinlines) + # Use pinlines if not a color code + else ( + self.pinlines + if not is_multicolor_string(self.pinlines) + else LAYERS.GOLD + ), color_map=self.pinlines_color_map, - location_map=self.gradient_location_map) + location_map=self.gradient_location_map, + ) """ * Groups """ @cached_property - def pt_group(self) -> Optional[LayerSet]: + def pt_group(self) -> LayerSet | None: """PT Box group, alternative Textless option used when Expansion Symbol is disabled.""" if self.is_textless and CFG.symbol_enabled: return if self.is_textless and self.is_pt_enabled: - return psd.getLayerSet(f'{LAYERS.PT_BOX} {LAYERS.TEXTLESS}') + return psd.getLayerSet(f"{LAYERS.PT_BOX} {LAYERS.TEXTLESS}") return super().pt_group @cached_property - def crown_group(self) -> LayerSet: + def crown_group(self) -> LayerSet | None: """Legendary Crown group, use inner group to allow textured overlays above.""" return psd.getLayerSet(LAYERS.LEGENDARY_CROWN, LAYERS.LEGENDARY_CROWN) @cached_property - def textbox_group(self) -> Optional[LayerSet]: + def textbox_group(self) -> LayerSet | None: """Optional[LayerSet]: Textbox group if not a 'Textless' render.""" if self.is_textless: return @@ -1525,17 +1811,21 @@ def text_layer_rules_name(self) -> str: """Compute the name of this layer separately, so we can use it for automatic textbox sizing.""" if self.is_creature: # Is a creature, Flipside P/T? - return LAYERS.RULES_TEXT_CREATURE_FLIP if ( - self.is_transform and self.is_flipside_creature - ) else LAYERS.RULES_TEXT_CREATURE + return ( + LAYERS.RULES_TEXT_CREATURE_FLIP + if (self.is_transform and self.is_flipside_creature) + else LAYERS.RULES_TEXT_CREATURE + ) # Not a creature, Flipside P/T? - return LAYERS.RULES_TEXT_NONCREATURE_FLIP if ( - self.is_transform and self.is_flipside_creature - ) else LAYERS.RULES_TEXT_NONCREATURE + return ( + LAYERS.RULES_TEXT_NONCREATURE_FLIP + if (self.is_transform and self.is_flipside_creature) + else LAYERS.RULES_TEXT_NONCREATURE + ) @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """Card rules text layer, use pre-computed layer name.""" return psd.getLayer(self.text_layer_rules_name, [self.text_group, self.size]) @@ -1544,13 +1834,21 @@ def text_layer_rules(self) -> Optional[ArtLayer]: """ @cached_property - def textbox_reference(self) -> Optional[ReferenceLayer]: + def textbox_reference(self) -> ReferenceLayer | None: """Use size appropriate textbox reference.""" - ref = psd.get_reference_layer(self.size, psd.getLayerSet(LAYERS.TEXTBOX_REFERENCE, self.text_group)) - if self.is_mdfc: - psd.copy_layer_mask( - layer_from=psd.getLayer(LAYERS.MDFC, [self.mask_group, LAYERS.TEXTBOX_REFERENCE]), - layer_to=ref) + ref = psd.get_reference_layer( + self.size, psd.getLayerSet(LAYERS.TEXTBOX_REFERENCE, self.text_group) + ) + if ( + self.is_mdfc + and ref + and ( + layer := psd.getLayer( + LAYERS.MDFC, [self.mask_group, LAYERS.TEXTBOX_REFERENCE] + ) + ) + ): + psd.copy_layer_mask(layer_from=layer, layer_to=ref) psd.apply_mask(ref) ref.visible = False return ref @@ -1560,33 +1858,43 @@ def textbox_reference(self) -> Optional[ReferenceLayer]: """ @cached_property - def textbox_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def textbox_shape(self) -> ArtLayer | None: """Support a size appropriate textbox shape and Transform Front addition.""" # Return None if textless if self.is_textless: - return [] + return None _shape_group = psd.getLayerSet(LAYERS.SHAPE, self.textbox_group) + return psd.getLayer(self.size, _shape_group) + @cached_property + def textbox_transform_front_addition_shape(self) -> ArtLayer | None: # Enable Transform Front addition if required - layers = [psd.getLayer(self.size, _shape_group)] - if self.is_transform and self.is_front: - layers.append(psd.getLayer(LAYERS.TRANSFORM_FRONT, _shape_group)) - return layers + if not self.is_textless and self.is_transform and self.is_front: + _shape_group = psd.getLayerSet(LAYERS.SHAPE, self.textbox_group) + return psd.getLayer(LAYERS.TRANSFORM_FRONT, _shape_group) + + @cached_property + def pinlines_shape(self) -> ArtLayer | None: + return None @cached_property - def pinlines_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: - """Support a variety of pinlines shapes including Transform, MDFC, Textless, Nickname, etc.""" + def pinlines_shapes(self) -> list[ArtLayer | LayerSet | None]: _shape_group = psd.getLayerSet(LAYERS.SHAPE, self.pinlines_group) # Name and typeline always included - layers = [ + layers: list[ArtLayer | LayerSet | None] = [ psd.getLayerSet( - LAYERS.TRANSFORM if self.is_transform else (LAYERS.MDFC if self.is_mdfc else LAYERS.NORMAL), - [_shape_group, LAYERS.NAME]), + LAYERS.TRANSFORM + if self.is_transform + else (LAYERS.MDFC if self.is_mdfc else LAYERS.NORMAL), + [_shape_group, LAYERS.NAME], + ), psd.getLayer( LAYERS.TEXTLESS if self.is_textless else self.size, - [_shape_group, LAYERS.TYPE_LINE])] + [_shape_group, LAYERS.TYPE_LINE], + ), + ] # Add nickname pinlines if required if self.is_nickname and not self.is_legendary: @@ -1602,89 +1910,166 @@ def pinlines_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None # Add Transform Front cutout if required if self.is_transform and self.is_front: layers.append( - psd.getLayer(LAYERS.TRANSFORM_FRONT, [_shape_group, LAYERS.TEXTBOX])) + psd.getLayer(LAYERS.TRANSFORM_FRONT, [_shape_group, LAYERS.TEXTBOX]) + ) return layers @cached_property - def twins_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def twins_shape(self) -> ArtLayer | None: + return None + + @cached_property + def twins_shapes(self) -> list[ArtLayer | LayerSet | None]: """Separate shapes for Name and Typeline box.""" _shape_group = psd.getLayerSet(LAYERS.SHAPE, self.twins_group) return [ psd.getLayer( - LAYERS.TRANSFORM if self.is_transform or self.is_mdfc else LAYERS.NORMAL, - [_shape_group, LAYERS.NAME]), + LAYERS.TRANSFORM + if self.is_transform or self.is_mdfc + else LAYERS.NORMAL, + [_shape_group, LAYERS.NAME], + ), psd.getLayer( LAYERS.TEXTLESS if self.is_textless else self.size, - [_shape_group, LAYERS.TYPE_LINE]) + [_shape_group, LAYERS.TYPE_LINE], + ), ] @cached_property - def crown_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def crown_shape(self) -> ArtLayer | None: """Vector shape for Legendary Crown.""" if not self.is_legendary: - return [] - return [psd.getLayer( + return None + return psd.getLayer( LAYERS.NICKNAME if self.is_nickname else LAYERS.NORMAL, - [self.crown_group, LAYERS.SHAPE] - )] + [self.crown_group, LAYERS.SHAPE], + ) + + @cached_property + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: + return [ + *super().enabled_shapes, + *self.pinlines_shapes, + *self.twins_shapes, + self.textbox_transform_front_addition_shape, + ] """ * Masks """ @cached_property - def border_mask(self) -> Optional[list]: + def border_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): """Support border mask for Textless and front face Transform modifications.""" - if self.is_textless: - return [psd.getLayer(LAYERS.TEXTLESS, [self.mask_group, LAYERS.BORDER]), self.border_group] - if self.is_transform and self.is_front: - return [psd.getLayer(LAYERS.TRANSFORM_FRONT, [self.mask_group, LAYERS.BORDER]), self.border_group] - return - - @cached_property - def pinlines_mask(self) -> dict: + if self.border_group: + if self.is_textless: + if layer := psd.getLayer( + LAYERS.TEXTLESS, [self.mask_group, LAYERS.BORDER] + ): + return (layer, self.border_group) + elif ( + self.is_transform + and self.is_front + and ( + layer := psd.getLayer( + LAYERS.TRANSFORM_FRONT, [self.mask_group, LAYERS.BORDER] + ) + ) + ): + return (layer, self.border_group) + + @cached_property + def pinlines_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): """Use pre-calculated frame type to find pinlines mask. This mask hides overlapping layer effects.""" - return { - 'mask': psd.getLayer(self.frame_type, [self.mask_group, LAYERS.PINLINES]), - 'layer': self.pinlines_group, - 'funcs': [psd.apply_mask_to_layer_fx] - } - - @cached_property - def pinlines_vector_mask(self) -> Optional[dict]: - """Enable the pinlines vector mask if card is Legendary. """ + if self.pinlines_group and ( + layer := psd.getLayer(self.frame_type, [self.mask_group, LAYERS.PINLINES]) + ): + return { + "mask": layer, + "layer": self.pinlines_group, + "funcs": [psd.apply_mask_to_layer_fx], + } + + @cached_property + def pinlines_vector_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): + """Enable the pinlines vector mask if card is Legendary.""" if not self.is_legendary: return - return { - 'mask': self.pinlines_group, - 'vector': True - } - - @cached_property - def crown_mask(self) -> Optional[dict]: + if self.pinlines_group: + return {"mask": self.pinlines_group, "vector": True} + + @cached_property + def crown_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): """Copy the pinlines mask to Legendary crown if card is Legendary.""" if not self.is_legendary: return - return { - 'mask': self.pinlines_mask['mask'], - 'layer': self.crown_group.parent, - 'funcs': [psd.apply_mask_to_layer_fx]} - - @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + if ( + (layer := psd.getLayer(self.frame_type, [self.mask_group, LAYERS.PINLINES])) + and self.crown_group + and isinstance((parent := self.crown_group.parent), LayerSet) + ): + return { + "mask": layer, + "layer": parent, + "funcs": [psd.apply_mask_to_layer_fx], + } + + @cached_property + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: """Masks that should be copied or enabled.""" return [ self.crown_mask, self.border_mask, self.pinlines_mask, - self.pinlines_vector_mask] + self.pinlines_vector_mask, + ] """ * Effects """ @cached_property - def nickname_fx(self) -> ArtLayer: + def nickname_fx(self) -> ArtLayer | None: """ArtLayer: Layer containing nickname effects.""" return psd.getLayer(LAYERS.NICKNAME, LAYERS.EFFECTS) @@ -1696,17 +2081,16 @@ def nickname_fx(self) -> ArtLayer: def basic_watermark_fx(self) -> list[LayerEffects]: """Defines the layer effects used on the Basic Land Watermark.""" return [ - EffectColorOverlay( - color=self.basic_watermark_color, - opacity=100), + EffectColorOverlay(color=self.basic_watermark_color, opacity=100), EffectBevel( - size=self.watermark_bevel_map.get(self.size), + size=self.watermark_bevel_map.get(self.size, 0), highlight_opacity=70, shadow_opacity=72, softness=14, rotation=45, altitude=22, - depth=100) + depth=100, + ), ] """Maps watermark bevel size to textbox size.""" @@ -1714,7 +2098,7 @@ def basic_watermark_fx(self) -> list[LayerEffects]: BorderlessTextbox.Short: 20, BorderlessTextbox.Medium: 22, BorderlessTextbox.Normal: 25, - BorderlessTextbox.Tall: 28 + BorderlessTextbox.Tall: 28, } """ @@ -1726,82 +2110,89 @@ def enable_frame_layers(self) -> None: super().enable_frame_layers() # Color the nickname plate if enabled in settings - if self.is_nickname and self.is_colored_nickname: - self.generate_layer( - group=self.nickname_group, - colors=self.twins_colors) + if self.is_nickname and self.is_colored_nickname and self.nickname_group: + self.generate_layer(group=self.nickname_group, colors=self.twins_colors) def enable_crown(self) -> None: """Allow modifying crown texture based on setting.""" # Enable Legendary Crown group and layers - self.generate_layer( - group=self.crown_group, - colors=self.crown_colors, - masks=self.crown_masks) + if self.crown_group: + self.generate_layer( + group=self.crown_group, colors=self.crown_colors, masks=self.crown_masks + ) # Enable Hollow Crown if self.is_hollow_crown: - self.enable_hollow_crown( - masks=[self.crown_group], - vector_masks=[self.pinlines_group]) + self.enable_hollow_crown() # Remove crown textures if disabled - if not self.crown_texture_enabled: + if not self.crown_texture_enabled and self.crown_group: + parent = self.crown_group.parent for n in ["Shading", "Highlight", "Overlay"]: - psd.getLayer(n, self.crown_group.parent).visible = False + if layer := psd.getLayer(n, parent): + layer.visible = False """ * Text Layer Methods """ def basic_text_layers(self) -> None: - # Establish whether this is a textless creature render with no symbol - self.text.extend([ - FormattedTextField( - layer=self.text_layer_mana, - contents=self.layout.mana_cost - ), - ScaledTextField( - layer=self.text_layer_type, - # Add Power/Toughness if rendering textless creature WITH symbol - contents=f"{self.layout.type_line} — {self.layout.power}/{self.layout.toughness}" if - self.is_creature and not self.is_pt_enabled else self.layout.type_line, - # Use PT box as right reference if rendering textless creature WITHOUT symbol - reference=psd.getLayer(LAYERS.PT_BOX, [self.pt_group, LAYERS.SHAPE]) - if self.is_textless and self.is_pt_enabled else self.type_reference + if self.text_layer_mana: + self.text.append( + FormattedTextField( + layer=self.text_layer_mana, contents=self.layout.mana_cost + ) + ) + if self.text_layer_type: + self.text.append( + ScaledTextField( + layer=self.text_layer_type, + # Add Power/Toughness if rendering textless creature WITH symbol + contents=f"{self.layout.type_line} — {self.layout.power}/{self.layout.toughness}" + if self.is_creature and not self.is_pt_enabled + else self.layout.type_line, + # Use PT box as right reference if rendering textless creature WITHOUT symbol + reference=psd.getLayer(LAYERS.PT_BOX, [self.pt_group, LAYERS.SHAPE]) + if self.is_textless and self.is_pt_enabled + else self.type_reference, + ) ) - ]) # Prompt for and add nickname if required _card_name = str(self.layout.name) - if self.is_nickname: + if self.is_nickname and self.text_layer_nickname: self.prompt_nickname_text() self.text.append( ScaledWidthTextField( layer=self.text_layer_nickname, contents=self.layout.name, - reference=self.nickname_shape)) + reference=self.nickname_shape, + ) + ) _card_name = self.layout.nickname # Add regular card name - self.text.append( - ScaledTextField( - layer=self.text_layer_name, - contents=_card_name, - reference=self.name_reference - )) + if self.text_layer_name: + self.text.append( + ScaledTextField( + layer=self.text_layer_name, + contents=_card_name, + reference=self.name_reference, + ) + ) def rules_text_and_pt_layers(self) -> None: """Skip this step for 'Textless' renders.""" if self.is_textless: - if self.is_pt_enabled: + if self.is_pt_enabled and self.text_layer_pt: self.text.append( TextField( layer=self.text_layer_pt, - contents=f'{self.layout.power}/' - f'{self.layout.toughness}')) + contents=f"{self.layout.power}/{self.layout.toughness}", + ) + ) return super().rules_text_and_pt_layers() @@ -1813,16 +2204,17 @@ def textless_adjustments(self) -> None: """Actions taken if this is a 'Textless' render.""" # Make positioning adjustments - if self.is_type_shifted: + if self.is_type_shifted and self.indicator_group: self.indicator_group.translate(-10, 0) - self.text_layer_type.translate(-10, 0) + if self.text_layer_type: + self.text_layer_type.translate(-10, 0) # Align and add PT text for creatures with no expansion symbol if self.is_pt_enabled: psd.align_all( layer=self.text_layer_pt, - ref=psd.getLayer(LAYERS.PT_BOX, [ - self.pt_group, LAYERS.SHAPE])) + ref=psd.getLayer(LAYERS.PT_BOX, [self.pt_group, LAYERS.SHAPE]), + ) return # Otherwise just shift the symbol over @@ -1833,39 +2225,47 @@ def token_adjustments(self) -> None: """Actions taken if this is a 'Token' card.""" # Change name font and center it - self.text_layer_name.textItem.font = CON.font_artist - psd.align_horizontal(self.text_layer_name, self.twins_shape[0]) + if self.text_layer_name: + self.text_layer_name.textItem.font = CON.font_artist + psd.align_horizontal(self.text_layer_name, self.twins_shapes[0]) # Add name plate shadow - psd.getLayer(LAYERS.TOKEN, self.text_group).visible = True + if layer := psd.getLayer(LAYERS.TOKEN, self.text_group): + layer.visible = True def textbox_positioning(self) -> None: """Reposition various elements when textbox size isn't 'Tall' (the default).""" # Get the delta between the highest box and the target box - shape = psd.getLayer(LAYERS.TALL, [ - self.pinlines_group, - LAYERS.SHAPE, - LAYERS.TYPE_LINE]) - dims_ref = psd.get_layer_dimensions(shape) - dims_obj = psd.get_layer_dimensions(self.pinlines_shape[1]) - delta = dims_obj[Dimensions.CenterY] - dims_ref[Dimensions.CenterY] - self.text_layer_type.translate(0, delta) - - # Shift expansion symbol - if CFG.symbol_enabled and self.expansion_symbol_layer: - self.expansion_symbol_layer.translate(0, delta) - - # Shift indicator - if self.is_type_shifted: - self.indicator_group.parent.translate(0, delta) + shape = psd.getLayer( + LAYERS.TALL, [self.pinlines_group, LAYERS.SHAPE, LAYERS.TYPE_LINE] + ) + if shape: + dims_ref = psd.get_layer_dimensions(shape) + if layer := self.pinlines_shapes[1]: + dims_obj = psd.get_layer_dimensions(layer) + delta = dims_obj["center_y"] - dims_ref["center_y"] + + if self.text_layer_type: + self.text_layer_type.translate(0, delta) + + # Shift expansion symbol + if CFG.symbol_enabled and self.expansion_symbol_layer: + self.expansion_symbol_layer.translate(0, delta) + + # Shift indicator + if ( + self.is_type_shifted + and self.indicator_group + and isinstance((parent := self.indicator_group.parent), LayerSet) + ): + parent.translate(0, delta) def text_layer_fx(self) -> None: """Handles all specialized text adjustments for a variety of render settings.""" # Add drop shadow if enabled and allowed if self.is_drop_shadow: - # Name and Typeline psd.enable_layer_fx(self.text_layer_name) psd.enable_layer_fx(self.text_layer_type) @@ -1879,7 +2279,9 @@ def text_layer_fx(self) -> None: psd.enable_layer_fx(self.text_layer_flipside_pt) # Allow exception for PT drop shadow on front face Vehicle cards - if (self.is_drop_shadow or (self.drop_shadow_enabled and self.is_vehicle)) and self.is_pt_enabled: + if ( + self.is_drop_shadow or (self.drop_shadow_enabled and self.is_vehicle) + ) and self.is_pt_enabled: psd.enable_layer_fx(self.text_layer_pt) """ @@ -1891,8 +2293,13 @@ def format_nickname_text(self) -> None: super().format_nickname_text() # Copy effects to legendary crown - if self.is_legendary: - psd.copy_layer_fx(self.nickname_fx, self.crown_group.parent) + if ( + self.is_legendary + and self.nickname_fx + and self.crown_group + and isinstance((parent := self.crown_group.parent), LayerSet) + ): + psd.copy_layer_fx(self.nickname_fx, parent) if self.is_drop_shadow: psd.enable_layer_fx(self.text_layer_nickname) @@ -1909,7 +2316,11 @@ def text_layers_transform_front(self) -> None: self.swap_font_color() # Switch flipside PT to light gray - if not self.is_authentic_front and self.is_flipside_creature: + if ( + not self.is_authentic_front + and self.is_flipside_creature + and self.text_layer_flipside_pt + ): self.text_layer_flipside_pt.textItem.color = psd.get_rgb(*[186, 186, 186]) def text_layers_transform_back(self): @@ -1929,7 +2340,7 @@ def text_layers_mdfc_front(self) -> None: * Util Methods """ - def swap_font_color(self, color: SolidColor = None) -> None: + def swap_font_color(self, color: SolidColor | None = None) -> None: """Switch the font color of each key text layer. Args: @@ -1940,15 +2351,17 @@ def swap_font_color(self, color: SolidColor = None) -> None: color = color or self.RGB_BLACK # Name and Typeline - self.text_layer_name.textItem.color = color - self.text_layer_type.textItem.color = color + if self.text_layer_name: + self.text_layer_name.textItem.color = color + if self.text_layer_type: + self.text_layer_type.textItem.color = color # Rules text if not textless - if not self.is_textless: + if not self.is_textless and self.text_layer_rules: self.text_layer_rules.textItem.color = color # PT if card is a non-vehicle creature - if self.is_pt_enabled and not self.is_vehicle: + if self.is_pt_enabled and not self.is_vehicle and self.text_layer_pt: self.text_layer_pt.textItem.color = color @@ -1956,23 +2369,28 @@ class ClassicModernTemplate(VectorTransformMod, VectorMDFCMod, VectorTemplate): """A modern frame version of iDerp's 'Classic Remastered' template.""" # Static properties - is_vehicle: bool = False - frame_suffix: str = 'Extended' - template_suffix: str = 'Classic Modern' + frame_suffix: str = "Extended" + template_suffix: str = "Classic Modern" + + @cached_property + def is_vehicle(self) -> bool: + return False # Color Maps - pinlines_color_map = {**pinlines_color_map.copy(), "Land": "#604a33"} + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return {**pinlines_color_map.copy(), "Land": "#604a33"} """ * Bool """ - @property + @cached_property def is_extended(self) -> bool: """TODO: Based on whether the extended setting is enabled.""" return True - @property + @cached_property def is_content_aware_enabled(self) -> bool: """Force enabled if 'is_extended' setting is enabled.""" return True if self.is_extended else super().is_content_aware_enabled @@ -1991,13 +2409,18 @@ def crown_mode(self) -> str: """ @cached_property - def textbox_reference(self) -> Optional[ArtLayer]: + def textbox_reference(self) -> ReferenceLayer | None: """Use a mask to reduce reference size for MDFC cards.""" - ref = getattr(super(), 'textbox_reference') - if self.is_mdfc: + ref = getattr(super(), "textbox_reference") + if self.is_mdfc and ( + layer := psd.getLayer( + LAYERS.MDFC, [self.mask_group, LAYERS.TEXTBOX_REFERENCE] + ) + ): psd.copy_layer_mask( - layer_from=psd.getLayer(LAYERS.MDFC, [self.mask_group, LAYERS.TEXTBOX_REFERENCE]), - layer_to=ref) + layer_from=layer, + layer_to=ref, + ) psd.apply_mask(ref) ref.visible = False return ref @@ -2013,12 +2436,12 @@ def textbox_colors(self) -> list[str]: # 2-3 color lands if 1 < len(self.identity) < self.color_limit and self.is_land: # Dual or tri colors - return [f'{n} {LAYERS.LAND}' for n in self.identity] + return [f"{n} {LAYERS.LAND}" for n in self.identity] # Plain land background if self.pinlines == LAYERS.LAND: return [LAYERS.LAND] # All other land backgrounds - return [f'{self.pinlines} {LAYERS.LAND}'] + return [f"{self.pinlines} {LAYERS.LAND}"] # Hybrid cards if self.is_hybrid: return list(self.pinlines) @@ -2030,102 +2453,188 @@ def crown_colors(self) -> str: """Support multicolor based on color limit.""" if self.crown_mode == ModernClassicCrown.TextureBackground: return self.background - return self.identity if 1 < len(self.identity) < self.color_limit else self.pinlines + return ( + self.identity + if 1 < len(self.identity) < self.color_limit + else self.pinlines + ) """ * Shapes """ @cached_property - def crown_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def crown_shape(self) -> ArtLayer | None: # Support Normal and Extended return psd.getLayer( LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, - [LAYERS.LEGENDARY_CROWN, LAYERS.SHAPE]) + [LAYERS.LEGENDARY_CROWN, LAYERS.SHAPE], + ) @cached_property - def border_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def border_shape(self) -> ArtLayer | None: # Support Normal and Legendary return psd.getLayer( - LAYERS.LEGENDARY if self.is_legendary else LAYERS.NORMAL, - LAYERS.BORDER) + LAYERS.LEGENDARY if self.is_legendary else LAYERS.NORMAL, LAYERS.BORDER + ) @cached_property - def pinlines_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: + def pinlines_shapes(self) -> list[ArtLayer | LayerSet | None]: # Add transform cutout textbox - masks = [ - psd.getLayer( - LAYERS.TRANSFORM_FRONT, - [self.pinlines_group, LAYERS.SHAPE, LAYERS.TEXTBOX] - ) - ] if self.is_transform and self.is_front else [] + masks = ( + [ + psd.getLayer( + LAYERS.TRANSFORM_FRONT, + [self.pinlines_group, LAYERS.SHAPE, LAYERS.TEXTBOX], + ) + ] + if self.is_transform and self.is_front + else [] + ) # Add Twins shape, supports Normal and Transform return [ *masks, psd.getLayerSet( - LAYERS.TRANSFORM if self.is_transform else ( - LAYERS.MDFC if self.is_mdfc else LAYERS.NORMAL), - [self.pinlines_group, LAYERS.SHAPE, LAYERS.NAME]), + LAYERS.TRANSFORM + if self.is_transform + else (LAYERS.MDFC if self.is_mdfc else LAYERS.NORMAL), + [self.pinlines_group, LAYERS.SHAPE, LAYERS.NAME], + ), ] @cached_property - def textbox_shape(self) -> Union[LayerObjectTypes, list[LayerObjectTypes], None]: - return [ - psd.getLayer(LAYERS.TRANSFORM_FRONT, [self.textbox_group, LAYERS.SHAPE]) - ] if self.is_transform and self.is_front else [] + def textbox_shape(self) -> ArtLayer | None: + if self.is_transform and self.is_front: + return psd.getLayer( + LAYERS.TRANSFORM_FRONT, [self.textbox_group, LAYERS.SHAPE] + ) + + @cached_property + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: + return [*super().enabled_shapes, *self.pinlines_shapes] """ * Masks """ @cached_property - def twins_mask(self) -> list[dict]: - return [{ - 'mask': self.twins_group, - 'vector': True - }] if self.is_extended else [] - - @cached_property - def border_mask(self) -> list[Union[ArtLayer, LayerSet]]: - return [ - psd.getLayer( - LAYERS.MDFC if self.is_mdfc and not self.is_legendary else LAYERS.NORMAL, - [self.mask_group, LAYERS.BORDER, LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL] - ), self.border_group - ] - - @cached_property - def background_mask(self) -> list[Union[ArtLayer, LayerSet]]: - return [ - psd.getLayer( + def twins_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): + if self.is_extended and self.twins_group: + return {"mask": self.twins_group, "vector": True} + + @cached_property + def border_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): + if self.border_group and ( + layer := psd.getLayer( + LAYERS.MDFC + if self.is_mdfc and not self.is_legendary + else LAYERS.NORMAL, + [ + self.mask_group, + LAYERS.BORDER, + LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, + ], + ) + ): + return (layer, self.border_group) + + @cached_property + def background_mask( + self, + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): + if self.background_group and ( + layer := psd.getLayer( LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, - [self.mask_group, LAYERS.BACKGROUND] - ), self.background_group - ] + [self.mask_group, LAYERS.BACKGROUND], + ) + ): + return ( + layer, + self.background_group, + ) @cached_property - def pinlines_mask(self) -> list[Union[list, dict]]: + def pinlines_mask( + self, + ) -> list[ + ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ) + ]: # Mask covering top on Legendary cards - masks: list[Union[list, dict]] = [{ - 'mask': self.pinlines_group, - 'vector': True - }] if self.is_legendary else [] - # Fade mask and covering layer effects - return [*masks, { - 'mask': psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.mask_group, LAYERS.PINLINES, LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL]), - 'layer': self.pinlines_group, - 'funcs': [psd.apply_mask_to_layer_fx] - }] - - @cached_property - def enabled_masks(self) -> list[Union[list, dict]]: + masks: list[ + ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ) + ] = [] + if self.pinlines_group: + if self.is_legendary: + masks.append({"mask": self.pinlines_group, "vector": True}) + # Fade mask and covering layer effects + if layer := psd.getLayer( + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [ + self.mask_group, + LAYERS.PINLINES, + LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, + ], + ): + masks.append( + { + "mask": layer, + "layer": self.pinlines_group, + "funcs": [psd.apply_mask_to_layer_fx], + } + ) + return masks + + @cached_property + def enabled_masks( + self, + ) -> list[ + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ]: return [ self.border_mask, self.background_mask, *self.pinlines_mask, - *self.twins_mask + self.twins_mask, ] """ @@ -2134,30 +2643,27 @@ def enabled_masks(self) -> list[Union[list, dict]]: def enable_crown(self) -> None: """Enable the Legendary crown, only called if card is Legendary.""" + if self.crown_group: + # Generate Legendary Crown using pinline colors + if self.crown_mode == ModernClassicCrown.Pinlines: + self.generate_layer(group=self.crown_group, colors=self.pinlines_colors) + return - # Generate Legendary Crown using pinline colors - if self.crown_mode == ModernClassicCrown.Pinlines: + # Generate Legendary Crown using textures self.generate_layer( - group=self.crown_group, - colors=self.pinlines_colors) - return - - # Generate Legendary Crown using textures - self.generate_layer( - group=self.crown_group, - colors=self.crown_colors, - masks=self.crown_masks) + group=self.crown_group, colors=self.crown_colors, masks=self.crown_masks + ) """ * Transform Methods """ def enable_transform_layers_back(self) -> None: - # Use darker Twins and PT texture - if self.is_creature: - psd.getLayer(LAYERS.LIGHTEN, self.pt_group).visible = False - psd.getLayer(LAYERS.LIGHTEN, self.twins_group).visible = False + if self.is_creature and (layer := psd.getLayer(LAYERS.LIGHTEN, self.pt_group)): + layer.visible = False + if layer := psd.getLayer(LAYERS.LIGHTEN, self.twins_group): + layer.visible = False super().enable_transform_layers_back() """ @@ -2165,13 +2671,18 @@ def enable_transform_layers_back(self) -> None: """ def enable_mdfc_layers_back(self) -> None: - # Use darker Twins and PT texture and white font color if self.is_creature: - psd.getLayer(LAYERS.LIGHTEN, self.pt_group).opacity = 10 - self.text_layer_pt.textItem.color = self.RGB_WHITE - psd.enable_layer_fx(self.text_layer_pt) - psd.getLayer(LAYERS.LIGHTEN, self.twins_group).opacity = 10 - self.text_layer_name.textItem.color = self.RGB_WHITE - psd.enable_layer_fx(self.text_layer_name) + if layer := psd.getLayer(LAYERS.LIGHTEN, self.pt_group): + layer.opacity = 10 + if self.text_layer_pt: + self.text_layer_pt.textItem.color = self.RGB_WHITE + psd.enable_layer_fx(self.text_layer_pt) + + if layer := psd.getLayer(LAYERS.LIGHTEN, self.twins_group): + layer.opacity = 10 + if self.text_layer_name: + self.text_layer_name.textItem.color = self.RGB_WHITE + psd.enable_layer_fx(self.text_layer_name) + super().enable_transform_layers_back() diff --git a/src/templates/planar.py b/src/templates/planar.py index 122ff9d6..e31ad000 100644 --- a/src/templates/planar.py +++ b/src/templates/planar.py @@ -1,19 +1,19 @@ """ * PLANAR TEMPLATES """ + # Standard Library Imports from functools import cached_property -from typing import Optional # Third Party Imports -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer # Local Imports from src import CFG from src.templates._core import StarterTemplate import src.text_layers as text_classes from src.enums.layers import LAYERS -from src.layouts import CardLayout +from src.layouts import NormalLayout import src.helpers as psd """ @@ -28,63 +28,72 @@ class PlanarTemplate(StarterTemplate): Needs a complete rework and a 'Modifier' class. """ - def __init__(self, layout: CardLayout, **kwargs): + def __init__(self, layout: NormalLayout): CFG.exit_early = True - super().__init__(layout, **kwargs) + super().__init__(layout) @cached_property - def text_layer_static_ability(self) -> ArtLayer: + def text_layer_static_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.STATIC_ABILITY, self.text_group) @cached_property - def text_layer_chaos_ability(self) -> ArtLayer: + def text_layer_chaos_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.CHAOS_ABILITY, self.text_group) def basic_text_layers(self): """No mana cost, don't scale name layer.""" - self.text.extend([ - text_classes.TextField( - layer=self.text_layer_name, - contents=self.layout.name - ), - text_classes.ScaledTextField( - layer=self.text_layer_type, - contents=self.layout.type_line, - reference=self.type_reference + if self.text_layer_name: + self.text.append( + text_classes.TextField( + layer=self.text_layer_name, contents=self.layout.name + ) + ) + if self.text_layer_type: + self.text.append( + text_classes.ScaledTextField( + layer=self.text_layer_type, + contents=self.layout.type_line, + reference=self.type_reference, + ) ) - ]) def rules_text_and_pt_layers(self): - # Phenomenon card? if self.layout.type_line == LAYERS.PHENOMENON: - # Insert oracle text into static ability layer and disable chaos ability & layer mask on textbox - self.text.append( - text_classes.FormattedTextField( - layer=self.text_layer_static_ability, - contents=self.layout.oracle_text + if self.text_layer_static_ability: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_static_ability, + contents=self.layout.oracle_text, + ) ) - ) psd.enable_mask(psd.getLayerSet(LAYERS.TEXTBOX)) - psd.getLayer(LAYERS.CHAOS_SYMBOL, self.text_group).visible = False - self.text_layer_chaos_ability.visible = False + if layer := psd.getLayer(LAYERS.CHAOS_SYMBOL, self.text_group): + layer.visible = False + if self.text_layer_chaos_ability: + self.text_layer_chaos_ability.visible = False else: - # Split oracle text on last line break, insert everything before into static, the rest into chaos linebreak_index = self.layout.oracle_text.rindex("\n") - self.text.extend([ - text_classes.FormattedTextField( - layer=self.text_layer_static_ability, - contents=self.layout.oracle_text[0:linebreak_index] - ), - text_classes.FormattedTextField( - layer=self.text_layer_chaos_ability, - contents=self.layout.oracle_text[linebreak_index + 1:] - ), - ]) + if self.text_layer_static_ability: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_static_ability, + contents=self.layout.oracle_text[0:linebreak_index], + ) + ) + if self.text_layer_chaos_ability: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_chaos_ability, + contents=self.layout.oracle_text[linebreak_index + 1 :], + ) + ) - def paste_scryfall_scan(self, rotate: bool = False, visible: bool = False) -> Optional[ArtLayer]: + def paste_scryfall_scan( + self, rotate: bool = False, visible: bool = False + ) -> ArtLayer | None: """Ensure we rotate the scan for Planar cards.""" return super().paste_scryfall_scan(rotate=True, visible=visible) diff --git a/src/templates/planeswalker.py b/src/templates/planeswalker.py index f10366fb..c65b565f 100644 --- a/src/templates/planeswalker.py +++ b/src/templates/planeswalker.py @@ -1,20 +1,21 @@ """ * PLANESWALKER TEMPLATES """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Callable, Sequence +from collections.abc import Callable, Sequence # Third Party Imports from photoshop.api import ElementPlacement, ColorBlendMode -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports from src.enums.layers import LAYERS import src.helpers as psd from src.helpers import scale_text_layers_to_height -from src.layouts import PlaneswalkerLayouts +from src.layouts import NormalLayout, PlaneswalkerAbility, PlaneswalkerLayout from src.templates._core import StarterTemplate from src.templates._cosmetic import BorderlessMod, FullartMod from src.templates.mdfc import MDFCMod @@ -27,7 +28,7 @@ """ -class PlaneswalkerMod (FullartMod, StarterTemplate): +class PlaneswalkerMod(FullartMod, StarterTemplate): """A modifier class which adds methods required for Planeswalker type cards introduced in Lorwyn block. Adds: @@ -35,28 +36,29 @@ class PlaneswalkerMod (FullartMod, StarterTemplate): * Planeswalker text layer positioning. * Planeswalker ability mask generation. """ - frame_suffix = 'Normal' - def __init__(self, layout: PlaneswalkerLayouts, **kwargs): - super().__init__(layout, **kwargs) + frame_suffix = "Normal" + + def __init__(self, layout: NormalLayout): + super().__init__(layout) # Settable Properties - self._ability_layers = [] - self._icons = [] - self._colons = [] + self._ability_layers: list[ArtLayer] = [] + self._icons: list[ArtLayer | LayerSet | None] = [] + self._colons: list[ArtLayer | LayerSet | None] = [] @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add and position Planeswalker text layers, add ability mask.""" return [*super().text_layer_methods, self.pw_text_layers] @cached_property - def post_text_methods(self) -> list[Callable]: + def post_text_methods(self) -> list[Callable[[], None]]: """Add Planeswalker layer positioning and ability mask step.""" return [ *super().post_text_methods, self.pw_layer_positioning, - self.pw_ability_mask + self.pw_ability_mask, ] """ @@ -64,16 +66,16 @@ def post_text_methods(self) -> list[Callable]: """ @cached_property - def art_frame_vertical(self): + def art_frame_vertical(self) -> str: """Use special Borderless frame for 'Colorless' cards.""" if self.is_colorless: return LAYERS.BORDERLESS_FRAME return LAYERS.FULL_ART_FRAME - + @cached_property def ability_text_spacing(self) -> float | int: return 64 - + @cached_property def ability_text_scaling_step_sizes(self) -> Sequence[float] | None: return None @@ -83,9 +85,11 @@ def ability_text_scaling_step_sizes(self) -> Sequence[float] | None: """ @cached_property - def abilities(self) -> list[dict]: + def abilities(self) -> list[PlaneswalkerAbility]: """List of Planeswalker abilities data.""" - return self.layout.pw_abilities + if isinstance(self.layout, PlaneswalkerLayout): + return self.layout.pw_abilities + return [] @cached_property def fill_color(self): @@ -101,23 +105,23 @@ def ability_layers(self) -> list[ArtLayer]: return self._ability_layers @ability_layers.setter - def ability_layers(self, value): + def ability_layers(self, value: list[ArtLayer]): self._ability_layers = value @property - def colons(self) -> list: + def colons(self) -> list[ArtLayer | LayerSet | None]: return self._colons @colons.setter - def colons(self, value): + def colons(self, value: list[ArtLayer | LayerSet | None]): self._colons = value @property - def icons(self) -> list: + def icons(self) -> list[ArtLayer | LayerSet | None]: return self._icons @icons.setter - def icons(self, value): + def icons(self, value: list[ArtLayer | LayerSet | None]): self._icons = value """ @@ -125,75 +129,88 @@ def icons(self, value): """ @cached_property - def group(self) -> LayerSet: + def planeswalker_group(self) -> LayerSet | None: """The main Planeswalker layer group, sized according to number of abilities.""" - if group := psd.getLayerSet(f"pw-{str(self.layout.pw_size)}"): + if isinstance(self.layout, PlaneswalkerLayout) and ( + group := psd.getLayerSet(f"pw-{str(self.layout.pw_size)}") + ): group.visible = True return group @cached_property - def loyalty_group(self) -> LayerSet: + def loyalty_group(self) -> LayerSet | None: """Group containing Planeswalker loyalty graphics.""" return psd.getLayerSet(LAYERS.LOYALTY_GRAPHICS) @cached_property - def border_group(self) -> Optional[LayerSet]: + def border_group(self) -> LayerSet | None: """Border group, nested in the appropriate Planeswalker group.""" - return psd.getLayerSet(LAYERS.BORDER, self.group) + return psd.getLayerSet(LAYERS.BORDER, self.planeswalker_group) @cached_property - def mask_group(self) -> Optional[LayerSet]: + def mask_group(self) -> LayerSet | None: """Group containing the vector shapes used to create the ragged lines divider mask.""" return psd.getLayerSet(LAYERS.MASKS) @cached_property - def textbox_group(self) -> Optional[LayerSet]: + def textbox_group(self) -> LayerSet | None: """Group to populate with ragged lines divider mask.""" - return psd.getLayerSet("Ragged Lines", [self.group, LAYERS.TEXTBOX, "Ability Dividers"]) + return psd.getLayerSet( + "Ragged Lines", + [self.planeswalker_group, LAYERS.TEXTBOX, "Ability Dividers"], + ) @cached_property - def text_group(self) -> LayerSet: + def text_group(self) -> LayerSet | None: """Text Layer group, nexted in the appropriate Planeswalker group.""" - return psd.getLayerSet(LAYERS.TEXT_AND_ICONS, self.group) + return psd.getLayerSet(LAYERS.TEXT_AND_ICONS, self.planeswalker_group) """ * Frame Layers """ @cached_property - def twins_layer(self) -> Optional[ArtLayer]: - return psd.getLayer(self.twins, psd.getLayerSet(LAYERS.TWINS, self.group)) + def twins_layer(self) -> ArtLayer | None: + return psd.getLayer( + self.twins, psd.getLayerSet(LAYERS.TWINS, self.planeswalker_group) + ) @cached_property - def pinlines_layer(self) -> Optional[ArtLayer]: - return psd.getLayer(self.pinlines, psd.getLayerSet(LAYERS.PINLINES, self.group)) + def pinlines_layer(self) -> ArtLayer | None: + return psd.getLayer( + self.pinlines, psd.getLayerSet(LAYERS.PINLINES, self.planeswalker_group) + ) @cached_property - def background_layer(self) -> Optional[ArtLayer]: - return psd.getLayer(self.background, psd.getLayerSet(LAYERS.BACKGROUND, self.group)) + def background_layer(self) -> ArtLayer | None: + return psd.getLayer( + self.background, psd.getLayerSet(LAYERS.BACKGROUND, self.planeswalker_group) + ) @cached_property - def color_indicator_layer(self) -> Optional[ArtLayer]: - return psd.getLayer(self.pinlines, [self.group, LAYERS.COLOR_INDICATOR]) + def color_indicator_layer(self) -> ArtLayer | None: + return psd.getLayer( + self.pinlines, [self.planeswalker_group, LAYERS.COLOR_INDICATOR] + ) """ * Text Layers """ @cached_property - def text_layer_loyalty(self) -> ArtLayer: + def text_layer_loyalty(self) -> ArtLayer | None: return psd.getLayer(LAYERS.TEXT, [self.loyalty_group, LAYERS.STARTING_LOYALTY]) @cached_property - def text_layer_ability(self) -> ArtLayer: + def text_layer_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.ABILITY_TEXT, self.loyalty_group) @cached_property - def text_layer_static(self) -> ArtLayer: + def text_layer_static(self) -> ArtLayer | None: return psd.getLayer(LAYERS.STATIC_TEXT, self.loyalty_group) @cached_property - def text_layer_colon(self) -> ArtLayer: + def text_layer_colon(self) -> ArtLayer | None: return psd.getLayer(LAYERS.COLON, self.loyalty_group) """ @@ -201,7 +218,7 @@ def text_layer_colon(self) -> ArtLayer: """ @cached_property - def loyalty_reference(self) -> ReferenceLayer: + def loyalty_reference(self) -> ReferenceLayer | None: """ArtLayer: Reference used to check ability layer collision with the loyalty box.""" return psd.get_reference_layer(LAYERS.LOYALTY_REFERENCE, self.loyalty_group) @@ -210,7 +227,6 @@ def loyalty_reference(self) -> ReferenceLayer: """ def enable_frame_layers(self): - # Twins if self.twins_layer: self.twins_layer.visible = True @@ -239,56 +255,69 @@ def pw_text_layers(self) -> None: self.pw_add_ability(ability) # Starting loyalty - if self.layout.loyalty: + if ( + isinstance(self.layout, PlaneswalkerLayout) + and self.layout.loyalty + and self.text_layer_loyalty + ): self.text_layer_loyalty.textItem.contents = self.layout.loyalty - else: - self.text_layer_loyalty.parent.visible = False + elif self.text_layer_loyalty and isinstance( + (parent := self.text_layer_loyalty.parent), LayerSet + ): + parent.visible = False def pw_layer_positioning(self) -> None: """Position Planeswalker ability layers and icons.""" - - # Auto-position the ability text, colons, and shields. - spacing = self.app.scale_by_dpi(self.ability_text_spacing) - spaces = len(self.ability_layers) + 1 - total_height = self.textbox_reference.dims['height'] - (spacing * spaces) - - # Resize text items till they fit in the available space - font_size = scale_text_layers_to_height( - text_layers=self.ability_layers, - ref_height=total_height, - step_sizes=self.ability_text_scaling_step_sizes) - - # Space abilities evenly apart - uniform_gap = True if len(self.ability_layers) < 3 or not self.layout.loyalty else False - psd.spread_layers_over_reference( - layers=self.ability_layers, - ref=self.textbox_reference, - gap=spacing if not uniform_gap else None) - - # Adjust text to avoid loyalty badge - if self.layout.loyalty and self.loyalty_reference: - psd.clear_reference_vertical_multi( + if isinstance(self.layout, PlaneswalkerLayout) and self.textbox_reference: + # Auto-position the ability text, colons, and shields. + spacing = self.app.scale_by_dpi(self.ability_text_spacing) + spaces = len(self.ability_layers) + 1 + total_height = self.textbox_reference.dims["height"] - (spacing * spaces) + + # Resize text items till they fit in the available space + font_size = scale_text_layers_to_height( text_layers=self.ability_layers, + ref_height=total_height, + step_sizes=self.ability_text_scaling_step_sizes, + ) + + # Space abilities evenly apart + uniform_gap = ( + True + if len(self.ability_layers) < 3 or not self.layout.loyalty + else False + ) + psd.spread_layers_over_reference( + layers=self.ability_layers, ref=self.textbox_reference, - loyalty_ref=self.loyalty_reference, - space=spacing, - uniform_gap=uniform_gap, - font_size=font_size, - docref=self.docref, - docsel=self.doc_selection) - - # Align colons and shields to respective text layers - for i, ref_layer in enumerate(self.ability_layers): - # Break if we encounter a length mismatch - if len(self.icons) < (i + 1) or len(self.colons) < (i + 1): - self.raise_warning("Encountered bizarre Planeswalker data!") - break - # Skip if this is a static ability - if self.icons[i] and self.colons[i]: - before = self.colons[i].bounds[1] - psd.align_vertical(self.colons[i], ref_layer) - difference = self.colons[i].bounds[1] - before - self.icons[i].translate(0, difference) + gap=spacing if not uniform_gap else 0, + ) + + # Adjust text to avoid loyalty badge + if self.layout.loyalty and self.loyalty_reference: + psd.clear_reference_vertical_multi( + text_layers=self.ability_layers, + ref=self.textbox_reference, + loyalty_ref=self.loyalty_reference, + space=spacing, + uniform_gap=uniform_gap, + font_size=font_size, + docref=self.docref, + docsel=self.doc_selection, + ) + + # Align colons and shields to respective text layers + for i, ref_layer in enumerate(self.ability_layers): + # Break if we encounter a length mismatch + if len(self.icons) < (i + 1) or len(self.colons) < (i + 1): + self.raise_warning("Encountered bizarre Planeswalker data!") + break + # Skip if this is a static ability + if (icon := self.icons[i]) and (colon := self.colons[i]): + before = colon.bounds[1] + psd.align_vertical(colon, ref_layer) + difference = colon.bounds[1] - before + icon.translate(0, difference) def pw_ability_mask(self) -> None: """Position the ragged edge ability mask.""" @@ -297,21 +326,36 @@ def pw_ability_mask(self) -> None: line_top = psd.getLayer(LAYERS.TOP, self.mask_group) line_bottom = psd.getLayer(LAYERS.BOTTOM, self.mask_group) - # Create our line mask pairs lines: list[list[ArtLayer]] = [] - for i in range(len(self.ability_layers)-1): - if lines and len(lines[-1]) == 1: - lines[-1].append(line_bottom.duplicate(self.textbox_group, ElementPlacement.PlaceInside)) - else: - lines.append([line_top.duplicate(self.textbox_group, ElementPlacement.PlaceInside)]) + if line_top and line_bottom: + # Create our line mask pairs + for _i in range(len(self.ability_layers) - 1): + if lines and len(lines[-1]) == 1: + lines[-1].append( + line_bottom.duplicate( + self.textbox_group, ElementPlacement.PlaceInside + ) + ) + else: + lines.append( + [ + line_top.duplicate( + self.textbox_group, ElementPlacement.PlaceInside + ) + ] + ) # Position and fill each pair n = 0 - for i, group in enumerate(lines): + for group in lines: # Position the top line, bottom if provided, then fill the area between - self.position_divider([self.ability_layers[n], self.ability_layers[n+1]], group[0]) + self.position_divider( + [self.ability_layers[n], self.ability_layers[n + 1]], group[0] + ) if len(group) == 2: - self.position_divider([self.ability_layers[n+1], self.ability_layers[n+2]], group[1]) + self.position_divider( + [self.ability_layers[n + 1], self.ability_layers[n + 2]], group[1] + ) self.fill_between_dividers(group) # Skip every other ability n += 2 @@ -320,35 +364,51 @@ def pw_ability_mask(self) -> None: * Utility Methods """ - def pw_add_ability(self, ability: dict) -> None: + def pw_add_ability(self, ability: PlaneswalkerAbility) -> None: """Add a Planeswalker ability. Args: ability: Planeswalker ability data. """ # Create an icon and colon if this isn't a static ability - static = False if ability.get('icon') and ability.get('cost') else True - icon = None if static else psd.getLayerSet(ability.get('icon', '0'), self.loyalty_group) - colon = None if static else self.text_layer_colon.duplicate() + static = False if ability.get("icon") and ability.get("cost") else True + icon = ( + None + if static + else psd.getLayerSet(ability.get("icon", "0"), self.loyalty_group) + ) + colon = ( + None + if static + else self.text_layer_colon.duplicate() + if self.text_layer_colon + else None + ) # Update ability cost if needed if not static: - psd.getLayer(LAYERS.COST, icon).textItem.contents = ability.get('cost', '0') - icon = icon.duplicate(*[self.icons[-1], ElementPlacement.PlaceBefore]) if ( - self.icons and self.icons[-1] - ) else icon.duplicate() + if layer := psd.getLayer(LAYERS.COST, icon): + layer.textItem.contents = ability.get("cost", "0") + if icon: + icon = ( + icon.duplicate(self.icons[-1], ElementPlacement.PlaceBefore) + if (self.icons and self.icons[-1]) + else icon.duplicate() + ) # Add ability, icons, and colons self.icons.append(icon) self.colons.append(colon) - self.ability_layers.append( - self.text_layer_static.duplicate() if static - else self.text_layer_ability.duplicate()) + if static: + if self.text_layer_static: + self.ability_layers.append(self.text_layer_static.duplicate()) + elif self.text_layer_ability: + self.ability_layers.append(self.text_layer_ability.duplicate()) self.text.append( text_classes.FormattedTextField( - layer=self.ability_layers[-1], - contents=ability.get('text', '') - )) + layer=self.ability_layers[-1], contents=ability.get("text", "") + ) + ) def fill_between_dividers(self, group: list[ArtLayer]) -> None: """Fill area between two ragged lines, or a top line and the bottom of the document. @@ -357,7 +417,9 @@ def fill_between_dividers(self, group: list[ArtLayer]) -> None: group: List containing 1 or 2 ragged lines to fill between. """ # If no second line is provided use the bottom of the document - bottom_bound: int = (group[1].bounds[1] if len(group) == 2 else self.docref.height) + 1 + bottom_bound = ( + group[1].bounds[1] if len(group) == 2 else self.docref.height + ) + 1 top_bound = group[0].bounds # Create a new layer to fill the selection @@ -365,12 +427,14 @@ def fill_between_dividers(self, group: list[ArtLayer]) -> None: self.active_layer.move(group[0], ElementPlacement.PlaceAfter) # Select between the two points and fill - self.doc_selection.select([ - [top_bound[0] - 200, top_bound[3] - 1], - [top_bound[2] + 200, top_bound[3] - 1], - [top_bound[2] + 200, bottom_bound], - [top_bound[0] - 200, bottom_bound] - ]) + self.doc_selection.select( + ( + (top_bound[0] - 200, top_bound[3] - 1), + (top_bound[2] + 200, top_bound[3] - 1), + (top_bound[2] + 200, bottom_bound), + (top_bound[0] - 200, bottom_bound), + ) + ) self.doc_selection.fill(self.fill_color, ColorBlendMode.NormalBlendColor, 100) self.doc_selection.deselect() @@ -393,18 +457,18 @@ def position_divider(layers: list[ArtLayer], line: ArtLayer) -> None: """ -class PlaneswalkerTemplate (PlaneswalkerMod, StarterTemplate): +class PlaneswalkerTemplate(PlaneswalkerMod, StarterTemplate): """Core Planeswalker 'Normal' M15-style template.""" -class PlaneswalkerBorderlessTemplate (BorderlessMod, PlaneswalkerTemplate): +class PlaneswalkerBorderlessTemplate(BorderlessMod, PlaneswalkerTemplate): """A Borderless version of PlaneswalkerTemplate.""" """ * Details """ - @property + @cached_property def art_frame_vertical(self) -> str: """No separation for 'Colorless' cards.""" return LAYERS.FULL_ART_FRAME @@ -415,7 +479,7 @@ def art_frame_vertical(self) -> str: """ -class PlaneswalkerMDFCTemplate (MDFCMod, PlaneswalkerTemplate): +class PlaneswalkerMDFCTemplate(MDFCMod, PlaneswalkerTemplate): """Adds MDFC functionality to the existing PlaneswalkerTemplate.""" """ @@ -423,22 +487,22 @@ class PlaneswalkerMDFCTemplate (MDFCMod, PlaneswalkerTemplate): """ @cached_property - def dfc_group(self) -> LayerSet: + def dfc_group(self) -> LayerSet | None: """LayerSet: DFC group at top level.""" face = LAYERS.FRONT if self.is_front else LAYERS.BACK - return psd.getLayerSet(f'{LAYERS.MDFC} {face}') + return psd.getLayerSet(f"{LAYERS.MDFC} {face}") """ * Text Layers """ @cached_property - def text_layer_name(self) -> ArtLayer: + def text_layer_name(self) -> ArtLayer | None: """ArtLayer: Name is always shifted.""" return psd.getLayer(LAYERS.NAME, self.text_group) -class PlaneswalkerMDFCBorderlessTemplate (MDFCMod, PlaneswalkerBorderlessTemplate): +class PlaneswalkerMDFCBorderlessTemplate(MDFCMod, PlaneswalkerBorderlessTemplate): """Adds MDFC functionality to the existing PlaneswalkerExtendedTemplate.""" """ @@ -446,17 +510,17 @@ class PlaneswalkerMDFCBorderlessTemplate (MDFCMod, PlaneswalkerBorderlessTemplat """ @cached_property - def dfc_group(self) -> LayerSet: + def dfc_group(self) -> LayerSet | None: """LayerSet: DFC group at top level.""" face = LAYERS.FRONT if self.is_front else LAYERS.BACK - return psd.getLayerSet(f'{LAYERS.MDFC} {face}') + return psd.getLayerSet(f"{LAYERS.MDFC} {face}") """ * Text Layers """ @cached_property - def text_layer_name(self) -> ArtLayer: + def text_layer_name(self) -> ArtLayer | None: """ArtLayer: Name is always shifted.""" return psd.getLayer(LAYERS.NAME, self.text_group) @@ -466,7 +530,7 @@ def text_layer_name(self) -> ArtLayer: """ -class PlaneswalkerTFTemplate (TransformMod, PlaneswalkerTemplate): +class PlaneswalkerTFTemplate(TransformMod, PlaneswalkerTemplate): """Adds Transform functionality to the existing PlaneswalkerTemplate.""" """ @@ -474,23 +538,23 @@ class PlaneswalkerTFTemplate (TransformMod, PlaneswalkerTemplate): """ @cached_property - def dfc_group(self) -> LayerSet: + def dfc_group(self) -> LayerSet | None: """LayerSet: DFC group at top level.""" return psd.getLayerSet( - LAYERS.FRONT if self.is_front else LAYERS.BACK, - LAYERS.TRANSFORM) + LAYERS.FRONT if self.is_front else LAYERS.BACK, LAYERS.TRANSFORM + ) """ * Text Layers """ @cached_property - def text_layer_name(self) -> ArtLayer: + def text_layer_name(self) -> ArtLayer | None: """ArtLayer: Name is always shifted.""" return psd.getLayer(LAYERS.NAME, self.text_group) @cached_property - def text_layer_type(self) -> Optional[ArtLayer]: + def text_layer_type(self) -> ArtLayer | None: """ArtLayer: Typeline is always shifted.""" return psd.getLayer(LAYERS.TYPE_LINE, self.text_group) @@ -503,7 +567,7 @@ def text_layers_transform(self): pass -class PlaneswalkerTFBorderlessTemplate (TransformMod, PlaneswalkerBorderlessTemplate): +class PlaneswalkerTFBorderlessTemplate(TransformMod, PlaneswalkerBorderlessTemplate): """Adds Transform functionality to the existing PlaneswalkerBorderlessTemplate.""" """ @@ -511,23 +575,23 @@ class PlaneswalkerTFBorderlessTemplate (TransformMod, PlaneswalkerBorderlessTemp """ @cached_property - def dfc_group(self) -> LayerSet: + def dfc_group(self) -> LayerSet | None: """LayerSet: DFC group at top level.""" return psd.getLayerSet( - LAYERS.FRONT if self.is_front else LAYERS.BACK, - LAYERS.TRANSFORM) + LAYERS.FRONT if self.is_front else LAYERS.BACK, LAYERS.TRANSFORM + ) """ * Text Layers """ @cached_property - def text_layer_name(self) -> ArtLayer: + def text_layer_name(self) -> ArtLayer | None: """ArtLayer: Name is always shifted.""" return psd.getLayer(LAYERS.NAME, self.text_group) @cached_property - def text_layer_type(self) -> Optional[ArtLayer]: + def text_layer_type(self) -> ArtLayer | None: """ArtLayer: Typeline is always shifted.""" return psd.getLayer(LAYERS.TYPE_LINE, self.text_group) diff --git a/src/templates/prototype.py b/src/templates/prototype.py index d128b42c..0bff1755 100644 --- a/src/templates/prototype.py +++ b/src/templates/prototype.py @@ -1,9 +1,10 @@ """ * Templates: Prototype """ + # Standard Library from functools import cached_property -from typing import Callable +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -37,15 +38,23 @@ class PrototypeMod(NormalTemplate): """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Prototype text layers.""" - funcs = [self.text_layers_prototype] if isinstance(self.layout, PrototypeLayout) else [] + funcs = ( + [self.text_layers_prototype] + if isinstance(self.layout, PrototypeLayout) + else [] + ) return [*super().text_layer_methods, *funcs] @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Enable Prototype frame layers.""" - funcs = [self.frame_layers_prototype] if isinstance(self.layout, PrototypeLayout) else [] + funcs = ( + [self.frame_layers_prototype] + if isinstance(self.layout, PrototypeLayout) + else [] + ) return [*super().frame_layer_methods, *funcs] """ @@ -53,7 +62,7 @@ def frame_layer_methods(self) -> list[Callable]: """ @cached_property - def prototype_reference(self) -> ReferenceLayer: + def prototype_reference(self) -> ReferenceLayer | None: """ReferenceLayer: Reference used to size and position the prototype text.""" return self.proto_textbox_layer @@ -62,17 +71,17 @@ def prototype_reference(self) -> ReferenceLayer: """ @cached_property - def proto_manabox_group(self) -> LayerSet: + def proto_manabox_group(self) -> LayerSet | None: """LayerSet: Layer group containing the colors and shape for the Prototype mana box.""" return psd.getLayerSet(LAYERS.PROTO_MANABOX, self.docref) @cached_property - def proto_textbox_group(self) -> LayerSet: + def proto_textbox_group(self) -> LayerSet | None: """LayerSet: Layer group containing textures for the Prototype textbox.""" return psd.getLayerSet(LAYERS.PROTO_TEXTBOX, self.docref) @cached_property - def proto_pt_group(self) -> LayerSet: + def proto_pt_group(self) -> LayerSet | None: """LayerSet: Layer group containing textures for the Prototype PT box.""" return psd.getLayerSet(LAYERS.PROTO_PTBOX, self.docref) @@ -81,52 +90,52 @@ def proto_pt_group(self) -> LayerSet: """ @cached_property - def proto_manabox_shape(self) -> ArtLayer: + def proto_manabox_shape(self) -> ArtLayer | None: """ArtLayer: Vector shape containing the Prototype mana text.""" - size = '2' if self.layout.proto_mana_cost.count('{') <= 2 else '3' - return psd.getLayer(size, [self.proto_manabox_group, LAYERS.SHAPE]) + if isinstance(self.layout, PrototypeLayout): + size = "2" if self.layout.proto_mana_cost.count("{") <= 2 else "3" + return psd.getLayer(size, [self.proto_manabox_group, LAYERS.SHAPE]) """ * Prototype Layers """ @cached_property - def proto_textbox_layer(self) -> ReferenceLayer: + def proto_textbox_layer(self) -> ReferenceLayer | None: """ReferenceLayer: Colored and outlined box containing the Prototype ability text.""" - return psd.get_reference_layer( - self.layout.proto_color, - self.proto_textbox_group) + if isinstance(self.layout, PrototypeLayout): + return psd.get_reference_layer( + self.layout.proto_color, self.proto_textbox_group + ) @cached_property - def proto_manabox_layer(self) -> ArtLayer: + def proto_manabox_layer(self) -> ArtLayer | None: """ArtLayer: Solid color adjustment layer used to color the Prototype manabox.""" - return psd.getLayer( - self.layout.proto_color, - self.proto_manabox_group) + if isinstance(self.layout, PrototypeLayout): + return psd.getLayer(self.layout.proto_color, self.proto_manabox_group) @cached_property - def proto_pt_layer(self) -> ArtLayer: + def proto_pt_layer(self) -> ArtLayer | None: """ArtLayer: Box for the P/T of the Prototype version of this card.""" - return psd.getLayer( - self.layout.proto_color, - self.proto_pt_group) + if isinstance(self.layout, PrototypeLayout): + return psd.getLayer(self.layout.proto_color, self.proto_pt_group) """ * Prototype Text Layers """ @cached_property - def text_layer_proto(self) -> ArtLayer: + def text_layer_proto(self) -> ArtLayer | None: """ArtLayer: Text layer containing the Prototype rules text.""" return psd.getLayer(LAYERS.PROTO_RULES, self.text_group) @cached_property - def text_layer_proto_mana(self) -> ArtLayer: + def text_layer_proto_mana(self) -> ArtLayer | None: """ArtLayer: Text layer containing the Prototype mana cost.""" return psd.getLayer(LAYERS.PROTO_MANA_COST, self.text_group) @cached_property - def text_layer_proto_pt(self) -> ArtLayer: + def text_layer_proto_pt(self) -> ArtLayer | None: """ArtLayer: Text layer containing the Prototype power/toughness.""" return psd.getLayer(LAYERS.PROTO_PT, self.text_group) @@ -143,8 +152,9 @@ def frame_layers_prototype(self): # Prototype Mana Box if self.proto_manabox_layer: - self.proto_manabox_shape.visible = True self.proto_manabox_layer.visible = True + if self.proto_manabox_shape: + self.proto_manabox_shape.visible = True # Prototype PT if self.proto_pt_layer: @@ -156,24 +166,34 @@ def frame_layers_prototype(self): def text_layers_prototype(self): """Add and modify text layers required by Prototype cards.""" - - # Add prototype PT and Mana Cost - self.text.extend([ - text_classes.FormattedTextField( - layer=self.text_layer_proto_mana, - contents=self.layout.proto_mana_cost), - text_classes.TextField( - layer=self.text_layer_proto_pt, - contents=self.layout.proto_pt)]) - - # Remove reminder text if necessary - if CFG.remove_reminder: - self.text_layer_proto.textItem.size = psd.get_text_scale_factor(self.text_layer_proto) * 9 - self.text.append( - text_classes.FormattedTextArea( - layer=self.text_layer_proto, - contents='Prototype', - reference=self.prototype_reference)) + if isinstance(self.layout, PrototypeLayout): + # Add prototype PT and Mana Cost + if self.text_layer_proto_mana: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_proto_mana, + contents=self.layout.proto_mana_cost, + ) + ) + if self.text_layer_proto_pt: + self.text.append( + text_classes.TextField( + layer=self.text_layer_proto_pt, contents=self.layout.proto_pt + ) + ) + + # Remove reminder text if necessary + if CFG.remove_reminder and self.text_layer_proto: + self.text_layer_proto.textItem.size = ( + psd.get_text_scale_factor(self.text_layer_proto) * 9 + ) + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_proto, + contents="Prototype", + reference=self.prototype_reference, + ) + ) """ diff --git a/src/templates/saga.py b/src/templates/saga.py index 41c7a2c1..8c65b208 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -1,9 +1,10 @@ """ * Templates: Saga """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Union, Callable +from collections.abc import Sequence, Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer @@ -12,11 +13,18 @@ # Local Imports from src.enums.layers import LAYERS import src.helpers as psd -from src.layouts import SagaLayout -from src.schema.colors import pinlines_color_map, saga_banner_color_map, saga_stripe_color_map +from src.schema.colors import GradientConfig +from src.helpers.layers import get_reference_layer +from src.layouts import NormalLayout, SagaLayout +from src.schema.colors import ( + ColorObject, + pinlines_color_map, + saga_banner_color_map, + saga_stripe_color_map, +) from src.templates import VectorNyxMod from src.templates._core import NormalTemplate -from src.templates._vector import VectorTemplate +from src.templates._vector import MaskAction, VectorTemplate from src.templates.transform import VectorTransformMod import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer @@ -26,7 +34,7 @@ """ -class SagaMod (NormalTemplate): +class SagaMod(NormalTemplate): """ * A template modifier for Saga cards introduced in Dominaria. * Utilizes some of the same automated positioning techniques as Planeswalker templates. @@ -37,10 +45,10 @@ class SagaMod (NormalTemplate): * Also has some layer support for double faced sagas. """ - def __init__(self, layout: SagaLayout, **kwargs): - self._abilities: list[ArtLayer] = [] - self._icons: list[list[ArtLayer]] = [] - super().__init__(layout, **kwargs) + def __init__(self, layout: NormalLayout): + self._saga_abilities: list[ArtLayer] = [] + self._saga_icons: list[list[ArtLayer | LayerSet]] = [] + super().__init__(layout) """ * Layout Checks @@ -56,19 +64,19 @@ def is_layout_saga(self) -> bool: """ @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Saga text layers.""" funcs = [self.text_layers_saga] if self.is_layout_saga else [] return [*super().text_layer_methods, *funcs] @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add Saga text layers.""" funcs = [self.frame_layers_saga] if self.is_layout_saga else [] return [*super().frame_layer_methods, *funcs] @cached_property - def post_text_methods(self) -> list[Callable]: + def post_text_methods(self) -> list[Callable[[], None]]: """Position Saga abilities, dividers, and icons.""" funcs = [self.layer_positioning_saga] if self.is_layout_saga else [] return [*super().post_text_methods, *funcs] @@ -86,35 +94,40 @@ def saga_group(self): """ @property - def ability_layers(self) -> list[ArtLayer]: - return self._abilities + def saga_ability_layers(self) -> list[ArtLayer]: + return self._saga_abilities - @ability_layers.setter - def ability_layers(self, value): - self._abilities = value + @saga_ability_layers.setter + def saga_ability_layers(self, value: list[ArtLayer]): + self._saga_abilities = value @property - def icon_layers(self) -> list[list[ArtLayer]]: - return self._icons + def saga_icon_layers(self) -> list[list[ArtLayer | LayerSet]]: + return self._saga_icons - @icon_layers.setter - def icon_layers(self, value): - self._icons = value + @saga_icon_layers.setter + def saga_icon_layers(self, value: list[list[ArtLayer | LayerSet]]): + self._saga_icons = value @cached_property - def ability_divider_layer(self) -> ArtLayer: + def ability_divider_layer(self) -> ArtLayer | None: return psd.getLayer(LAYERS.DIVIDER, self.saga_group) + def get_saga_stage_icon(self, stage: str) -> ArtLayer | LayerSet: + if layer := psd.getLayer(stage, self.saga_group): + return layer.duplicate() + raise ValueError(f"Couldn't get icon for Saga stage '{stage}'.") + """ * Text Layers """ @cached_property - def text_layer_ability(self) -> ArtLayer: + def text_layer_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.TEXT, self.saga_group) @cached_property - def text_layer_reminder(self) -> ArtLayer: + def text_layer_reminder(self) -> ArtLayer | None: return psd.getLayer("Reminder Text", self.saga_group) """ @@ -122,11 +135,11 @@ def text_layer_reminder(self) -> ArtLayer: """ @cached_property - def art_reference(self) -> ArtLayer: - return psd.getLayer(LAYERS.ART_FRAME) + def art_reference(self) -> ReferenceLayer | None: + return get_reference_layer(LAYERS.ART_FRAME) @cached_property - def reminder_reference(self) -> ReferenceLayer: + def reminder_reference(self) -> ReferenceLayer | None: return psd.get_reference_layer("Description Reference", self.saga_group) """ @@ -145,7 +158,8 @@ def frame_layers_saga(self): """Enable frame layers required by Saga cards.""" # Saga stripe - psd.getLayer(self.pinlines, LAYERS.PINLINES_AND_SAGA_STRIPE).visible = True + if layer := psd.getLayer(self.pinlines, LAYERS.PINLINES_AND_SAGA_STRIPE): + layer.visible = True """ * Saga Text Layer Methods @@ -153,26 +167,37 @@ def frame_layers_saga(self): def text_layers_saga(self): """Add and modify text layers required by Saga cards.""" - - # Add description text with reminder - self.text.append( - text_classes.FormattedTextArea( - layer=self.text_layer_reminder, - contents=self.layout.saga_description, - reference=self.reminder_reference)) - - # Iterate through each saga stage and add line to text layers - for i, line in enumerate(self.layout.saga_lines): - - # Add icon layers for this ability - self.icon_layers.append([psd.getLayer(n, self.saga_group).duplicate() for n in line['icons']]) - - # Add ability text for this ability - layer = self.text_layer_ability if i == 0 else self.text_layer_ability.duplicate() - self.ability_layers.append(layer) - self.text.append( - text_classes.FormattedTextField( - layer=layer, contents=line['text'])) + if isinstance(self.layout, SagaLayout): + # Add description text with reminder + if self.text_layer_reminder: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_reminder, + contents=self.layout.saga_description, + reference=self.reminder_reference, + ) + ) + + if self.text_layer_ability: + # Iterate through each saga stage and add line to text layers + for i, line in enumerate(self.layout.saga_lines): + # Add icon layers for this ability + self.saga_icon_layers.append( + [self.get_saga_stage_icon(n) for n in line["icons"]] + ) + + # Add ability text for this ability + layer = ( + self.text_layer_ability + if i == 0 + else self.text_layer_ability.duplicate() + ) + self.saga_ability_layers.append(layer) + self.text.append( + text_classes.FormattedTextField( + layer=layer, contents=line["text"] + ) + ) """ * Saga Positioning Methods @@ -180,56 +205,59 @@ def text_layers_saga(self): def layer_positioning_saga(self) -> None: """Position Saga ability, icon, and divider layers.""" - - # Core vars - spacing = self.app.scale_by_dpi(80) - spaces = len(self.ability_layers) - 1 - spacing_total = (spaces * 1.5) + 2 - ref_height = self.textbox_reference.dims['height'] - total_height = ref_height - (((spacing * 1.5) * spaces) + (spacing * 2)) - - # Resize text items till they fit in the available space - psd.scale_text_layers_to_height( - text_layers=self.ability_layers, - ref_height=total_height) - - # Get the exact gap between each layer left over - layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.ability_layers]) - gap = (ref_height - layer_heights) * (1 / spacing_total) - inside_gap = (ref_height - layer_heights) * (1.5 / spacing_total) - - # Space Saga lines evenly apart - psd.spread_layers_over_reference( - layers=self.ability_layers, - ref=self.textbox_reference, - gap=gap, - inside_gap=inside_gap) - - # Align icons to respective text layers - for i, ref_layer in enumerate(self.ability_layers): - - # Skip if no icons present or icons are invalid - if not (icons := self.icon_layers[i]): - continue - if not all(icons): - continue - - # Space multiple icons apart - if len(icons) > 1: - psd.space_layers_apart( - layers=icons, - gap=spacing / 3) - - # Combine icons and align them - layer = icons[0] if len(icons) == 1 else psd.merge_layers(icons) - psd.align_vertical(layer, ref_layer) - - # Position divider lines - dividers = [self.ability_divider_layer.duplicate() for _ in range(len(self.ability_layers) - 1)] - psd.position_dividers( - dividers=dividers, - layers=self.ability_layers, - docref=self.docref) + if self.textbox_reference: + # Core vars + spacing = self.app.scale_by_dpi(80) + spaces = len(self.saga_ability_layers) - 1 + spacing_total = (spaces * 1.5) + 2 + ref_height = self.textbox_reference.dims["height"] + total_height = ref_height - (((spacing * 1.5) * spaces) + (spacing * 2)) + + # Resize text items till they fit in the available space + psd.scale_text_layers_to_height( + text_layers=self.saga_ability_layers, ref_height=total_height + ) + + # Get the exact gap between each layer left over + layer_heights = sum( + [psd.get_layer_height(lyr) for lyr in self.saga_ability_layers] + ) + gap = (ref_height - layer_heights) * (1 / spacing_total) + inside_gap = (ref_height - layer_heights) * (1.5 / spacing_total) + + # Space Saga lines evenly apart + psd.spread_layers_over_reference( + layers=self.saga_ability_layers, + ref=self.textbox_reference, + gap=gap, + inside_gap=inside_gap, + ) + + # Align icons to respective text layers + for i, ref_layer in enumerate(self.saga_ability_layers): + # Skip if no icons present or icons are invalid + if not (icons := self.saga_icon_layers[i]): + continue + if not all(icons): + continue + + # Space multiple icons apart + if len(icons) > 1: + psd.space_layers_apart(layers=icons, gap=spacing / 3) + + # Combine icons and align them + layer = icons[0] if len(icons) == 1 else psd.merge_layers(icons) + psd.align_vertical(layer, ref_layer) + + # Position divider lines + if self.ability_divider_layer: + dividers = [ + self.ability_divider_layer.duplicate() + for _ in range(len(self.saga_ability_layers) - 1) + ] + psd.position_dividers( + dividers=dividers, layers=self.saga_ability_layers, docref=self.docref + ) class VectorSagaMod(SagaMod, VectorTemplate): @@ -244,18 +272,22 @@ class VectorSagaMod(SagaMod, VectorTemplate): """ @cached_property - def saga_banner_colors(self) -> list[list[int]]: - """Must be returned as list of RGB/CMYK integer lists.""" + def saga_banner_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: + """Must be returned as list of RGB/CMYK color notations.""" if len(self.pinlines) == 2: - return [self.saga_banner_color_map.get(c, [0, 0, 0]) for c in self.pinlines] - return [self.saga_banner_color_map.get(self.pinlines, [0, 0, 0])] + return [self.saga_banner_color_map.get(c, (0, 0, 0)) for c in self.pinlines] + return [self.saga_banner_color_map.get(self.pinlines, (0, 0, 0))] @cached_property - def saga_stripe_colors(self) -> list[int]: - """Must be returned as an RGB/CMYK integer list.""" + def saga_stripe_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: + """Must be returned as an RGB/CMYK color notation.""" if len(self.pinlines) == 2: - return self.saga_stripe_color_map.get('Dual', [0, 0, 0]) - return self.saga_stripe_color_map.get(self.pinlines, [0, 0, 0]) + return self.saga_stripe_color_map.get("Dual", (0, 0, 0)) + return self.saga_stripe_color_map.get(self.pinlines, (0, 0, 0)) """ * Blending Masks @@ -263,18 +295,20 @@ def saga_stripe_colors(self) -> list[int]: @cached_property def saga_banner_masks(self) -> list[ArtLayer]: - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BANNER])] + if layer := psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BANNER]): + return [layer] + return [] """ * Groups """ @cached_property - def saga_banner_group(self) -> LayerSet: + def saga_banner_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.BANNER, self.saga_group) @cached_property - def saga_stripe_group(self) -> LayerSet: + def saga_stripe_group(self) -> LayerSet | None: return psd.getLayerSet(LAYERS.STRIPE, self.saga_group) """ @@ -282,25 +316,34 @@ def saga_stripe_group(self) -> LayerSet: """ @cached_property - def saga_stripe_shape(self) -> ArtLayer: + def saga_stripe_shape(self) -> ArtLayer | None: """The stripe shape in the middle of the Saga banner.""" return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.saga_stripe_group, LAYERS.SHAPE]) + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.saga_stripe_group, LAYERS.SHAPE], + ) @cached_property - def saga_banner_shape(self) -> ArtLayer: + def saga_banner_shape(self) -> ArtLayer | None: """The overall Saga banner shape.""" return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.saga_banner_group, LAYERS.SHAPE]) + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.saga_banner_group, LAYERS.SHAPE], + ) @cached_property - def saga_trim_shape(self) -> ArtLayer: + def saga_trim_shape(self) -> ArtLayer | None: """The gold trim on the Saga Banner.""" return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - self.saga_group) + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + self.saga_group, + ) """ * Saga Frame Layer Methods @@ -310,16 +353,20 @@ def frame_layers_saga(self): """Enable layers required by Saga cards.""" # Enable Saga group - self.saga_group.visible = True + if self.saga_group: + self.saga_group.visible = True # Add colors - self.generate_layer( - group=self.saga_banner_group, - colors=self.saga_banner_colors, - masks=self.saga_banner_masks) - self.generate_layer( - group=self.saga_stripe_group, - colors=self.saga_stripe_colors) + if self.saga_banner_group: + self.generate_layer( + group=self.saga_banner_group, + colors=self.saga_banner_colors, + masks=self.saga_banner_masks, + ) + if self.saga_stripe_group: + self.generate_layer( + group=self.saga_stripe_group, colors=self.saga_stripe_colors + ) """ @@ -327,7 +374,9 @@ def frame_layers_saga(self): """ -class SagaVectorTemplate(VectorNyxMod, VectorSagaMod, VectorTransformMod, VectorTemplate): +class SagaVectorTemplate( + VectorNyxMod, VectorSagaMod, VectorTransformMod, VectorTemplate +): """Saga template using vector shape layers and automatic pinlines / multicolor generation.""" """ @@ -344,9 +393,15 @@ def is_name_shifted(self) -> bool: """ @cached_property - def twins_colors(self) -> list[str]: + def twins_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """list[str]: Use Back face versions for back side Transform.""" - return [f'{self.twins} {LAYERS.BACK}'] if self.is_transform and not self.is_front else [self.twins] + return ( + [f"{self.twins} {LAYERS.BACK}"] + if self.is_transform and not self.is_front + else [self.twins] + ) @cached_property def textbox_colors(self) -> list[str]: @@ -364,23 +419,25 @@ def textbox_colors(self) -> list[str]: return [self.pinlines] @cached_property - def crown_colors(self) -> Union[list[int], list[dict]]: + def crown_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Return RGB/CMYK integer list or Gradient dict notation for color adjustment layers.""" return psd.get_pinline_gradient( - colors=self.pinlines, - color_map=self.crown_color_map) + colors=self.pinlines, color_map=self.crown_color_map + ) """ * Groups """ @cached_property - def crown_group(self) -> LayerSet: + def crown_group(self) -> LayerSet | None: """Legendary crown group.""" return psd.getLayerSet(LAYERS.SHAPE, LAYERS.LEGENDARY_CROWN) @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """Must enable textbox group.""" if group := psd.getLayerSet(LAYERS.TEXTBOX): group.visible = True @@ -391,7 +448,7 @@ def textbox_group(self) -> LayerSet: """ @cached_property - def border_layer(self) -> Optional[ArtLayer]: + def border_layer(self) -> ArtLayer | None: """Check for Legendary and/or front face Transform.""" name = LAYERS.LEGENDARY if self.is_legendary else LAYERS.NORMAL if self.is_transform and self.is_front: @@ -403,13 +460,15 @@ def border_layer(self) -> Optional[ArtLayer]: """ @cached_property - def art_reference(self) -> ArtLayer: - return psd.getLayer(LAYERS.ART_FRAME + " Right") + def art_reference(self) -> ReferenceLayer | None: + return get_reference_layer(LAYERS.ART_FRAME + " Right") @cached_property - def textbox_reference(self) -> Optional[ReferenceLayer]: + def textbox_reference(self) -> ReferenceLayer | None: if self.is_front and self.is_flipside_creature: - return psd.get_reference_layer(f"{LAYERS.TEXTBOX_REFERENCE} {LAYERS.TRANSFORM_FRONT}", self.saga_group) + return psd.get_reference_layer( + f"{LAYERS.TEXTBOX_REFERENCE} {LAYERS.TRANSFORM_FRONT}", self.saga_group + ) return psd.get_reference_layer(LAYERS.TEXTBOX_REFERENCE, self.saga_group) """ @@ -418,55 +477,71 @@ def textbox_reference(self) -> Optional[ReferenceLayer]: @cached_property def textbox_masks(self) -> list[ArtLayer]: - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.TEXTBOX])] + if layer := psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.TEXTBOX]): + return [layer] + return [] @cached_property def background_masks(self) -> list[ArtLayer]: - return [psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BACKGROUND])] + if layer := psd.getLayer(LAYERS.HALF, [self.mask_group, LAYERS.BACKGROUND]): + return [layer] + return [] """ * Shape Layers """ @cached_property - def pinlines_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def pinlines_shapes(self) -> list[ArtLayer | LayerSet | None]: """Add Legendary shape to pinlines shapes.""" - shapes = [psd.getLayerSet(LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE])] if self.is_legendary else [] + shapes = ( + [psd.getLayerSet(LAYERS.LEGENDARY, [self.pinlines_group, LAYERS.SHAPE])] + if self.is_legendary + else [] + ) return [ # Normal or Transform pinline psd.getLayerSet( (LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK) - if self.is_transform else LAYERS.NORMAL, - [self.pinlines_group, LAYERS.SHAPE] - ), *shapes + if self.is_transform + else LAYERS.NORMAL, + [self.pinlines_group, LAYERS.SHAPE], + ), + *shapes, ] @cached_property - def textbox_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def textbox_shapes(self) -> list[ArtLayer | LayerSet | None]: """Optional Textbox cutout shapes.""" if self.is_transform and self.is_front: - return [psd.getLayer(LAYERS.TRANSFORM_FRONT, [self.textbox_group, LAYERS.SHAPE])] + return [ + psd.getLayer(LAYERS.TRANSFORM_FRONT, [self.textbox_group, LAYERS.SHAPE]) + ] return [] @cached_property - def twins_shape(self) -> ArtLayer: + def twins_shape(self) -> ArtLayer | None: """Allow for both front and back Transform twins.""" if self.is_transform: return psd.getLayer( LAYERS.TRANSFORM_FRONT if self.is_front else LAYERS.TRANSFORM_BACK, - [self.twins_group, LAYERS.SHAPE]) + [self.twins_group, LAYERS.SHAPE], + ) # Normal twins return psd.getLayer(LAYERS.NORMAL, [self.twins_group, LAYERS.SHAPE]) @cached_property - def outline_shape(self) -> ArtLayer: + def outline_shape(self) -> ArtLayer | None: """Outline for textbox and art area.""" return psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - LAYERS.OUTLINE) + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + LAYERS.OUTLINE, + ) @cached_property - def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: """Add support for outline shape, multiple pinlines shapes, and saga shapes.""" return [ *self.pinlines_shapes, @@ -476,7 +551,7 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: self.saga_trim_shape, self.outline_shape, self.border_shape, - self.twins_shape + self.twins_shape, ] """ @@ -484,17 +559,26 @@ def enabled_shapes(self) -> list[Union[ArtLayer, LayerSet, None]]: """ @cached_property - def pinlines_mask(self) -> list[ArtLayer]: + def pinlines_mask( + self, + ) -> MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None: """Mask hiding pinlines effects inside textbox and art frame.""" - return [ - psd.getLayer( - LAYERS.TRANSFORM_FRONT if self.is_transform and self.is_front else LAYERS.NORMAL, - [self.mask_group, LAYERS.PINLINES]), - self.pinlines_group - ] - - @cached_property - def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: + if self.pinlines_group and ( + layer := psd.getLayer( + LAYERS.TRANSFORM_FRONT + if self.is_transform and self.is_front + else LAYERS.NORMAL, + [self.mask_group, LAYERS.PINLINES], + ) + ): + return (layer, self.pinlines_group) + + @cached_property + def enabled_masks( + self, + ) -> list[ + MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None + ]: """Support a pinlines mask.""" return [self.pinlines_mask] @@ -503,10 +587,11 @@ def enabled_masks(self) -> list[Union[dict, list, ArtLayer, LayerSet, None]]: """ def enable_transform_layers(self): - # Must enable Transform Icon group - if self.transform_icon_layer: - self.transform_icon_layer.parent.visible = True + if self.transform_icon_layer and isinstance( + (parent := self.transform_icon_layer.parent), LayerSet + ): + parent.visible = True self.transform_icon_layer.visible = True """ @@ -514,29 +599,33 @@ def enable_transform_layers(self): """ def text_layers_transform_back(self): - # Change back face name and typeline to white - self.text_layer_name.textItem.color = psd.rgb_white() - self.text_layer_type.textItem.color = psd.rgb_white() + if self.text_layer_name: + self.text_layer_name.textItem.color = psd.rgb_white() + if self.text_layer_type: + self.text_layer_type.textItem.color = psd.rgb_white() class UniversesBeyondSagaTemplate(SagaVectorTemplate): """Saga Vector template with Universes Beyond frame treatment.""" - template_suffix = 'Universes Beyond' + + template_suffix = "Universes Beyond" # Color Maps - pinlines_color_map = { - **pinlines_color_map.copy(), - 'W': [246, 247, 241], - 'U': [0, 131, 193], - 'B': [44, 40, 33], - 'R': [237, 66, 31], - 'G': [5, 129, 64], - 'Gold': [239, 209, 107], - 'Land': [165, 150, 132], - 'Artifact': [227, 228, 230], - 'Colorless': [227, 228, 230] - } + @cached_property + def pinlines_color_map(self) -> dict[str, ColorObject]: + return { + **pinlines_color_map, + "W": (246, 247, 241), + "U": (0, 131, 193), + "B": (44, 40, 33), + "R": (237, 66, 31), + "G": (5, 129, 64), + "Gold": (239, 209, 107), + "Land": (165, 150, 132), + "Artifact": (227, 228, 230), + "Colorless": (227, 228, 230), + } """ * Colors @@ -550,21 +639,23 @@ def textbox_colors(self) -> list[str]: return [self.pinlines] @cached_property - def twins_colors(self) -> Optional[str]: + def twins_colors( + self, + ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: """Look for 'Beyond' variant texture.""" - return f'{self.twins} Beyond' + return f"{self.twins} Beyond" """ * Groups """ @cached_property - def background_group(self) -> LayerSet: + def background_group(self) -> LayerSet | None: """Look for 'Beyond' variant group.""" - return psd.getLayerSet(f'{LAYERS.BACKGROUND} Beyond') + return psd.getLayerSet(f"{LAYERS.BACKGROUND} Beyond") @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """Look for 'Beyond' variant group. Must be enabled.""" if group := psd.getLayerSet(f"{LAYERS.TEXTBOX} Beyond"): group.visible = True @@ -579,5 +670,7 @@ def enable_transform_layers(self): # Switch to darker colors for back side if not self.is_front: - psd.getLayer(LAYERS.BACK, self.textbox_group).visible = True - psd.getLayer(LAYERS.BACK, self.twins_group).visible = True + if layer := psd.getLayer(LAYERS.BACK, self.textbox_group): + layer.visible = True + if layer := psd.getLayer(LAYERS.BACK, self.twins_group): + layer.visible = True diff --git a/src/templates/split.py b/src/templates/split.py index 9b4dfa64..c609db94 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -5,10 +5,10 @@ from _ctypes import COMError from functools import cached_property from pathlib import Path -from typing import Callable +from collections.abc import Callable, Sequence from omnitils.strings import normalize_str -from photoshop.api import BlendMode, ElementPlacement, SolidColor +from photoshop.api import BlendMode, ElementPlacement from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -16,7 +16,7 @@ from src import CFG, CON from src.enums.layers import LAYERS from src.helpers import LayerEffects -from src.helpers.colors import GradientConfig +from src.schema.colors import GradientConfig from src.helpers.effects import copy_layer_fx from src.helpers.layers import get_reference_layer from src.layouts import SplitLayout @@ -64,12 +64,7 @@ def pinline_gradient_locations(self) -> list[dict[int, list[int | float]]]: @cached_property def color_limit(self) -> int: """One more than the max number of colors this card can split by.""" - setting = CFG.get_setting( - section="COLORS", key="Max.Colors", default="2", is_bool=False - ) - if isinstance(setting, str): - return int(setting) + 1 - raise ValueError(f"Received invalid value for color limit: {setting}") + return CFG.get_int_setting(section="COLORS", key="Max.Colors", default=2) + 1 # endregion Settings @@ -159,7 +154,7 @@ def fuse_pinline_colors( @cached_property def pinlines_colors_split( self, - ) -> list[(ColorObject | list[ColorObject] | list[GradientConfig])]: + ) -> list[(ColorObject | Sequence[ColorObject] | Sequence[GradientConfig])]: """Color definitions used for pinlines of each side.""" if isinstance(self.layout, SplitLayout): return [ @@ -376,7 +371,7 @@ def load_artworks( if len(art_files) == 1: # Manually select a second art self.console.update("Please select the second split art!") - file: list[str] = self.app.openDialog() + file: list[Path] = self.app.openDialog() if not file: self.console.update("No art selected, cancelling render.") self.console.cancel_thread(thr=self.event) @@ -406,10 +401,10 @@ def load_artworks( # region Watermarks @cached_property - def watermarks_colors(self) -> list[list[SolidColor | list[int]]]: + def watermarks_colors(self) -> list[list[ColorObject]]: """A list of 'SolidColor' objects for each face.""" if isinstance(self.layout, SplitLayout): - colors: list[list[SolidColor | list[int]]] = [] + colors: list[list[ColorObject]] = [] for i, pinline in enumerate(self.layout.pinline_identities): if pinline in self.watermark_color_map: # Named pinline colors @@ -628,14 +623,16 @@ def frame_layers_split(self) -> None: # Frame layers for i in range(len(self.sides)): if (group := self.twins_groups[i]) and (layer := self.twins_layers[i]): - # Copy twins and position - layer.visible = True - twins = layer.parent.duplicate(group, ElementPlacement.PlaceBefore) - layer.visible = False - twins.visible = True - - if ref := self.twins_references[i]: - psd.align_horizontal(twins, ref) + parent = layer.parent + if isinstance(parent, LayerSet): + # Copy twins and position + layer.visible = True + twins = parent.duplicate(group, ElementPlacement.PlaceBefore) + layer.visible = False + twins.visible = True + + if ref := self.twins_references[i]: + psd.align_horizontal(twins, ref) if layer := self.background_layers[i]: # Copy background and position diff --git a/src/templates/station.py b/src/templates/station.py index e65fac11..098a2cbb 100644 --- a/src/templates/station.py +++ b/src/templates/station.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Callable +from collections.abc import Callable from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -10,7 +10,7 @@ get_layer_dimensions, get_layer_height, ) -from src.helpers.layers import duplicate_group, getLayer, getLayerSet, select_layer +from src.helpers.layers import duplicate_group, getLayer, getLayerSet from src.helpers.position import spread_layers_over_reference from src.helpers.text import scale_text_layers_to_height from src.layouts import StationLayout @@ -65,9 +65,11 @@ def station_level_groups(self) -> list[LayerSet]: if self.station_level_base_group: if isinstance(self.layout, StationLayout): for i in range(len(self.layout.stations) - 1): - select_layer(self.station_level_base_group) groups.append( - duplicate_group(f"{self.station_level_base_group.name} {i}") + duplicate_group( + self.station_level_base_group, + f"{self.station_level_base_group.name} {i}", + ) ) groups.append(self.station_level_base_group) return groups diff --git a/src/templates/token.py b/src/templates/token.py index 547d5ac6..ff675244 100644 --- a/src/templates/token.py +++ b/src/templates/token.py @@ -2,13 +2,14 @@ * Templates: Token * Treated as 'Normal' templates, separated for better organization. """ + # Standard Library Imports from functools import cached_property -from typing import Optional +from collections.abc import Callable # Third Party Imports from omnitils.strings import is_multiline -from photoshop.api.application import ArtLayer +from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports @@ -36,14 +37,15 @@ class TokenTemplate(FullartMod, StarterTemplate): Modifies: * Only supports a singular frame layer which is the Background layer. """ - frame_suffix = 'Token' + + frame_suffix = "Token" """ * Mixin Methods """ - @property - def post_text_methods(self): + @cached_property + def post_text_methods(self) -> list[Callable[[], None]]: """Make some text positioning adjustments after text is formatted.""" return [*super().post_text_methods, self.text_adjustments] @@ -52,20 +54,23 @@ def post_text_methods(self): """ @cached_property - def background_layer(self) -> ArtLayer: + def background_layer(self) -> ArtLayer | None: """ArtLayer: Background governed by Legendary and creature checks.""" - return psd.getLayer(self.layout.pinlines, [ - LAYERS.FRAME, - LAYERS.LEGENDARY if self.is_legendary else LAYERS.NON_LEGENDARY, - LAYERS.CREATURE if self.is_creature else LAYERS.NON_CREATURE - ]) + return psd.getLayer( + self.layout.pinlines, + [ + LAYERS.FRAME, + LAYERS.LEGENDARY if self.is_legendary else LAYERS.NON_LEGENDARY, + LAYERS.CREATURE if self.is_creature else LAYERS.NON_CREATURE, + ], + ) """ * Groups """ @cached_property - def textbox_group(self) -> LayerSet: + def textbox_group(self) -> LayerSet | None: """A group containing background, text, and reference based upon oracle text requirements.""" # Decide the textbox size @@ -84,49 +89,53 @@ def textbox_group(self) -> LayerSet: group = LAYERS.FULL # Enable and return group - group = psd.getLayerSet(group, LAYERS.TEXTBOX) - group.visible = True - return group + if group := psd.getLayerSet(group, LAYERS.TEXTBOX): + group.visible = True + return group """ * References """ @cached_property - def textbox_reference(self) -> ReferenceLayer: + def textbox_reference(self) -> ReferenceLayer | None: """Pull from the textbox group.""" return psd.get_reference_layer(LAYERS.TEXTBOX_REFERENCE, self.textbox_group) @cached_property - def type_line_reference(self) -> ReferenceLayer: + def type_line_reference(self) -> ReferenceLayer | None: """Reference to scale the TypeLine.""" return psd.get_reference_layer(LAYERS.TYPE_LINE_REFERENCE, self.textbox_group) @cached_property - def name_reference(self) -> ReferenceLayer: + def name_reference(self) -> ReferenceLayer | None: """Reference to scale the Card Name""" if self.is_legendary: - return psd.get_reference_layer(LAYERS.NAME_REFERENCE_LEGENDARY, self.text_group) - return psd.get_reference_layer(LAYERS.NAME_REFERENCE_NON_LEGENDARY, self.text_group) + return psd.get_reference_layer( + LAYERS.NAME_REFERENCE_LEGENDARY, self.text_group + ) + return psd.get_reference_layer( + LAYERS.NAME_REFERENCE_NON_LEGENDARY, self.text_group + ) """ * Text Layers """ @cached_property - def text_layer_type(self) -> ArtLayer: + def text_layer_type(self) -> ArtLayer | None: """Pull from the textbox group.""" return psd.getLayer(LAYERS.TYPE_LINE, self.textbox_group) @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """Full textbox group has both creature and non-creature option.""" - if self.textbox_group.name == LAYERS.FULL: + if self.textbox_group and self.textbox_group.name == LAYERS.FULL: return psd.getLayer( - LAYERS.RULES_TEXT_CREATURE if ( - self.is_creature - ) else LAYERS.RULES_TEXT_NONCREATURE, - self.textbox_group + LAYERS.RULES_TEXT_CREATURE + if (self.is_creature) + else LAYERS.RULES_TEXT_NONCREATURE, + self.textbox_group, ) return psd.getLayer(LAYERS.RULES_TEXT, self.textbox_group) @@ -145,18 +154,22 @@ def enable_frame_layers(self) -> None: def basic_text_layers(self) -> None: """Don't include the mana cost in basic text layers.""" - self.text.extend([ - text_classes.ScaledWidthTextField( - layer=self.text_layer_name, - contents=self.layout.name, - reference=self.name_reference - ), - text_classes.ScaledWidthTextField( - layer=self.text_layer_type, - contents=self.layout.type_line, - reference=self.type_reference + if self.text_layer_name: + self.text.append( + text_classes.ScaledWidthTextField( + layer=self.text_layer_name, + contents=self.layout.name, + reference=self.name_reference, + ) + ) + if self.text_layer_type: + self.text.append( + text_classes.ScaledWidthTextField( + layer=self.text_layer_type, + contents=self.layout.type_line, + reference=self.type_reference, + ) ) - ]) def rules_text_and_pt_layers(self) -> None: """3 rules text modes: None, One-Line, and Full""" @@ -165,44 +178,48 @@ def rules_text_and_pt_layers(self) -> None: CON.line_break_lead = 3 # Rules Text - if self.textbox_group.name == LAYERS.ONE_LINE: - self.text.append( - text_classes.FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - color=psd.rgb_white(), - flavor=self.layout.flavor_text, - reference=self.textbox_reference, - scale_height=False, - scale_width=True, - centered=True + if self.textbox_group and self.text_layer_rules: + if self.textbox_group.name == LAYERS.ONE_LINE: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + color=psd.rgb_white(), + flavor=self.layout.flavor_text, + reference=self.textbox_reference, + scale_height=False, + scale_width=True, + centered=True, + ) ) - ) - elif self.textbox_group.name == LAYERS.FULL: - self.text.append( - text_classes.FormattedTextArea( - layer=self.text_layer_rules, - contents=self.layout.oracle_text, - color=psd.rgb_white(), - flavor=self.layout.flavor_text, - reference=self.textbox_reference, - centered=True + elif self.textbox_group.name == LAYERS.FULL: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules, + contents=self.layout.oracle_text, + color=psd.rgb_white(), + flavor=self.layout.flavor_text, + reference=self.textbox_reference, + centered=True, + ) ) - ) # PT Layer if self.is_creature: # Enable cutout - psd.enable_vector_mask(self.textbox_group.parent) - self.text.append( - text_classes.TextField( - layer=self.text_layer_pt, - contents=f"{self.layout.power}/{self.layout.toughness}" + if self.textbox_group and isinstance( + (parent := self.textbox_group.parent), LayerSet + ): + psd.enable_vector_mask(parent) + if self.text_layer_pt: + self.text.append( + text_classes.TextField( + layer=self.text_layer_pt, + contents=f"{self.layout.power}/{self.layout.toughness}", + ) ) - ) def text_adjustments(self) -> None: - # Vertically center the name after it's been scaled psd.align_all(self.text_layer_name, self.name_reference) diff --git a/src/templates/transform.py b/src/templates/transform.py index fc7d57d5..8055b4bf 100644 --- a/src/templates/transform.py +++ b/src/templates/transform.py @@ -1,18 +1,21 @@ """ * Templates: Transform / Ixalan """ + # Standard Library Imports from functools import cached_property -from typing import Optional, Callable +from collections.abc import Callable # Third Party Imports from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet # Local Imports -from src.enums.adobe import Dimensions from src.enums.layers import LAYERS from src.enums.mtg import TransformIcons import src.helpers as psd +from src.helpers.position import DimensionNames +from src.layouts import NormalLayout from src.templates._core import BaseTemplate, NormalTemplate from src.templates._vector import VectorTemplate from src.text_layers import TextField @@ -40,13 +43,13 @@ class TransformMod(BaseTemplate): """ @cached_property - def frame_layer_methods(self) -> list[Callable]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add Transform frame layers step.""" funcs = [self.enable_transform_layers] if self.is_transform else [] return super().frame_layer_methods + funcs @cached_property - def text_layer_methods(self) -> list[Callable]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Transform text layers step.""" funcs = [self.text_layers_transform] if self.is_transform else [] return super().text_layer_methods + funcs @@ -56,7 +59,7 @@ def text_layer_methods(self) -> list[Callable]: """ @cached_property - def text_layer_rules(self) -> Optional[ArtLayer]: + def text_layer_rules(self) -> ArtLayer | None: """Supports noncreature and creature, with or without flipside PT.""" if self.is_transform and self.is_front and self.is_flipside_creature: if self.is_creature: @@ -69,7 +72,7 @@ def text_layer_rules(self) -> Optional[ArtLayer]: """ @cached_property - def text_layer_flipside_pt(self) -> Optional[ArtLayer]: + def text_layer_flipside_pt(self) -> ArtLayer | None: """Flipside power/toughness layer for front face Transform cards.""" return psd.getLayer(LAYERS.FLIPSIDE_POWER_TOUGHNESS, self.text_group) @@ -113,21 +116,25 @@ def text_layers_transform_front(self) -> None: """Adds and modifies text layers for front face transform cards.""" # Add flipside Power/Toughness - if self.is_flipside_creature: + if self.is_flipside_creature and self.text_layer_flipside_pt: self.text.append( TextField( layer=self.text_layer_flipside_pt, - contents=f'{self.layout.other_face_power}/' - f'{self.layout.other_face_toughness}')) + contents=f"{self.layout.other_face_power}/" + f"{self.layout.other_face_toughness}", + ) + ) def text_layers_transform_back(self) -> None: """Adds and modifies text layers for back face transform cards.""" # Rear face Eldrazi cards: Black rules, typeline, and PT text if self.layout.transform_icon == TransformIcons.MOONELDRAZI: - self.text_layer_name.textItem.color = self.RGB_BLACK - self.text_layer_type.textItem.color = self.RGB_BLACK - if self.is_creature: + if self.text_layer_name: + self.text_layer_name.textItem.color = self.RGB_BLACK + if self.text_layer_type: + self.text_layer_type.textItem.color = self.RGB_BLACK + if self.is_creature and self.text_layer_pt: self.text_layer_pt.textItem.color = self.RGB_BLACK @@ -140,7 +147,8 @@ class VectorTransformMod(TransformMod, VectorTemplate): def enable_transform_layers(self) -> None: """Enable group containing Transform layers.""" - self.dfc_group.parent.visible = True + if self.dfc_group and isinstance((parent := self.dfc_group.parent), LayerSet): + parent.visible = True super().enable_transform_layers() """ @@ -154,9 +162,11 @@ def text_layers_transform_back(self) -> None: if self.layout.transform_icon != TransformIcons.MOONELDRAZI: psd.enable_layer_fx(self.text_layer_name) psd.enable_layer_fx(self.text_layer_type) - self.text_layer_name.textItem.color = psd.rgb_white() - self.text_layer_type.textItem.color = psd.rgb_white() - if self.is_creature: + if self.text_layer_name: + self.text_layer_name.textItem.color = psd.rgb_white() + if self.text_layer_type: + self.text_layer_type.textItem.color = psd.rgb_white() + if self.is_creature and self.text_layer_pt: psd.enable_layer_fx(self.text_layer_pt) self.text_layer_pt.textItem.color = psd.rgb_white() @@ -172,24 +182,30 @@ class IxalanMod(NormalTemplate): * No mana cost or scaled typeline * Centered Expansion Symbol """ - is_creature = False - is_name_shifted = False + + @cached_property + def is_creature(self) -> bool: + return False + + @cached_property + def is_name_shifted(self) -> bool: + return False """ * Expansion Symbol """ @cached_property - def expansion_symbol_alignments(self) -> list[Dimensions]: + def expansion_symbol_alignments(self) -> list[DimensionNames]: """Expansion symbol is entirely centered.""" - return [Dimensions.CenterX, Dimensions.CenterY] + return ["center_x", "center_y"] """ * Layer Properties """ @cached_property - def background_layer(self) -> Optional[ArtLayer]: + def background_layer(self) -> ArtLayer | None: """Uses pinline color for background choice layer.""" return psd.getLayer(self.pinlines, LAYERS.BACKGROUND) @@ -199,7 +215,8 @@ def background_layer(self) -> Optional[ArtLayer]: def enable_frame_layers(self): """Only background frame layer.""" - self.background_layer.visible = True + if self.background_layer: + self.background_layer.visible = True """ * Text Layer Methods @@ -207,14 +224,14 @@ def enable_frame_layers(self): def basic_text_layers(self): """No mana cost layer, no scaled typeline.""" - self.text.extend([ - TextField( - layer=self.text_layer_name, - contents=self.layout.name), - TextField( - layer=self.text_layer_type, - contents=self.layout.type_line) - ]) + if self.text_layer_name: + self.text.append( + TextField(layer=self.text_layer_name, contents=self.layout.name), + ) + if self.text_layer_type: + self.text.append( + TextField(layer=self.text_layer_type, contents=self.layout.type_line) + ) """ @@ -226,7 +243,7 @@ class TransformTemplate(TransformMod, NormalTemplate): """Template for double faced Transform cards introduced in Innistrad block.""" @cached_property - def pinlines_layer(self) -> Optional[ArtLayer]: + def pinlines_layer(self) -> ArtLayer | None: """Does not support colored land layers.""" if self.is_land and self.pinlines != LAYERS.LAND: return psd.getLayer(self.pinlines, LAYERS.PINLINES_TEXTBOX) @@ -237,7 +254,7 @@ class IxalanTemplate(IxalanMod, NormalTemplate): """Template for the back face lands for transforming cards from Ixalan block.""" @classmethod - def get_template_route(cls, layout, **kwargs) -> BaseTemplate: + def get_template_route(cls, layout: NormalLayout) -> BaseTemplate: """Reroute for multicolor cards, front cards, and non-land and/or creature cards. Args: @@ -246,17 +263,19 @@ def get_template_route(cls, layout, **kwargs) -> BaseTemplate: Returns: Initialized template class object. """ - if any([ - len(layout.identity) > 1, - not layout.is_land, - layout.is_front, - layout.is_creature - ]): + if any( + [ + len(layout.identity) > 1, + not layout.is_land, + layout.is_front, + layout.is_creature, + ] + ): # Redirect to regular Transform template return cls.redirect_template( template_class=TransformTemplate, - template_file='tf-front.psd' if layout.is_front else 'tf-back.psd', + template_file="tf-front.psd" if layout.is_front else "tf-back.psd", layout=layout, - **kwargs) + ) # Route normally - return super().get_template_route(layout, **kwargs) + return super().get_template_route(layout) diff --git a/src/text_layers.py b/src/text_layers.py index f8a13e9d..b397ceb8 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -1,10 +1,11 @@ """ * Text Layer Classes """ + # Standard Library Imports from contextlib import suppress from functools import cached_property -from typing import Optional, Union +from typing import NotRequired, TypedDict, Unpack # Third Party Imports from photoshop.api import ( @@ -16,7 +17,8 @@ SolidColor, Language, Justification, - RasterizeType) + RasterizeType, +) from photoshop.api._document import Document from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -25,11 +27,17 @@ # Local Imports from src import APP, CFG, CON, CONSOLE -from src.cards import generate_italics, locate_symbols, locate_italics, CardItalicString, CardSymbolString +from src.cards import ( + generate_italics, + locate_symbols, + locate_italics, + CardItalicString, + CardSymbolString, +) from src.enums.mtg import CardFonts from src.helpers import select_layer from src.helpers.bounds import get_layer_dimensions, LayerDimensions, get_layer_width -from src.helpers.colors import apply_color, get_text_item_color +from src.helpers.colors import apply_color from src.helpers.position import position_between_layers, clear_reference_vertical from src.helpers.selection import select_layer_bounds from src.helpers.text import ( @@ -39,7 +47,8 @@ scale_text_to_width, scale_text_to_height, scale_text_left_overlap, - scale_text_right_overlap) + scale_text_right_overlap, +) from src.schema.colors import ColorObject from src.utils.adobe import ReferenceLayer @@ -53,12 +62,39 @@ """ +class TextFieldKwargs(TypedDict): + color: NotRequired[SolidColor | None] + font: NotRequired[str | None] + font_mana: NotRequired[str | None] + font_italic: NotRequired[str | None] + font_bold: NotRequired[str | None] + reference: NotRequired[ArtLayer | None] + symbol_map: NotRequired[dict[str, tuple[str, list[ColorObject]]]] + flavor: NotRequired[str | None] + flavor_color: NotRequired[SolidColor] + line_break_lead: NotRequired[int | float] + flavor_text_lead: NotRequired[int | float] + flavor_text_lead_divider: NotRequired[int | float] + centered: NotRequired[bool] + flavor_centered: NotRequired[bool] + bold_rules_text: NotRequired[bool] + right_align_quote: NotRequired[bool] + pt_reference: NotRequired[ReferenceLayer | None] + divider: NotRequired[ArtLayer | LayerSet | None] + scale_height: NotRequired[bool] + scale_width: NotRequired[bool] + fix_overflow_height: NotRequired[bool] + fix_overflow_width: NotRequired[bool] + + class TextField: FONT = CardFonts.TITLES FONT_ITALIC = CardFonts.RULES_ITALIC FONT_BOLD = CardFonts.RULES_BOLD - def __init__(self, layer: ArtLayer, contents: str = "", **kwargs): + def __init__( + self, layer: ArtLayer, contents: str = "", **kwargs: Unpack[TextFieldKwargs] + ): """A generic TextField, which allows you to set a text layer's contents and text color. Args: @@ -76,47 +112,46 @@ def __init__(self, layer: ArtLayer, contents: str = "", **kwargs): """ self._kwargs = kwargs self._layer = layer - self.contents = contents.replace( - "\n", "\r") + self.contents = contents.replace("\n", "\r") """ * Keyword Arguments """ @cached_property - def kwargs(self) -> dict: + def kwargs(self) -> TextFieldKwargs: """Contains optional parameters to modify text formatting behavior.""" return self._kwargs @cached_property - def kw_color(self) -> Optional[SolidColor]: + def kw_color(self) -> SolidColor | None: """Color to apply to the TextItem.""" - return self.kwargs.get('color') + return self.kwargs.get("color") @cached_property - def kw_font(self) -> Optional[str]: + def kw_font(self) -> str | None: """Font to apply to the root TextItem.""" - return self.kwargs.get('font') + return self.kwargs.get("font") @cached_property - def kw_font_mana(self) -> Optional[str]: + def kw_font_mana(self) -> str | None: """Font to apply to any mana symbols in the TextItem.""" - return self.kwargs.get('font_mana') + return self.kwargs.get("font_mana") @cached_property - def kw_font_italic(self) -> Optional[str]: + def kw_font_italic(self) -> str | None: """Font to apply to any italicized text in the TextItem.""" - return self.kwargs.get('font_italic') + return self.kwargs.get("font_italic") @cached_property - def kw_font_bold(self) -> Optional[str]: + def kw_font_bold(self) -> str | None: """Font to apply to any bold text in the TextItem.""" - return self.kwargs.get('font_bold') + return self.kwargs.get("font_bold") @cached_property def kw_symbol_map(self) -> dict[str, tuple[str, list[ColorObject]]]: """Symbol map to use for formatting mana symbols.""" - return self.kwargs.get('symbol_map', CON.symbol_map) + return self.kwargs.get("symbol_map", CON.symbol_map) """ * Checks @@ -152,20 +187,20 @@ def TI(self) -> TextItem: return self.layer.textItem @cached_property - def reference(self) -> Optional[ArtLayer]: + def reference(self) -> ArtLayer | None: """A reference layer, typically used for scaling the TextItem.""" - return self.kwargs.get('reference', None) + return self.kwargs.get("reference", None) @cached_property - def reference_dims(self) -> Optional[type[LayerDimensions]]: - """Optional[type[LayerDimensions]]: Dimensions of the scaling reference layer.""" + def reference_dims(self) -> LayerDimensions | None: + """Dimensions of the scaling reference layer.""" if isinstance(self.reference, ReferenceLayer): return self.reference.dims if self.reference: return get_layer_dimensions(self.reference) return None - @property + @cached_property def input(self) -> str: """Raw contents provided to fill the TextItem.""" return self.contents @@ -173,7 +208,7 @@ def input(self) -> str: @cached_property def color(self) -> SolidColor: """A SolidColor object provided, or fallback on current TextItem color.""" - return self.kw_color or get_text_item_color(self.TI) + return self.kw_color or self.TI.color @cached_property def font(self) -> str: @@ -192,9 +227,11 @@ def validate(self): return True with suppress(Exception): # Layer provided doesn't exist or isn't a text layer - name = self.layer.name if self.layer else '[Non-Layer]' - print(f'Text Field class: {self.__class__.__name__}\n' - f'Invalid layer provided: {name}') + name = self.layer.name if self.layer else "[Non-Layer]" + print( + f"Text Field class: {self.__class__.__name__}\n" + f"Invalid layer provided: {name}" + ) self.layer.visible = False return False @@ -217,9 +254,9 @@ def execute(self): self.TI.language = Language.EnglishUSA -class ScaledTextField (TextField): +class ScaledTextField(TextField): """A TextField which automatically scales down its font size until the right bound - no longer overlaps with the `reference` layer's left bound.""" + no longer overlaps with the `reference` layer's left bound.""" def execute(self): super().execute() @@ -229,9 +266,9 @@ def execute(self): scale_text_right_overlap(self.layer, self.reference) -class ScaledTextFieldLeft (TextField): +class ScaledTextFieldLeft(TextField): """A TextField which automatically scales down its font size until the left bound - no longer overlaps with the `reference` layer's right bound.""" + no longer overlaps with the `reference` layer's right bound.""" def execute(self): super().execute() @@ -241,9 +278,10 @@ def execute(self): scale_text_left_overlap(self.layer, self.reference) -class ScaledWidthTextField (TextField): +class ScaledWidthTextField(TextField): """A TextField which automatically scales down its font size until the width of the - layer is within the horizontal bound of a reference layer.""" + layer is within the horizontal bound of a reference layer.""" + FONT = CardFonts.RULES @cached_property @@ -252,28 +290,39 @@ def font(self) -> str: return self.kw_font or CON.font_rules_text @cached_property - def reference_width(self) -> Union[float, int]: + def reference_width(self) -> float | int | None: """Union[float, int]: Width of the reference layer provided.""" - return get_layer_width(self.reference) + if self.reference: + return get_layer_width(self.reference) def execute(self): super().execute() # Scale down the text layer until it doesn't overlap with a reference layer - if self.reference: + if self.reference_width: scale_text_to_width(self.layer, width=self.reference_width) -class FormattedTextField (TextField): +class TextDetails(TypedDict): + rules_text: str + input_string: str + symbol_indices: list[CardSymbolString] + italics_indices: list[CardItalicString] + + +class FormattedTextField(TextField): """A utility class containing the required infrastructure to format text with action descriptors. * Formats any recognized mana symbols contained in the text. * Formats any modal/bullet point sections in the text. * Formats any italicized or bolded text, as well as line breaks. """ + FONT = CardFonts.RULES - def __init__(self, layer: ArtLayer, contents: str = "", **kwargs): + def __init__( + self, layer: ArtLayer, contents: str = "", **kwargs: Unpack[TextFieldKwargs] + ): super().__init__(layer, contents, **kwargs) # Pre-cache text details @@ -284,8 +333,7 @@ def __init__(self, layer: ArtLayer, contents: str = "", **kwargs): """ @cached_property - def text_details(self) -> dict: - + def text_details(self) -> TextDetails: # Generate italic text arrays from things in (parentheses), ability words, and the given flavor text italic_text = generate_italics(self.contents) if self.contents else [] @@ -293,51 +341,60 @@ def text_details(self) -> dict: if self.flavor_text.count("*") >= 2: # Don't italicize text between asterisk flavor_text_split = self.flavor_text.split("*") - italic_text.extend([v for i, v in enumerate(flavor_text_split) if not i % 2 and not v == '']) - self.flavor_text = ''.join(flavor_text_split) + italic_text.extend( + [ + v + for i, v in enumerate(flavor_text_split) + if not i % 2 and not v == "" + ] + ) + self.flavor_text = "".join(flavor_text_split) elif self.flavor_text: # Regular flavor text italic_text.append(self.flavor_text) # Locate symbols and update the rules string rules, symbols = locate_symbols( - text=self.contents, - symbol_map=self.kw_symbol_map, - logger=CONSOLE) + text=self.contents, symbol_map=self.kw_symbol_map, logger=CONSOLE + ) # Create the new input string - input_str = f'{rules}\r{self.flavor_text}' if rules and self.flavor_text else ( - rules if rules else self.flavor_text) + input_str = ( + f"{rules}\r{self.flavor_text}" + if rules and self.flavor_text + else (rules if rules else self.flavor_text) + ) # Locate italics text indices italicized = locate_italics( st=input_str, italics_strings=italic_text, symbol_map=self.kw_symbol_map, - logger=CONSOLE) + logger=CONSOLE, + ) # Return text details return { - 'rules_text': rules, - 'input_string': input_str, - 'symbol_indices': symbols, - 'italics_indices': italicized, + "rules_text": rules, + "input_string": input_str, + "symbol_indices": symbols, + "italics_indices": italicized, } @cached_property def italics_indices(self) -> list[CardItalicString]: - return self.text_details['italics_indices'] + return self.text_details["italics_indices"] @cached_property def symbol_indices(self) -> list[CardSymbolString]: - return self.text_details['symbol_indices'] + return self.text_details["symbol_indices"] @cached_property def input(self) -> str: - return self.text_details['input_string'] + return self.text_details["input_string"] - @property - def divider(self) -> Optional[ArtLayer]: + @cached_property + def divider(self) -> ArtLayer | LayerSet | None: # Default to None unless overridden return @@ -347,10 +404,10 @@ def divider(self) -> Optional[ArtLayer]: @cached_property def rules_text(self) -> str: - return self.text_details['rules_text'] + return self.text_details["rules_text"] @cached_property - def rules_range(self) -> Optional[tuple[int, int]]: + def rules_range(self) -> tuple[int, int] | None: if not self.rules_text: return return 0, len(self.rules_text) @@ -373,10 +430,12 @@ def rules_end(self) -> int: @cached_property def flavor_text(self) -> str: - return self.kwargs.get('flavor', '').replace('\n', '\r') + if flavor := self.kwargs.get("flavor", ""): + return flavor.replace("\n", "\r") + return "" @cached_property - def flavor_text_range(self) -> Optional[tuple[int, int]]: + def flavor_text_range(self) -> tuple[int, int] | None: if not self.flavor_text: return total = len(self.input) @@ -405,9 +464,9 @@ def quote_index(self) -> int: """ @cached_property - def flavor_color(self) -> Optional[SolidColor]: + def flavor_color(self) -> SolidColor | None: """If defined separately, `color` is effectively the `rules_color`.""" - return self.kwargs.get('flavor_color') + return self.kwargs.get("flavor_color", None) """ * Fonts @@ -438,19 +497,20 @@ def font_bold(self) -> str: """ @cached_property - def line_break_lead(self) -> Union[int, float]: + def line_break_lead(self) -> int | float: """Leading space before linebreaks.""" return self.kwargs.get( - 'line_break_lead', - 0 if self.contents_centered else CON.line_break_lead + "line_break_lead", 0 if self.contents_centered else CON.line_break_lead ) @cached_property - def flavor_text_lead(self) -> Union[int, float]: + def flavor_text_lead(self) -> int | float: """Leading space before linebreak separating rules and flavor text. Increased if divider is present.""" if self.divider: - return self.kwargs.get('flavor_text_lead_divider', CON.flavor_text_lead_divider) - return self.kwargs.get('flavor_text_lead', CON.flavor_text_lead) + return self.kwargs.get( + "flavor_text_lead_divider", CON.flavor_text_lead_divider + ) + return self.kwargs.get("flavor_text_lead", CON.flavor_text_lead) """ * Optional Properties @@ -458,23 +518,23 @@ def flavor_text_lead(self) -> Union[int, float]: @cached_property def contents_centered(self) -> bool: - return self.kwargs.get('centered', False) + return self.kwargs.get("centered", False) @cached_property def flavor_centered(self) -> bool: - return self.kwargs.get('flavor_centered', self.contents_centered) + return self.kwargs.get("flavor_centered", self.contents_centered) @cached_property def bold_rules_text(self) -> bool: - return self.kwargs.get('bold_rules_text', False) + return self.kwargs.get("bold_rules_text", False) @cached_property def right_align_quote(self) -> bool: - return self.kwargs.get('right_align_quote', False) + return self.kwargs.get("right_align_quote", False) @cached_property def font_size(self) -> float: - if font_size := self.kwargs.get('font_size'): + if font_size := self.kwargs.get("font_size"): return font_size * get_text_scale_factor(self.layer) return self.TI.size * get_text_scale_factor(self.layer) @@ -492,7 +552,7 @@ def is_quote_text(self) -> bool: @cached_property def is_modal(self) -> bool: - return bool('\u2022' in self.input) + return bool("\u2022" in self.input) """ * Methods @@ -518,31 +578,31 @@ def format_text(self): main_list = ActionList() # Descriptor ID's - idTo = sID('to') - size = sID('size') - idFrom = sID('from') - textLayer = sID('textLayer') - textStyle = sID('textStyle') - ptUnit = sID('pointsUnit') - spaceAfter = sID('spaceAfter') - autoLeading = sID('autoLeading') - startIndent = sID('startIndent') - spaceBefore = sID('spaceBefore') - leadingType = sID('leadingType') - styleRange = sID('textStyleRange') - paragraphStyle = sID('paragraphStyle') - firstLineIndent = sID('firstLineIndent') - fontPostScriptName = sID('fontPostScriptName') - paragraphStyleRange = sID('paragraphStyleRange') + idTo = sID("to") + size = sID("size") + idFrom = sID("from") + textLayer = sID("textLayer") + textStyle = sID("textStyle") + ptUnit = sID("pointsUnit") + spaceAfter = sID("spaceAfter") + autoLeading = sID("autoLeading") + startIndent = sID("startIndent") + spaceBefore = sID("spaceBefore") + leadingType = sID("leadingType") + styleRange = sID("textStyleRange") + paragraphStyle = sID("paragraphStyle") + firstLineIndent = sID("firstLineIndent") + fontPostScriptName = sID("fontPostScriptName") + paragraphStyleRange = sID("paragraphStyleRange") # Spin up the text insertion action - main_desc.putString(sID('textKey'), self.input) + main_desc.putString(sID("textKey"), self.input) main_range.putInteger(idFrom, 0) main_range.putInteger(idTo, len(self.input)) apply_color(main_style, self.color) main_style.putBoolean(autoLeading, False) main_style.putUnitDouble(size, ptUnit, self.font_size) - main_style.putUnitDouble(sID('leading'), ptUnit, self.font_size) + main_style.putUnitDouble(sID("leading"), ptUnit, self.font_size) main_style.putString(fontPostScriptName, self.font) main_range.putObject(textStyle, textStyle, main_style) main_list.putObject(styleRange, main_range) @@ -604,7 +664,6 @@ def format_text(self): # Flavor text actions if self.is_flavor_text: - # Add linebreak spacing between rules and flavor text para_range.putInteger(idFrom, self.flavor_start + 3) para_range.putInteger(idTo, self.flavor_start + 4) @@ -618,16 +677,15 @@ def format_text(self): # Adjust flavor text color if self.flavor_color: - main_range.PutInteger(idFrom, self.flavor_start) - main_range.PutInteger(idTo, self.flavor_end) + main_range.putInteger(idFrom, self.flavor_start) + main_range.putInteger(idTo, self.flavor_end) apply_color(main_style, self.flavor_color) main_style.putString(fontPostScriptName, self.font_italic) - main_range.PutObject(textStyle, textStyle, main_style) + main_range.putObject(textStyle, textStyle, main_style) main_list.putObject(styleRange, main_range) # Quote actions flavor text if self.is_quote_text: - # Adjust line break spacing if there's a line break in the flavor text para_range.putInteger(idFrom, self.quote_index + 3) para_range.putInteger(idTo, len(self.input)) @@ -639,8 +697,10 @@ def format_text(self): if self.right_align_quote and '"\r—' in self.flavor_text: para_range.putInteger(idFrom, self.input.find('"\r—') + 2) para_range.putInteger(idTo, self.flavor_end) - para_style.putBoolean(sID('styleSheetHasParent'), True) - para_range.putEnumerated(sID('align'), sID('alignmentType'), sID('right')) + para_style.putBoolean(sID("styleSheetHasParent"), True) + para_range.putEnumerated( + sID("align"), sID("alignmentType"), sID("right") + ) para_range.putObject(paragraphStyle, paragraphStyle, para_style) style_list.putObject(paragraphStyleRange, para_range) @@ -668,7 +728,7 @@ def execute(self): self.TI.hyphenation = False -class FormattedTextArea (FormattedTextField): +class FormattedTextArea(FormattedTextField): """A FormattedTextField where the text is required to fit within a given area. * Reduces font size until the text fits within the reference layer's bounds. @@ -681,13 +741,15 @@ class FormattedTextArea (FormattedTextField): """ @cached_property - def pt_reference(self) -> Optional[ReferenceLayer]: - return self.kwargs.get('pt_reference', None) + def pt_reference(self) -> ReferenceLayer | None: + return self.kwargs.get("pt_reference", None) @cached_property - def divider(self) -> Optional[Union[ArtLayer, LayerSet]]: + def divider(self) -> ArtLayer | LayerSet | None: """Divider layer, if provided and flavor text exists.""" - if (divider := self.kwargs.get('divider')) and all([self.flavor_text, self.contents, CFG.flavor_divider]): + if (divider := self.kwargs.get("divider")) and all( + [self.flavor_text, self.contents, CFG.flavor_divider] + ): divider.visible = True return divider return @@ -695,32 +757,22 @@ def divider(self) -> Optional[Union[ArtLayer, LayerSet]]: @cached_property def scale_height(self) -> bool: """Scale text to fit reference height (Default: True).""" - if scale_height := self.kwargs.get('scale_height'): - return scale_height - return True + return self.kwargs.get("scale_height", True) @cached_property def scale_width(self) -> bool: """Scale text to fit reference width (Default: False).""" - if scale_width := self.kwargs.get('scale_width'): - return scale_width - return False + return self.kwargs.get("scale_width", True) @cached_property def fix_overflow_width(self) -> bool: """Scale text to fit bounding box width (Default: False).""" - if fix_overflow_width := self.kwargs.get('fix_overflow_width'): - return fix_overflow_width - return True + return self.kwargs.get("fix_overflow_width", False) @cached_property def fix_overflow_height(self) -> bool: - """Scale text to fit bounding box height (Default: If it overflows the bounds).""" - if len(self.contents + self.flavor_text) > 280: - return True - if fix_overflow_height := self.kwargs.get('fix_overflow_height'): - return fix_overflow_height - return False + """Scale text to fit bounding box height (Default: True).""" + return self.kwargs.get("fix_overflow_height", True) """ * Methods @@ -728,70 +780,75 @@ def fix_overflow_height(self) -> bool: def insert_divider(self): """Inserts and correctly positions flavor text divider.""" - - # Create a reference layer with no effects - flavor = self.layer.duplicate() - rules = flavor.duplicate() - flavor.rasterize(RasterizeType.EntireLayer) - remove_trailing_text(rules, self.flavor_start) - select_layer_bounds(rules, self.doc_selection) - self.docref.activeLayer = flavor - self.doc_selection.expand(2) - self.doc_selection.clear() - - # Move flavor text to bottom, then position divider - flavor.translate(0, self.layer.bounds[3] - flavor.bounds[3]) - position_between_layers(self.divider, rules, flavor) - self.doc_selection.deselect() - flavor.remove() - rules.remove() - - def pre_scale_to_fit(self) -> Optional[float]: + if self.divider: + # Create a reference layer with no effects + flavor = self.layer.duplicate() + rules = flavor.duplicate() + flavor.rasterize(RasterizeType.EntireLayer) + remove_trailing_text(rules, self.flavor_start) + select_layer_bounds(rules, self.doc_selection) + self.docref.activeLayer = flavor + self.doc_selection.expand(2) + self.doc_selection.clear() + + # Move flavor text to bottom, then position divider + flavor.translate(0, self.layer.bounds[3] - flavor.bounds[3]) + position_between_layers(self.divider, rules, flavor) + self.doc_selection.deselect() + flavor.remove() + rules.remove() + + def pre_scale_to_fit(self) -> float | None: """Fix height overflow before formatting text.""" - contents = self.contents if not self.flavor_text else str( - self.contents + "\r" + self.flavor_text) - self.TI.contents = contents - return scale_text_to_height( - layer=self.layer, - height=int(self.reference_dims['height']*1.1)) - - def scale_to_fit(self, font_size: Optional[float] = None) -> None: + if self.reference_dims: + contents = ( + self.contents + if not self.flavor_text + else str(self.contents + "\r" + self.flavor_text) + ) + self.TI.contents = contents + return scale_text_to_height( + layer=self.layer, height=int(self.reference_dims["height"] * 1.1) + ) + + def scale_to_fit(self, font_size: float | None = None) -> None: """Scale font size to fit within any references.""" # Scale layer to reference if self.reference_dims: - # Resize the text until it fits the reference vertically if self.scale_height: font_size = scale_text_to_height( - layer=self.layer, - height=self.reference_dims['height']) + layer=self.layer, height=self.reference_dims["height"] + ) # Resize the text until it fits the reference horizontally if self.scale_width: font_size = scale_text_to_width( layer=self.layer, - width=self.reference_dims['width'], - step=0.2, font_size=font_size) + width=self.reference_dims["width"], + step=0.2, + font_size=font_size, + ) # Resize the text until it fits the TextLayer bounding box if self.fix_overflow_width: - scale_text_to_width_textbox( - layer=self.layer, font_size=font_size) + scale_text_to_width_textbox(layer=self.layer, font_size=font_size) def position_within_reference(self): """Positions the layer with respect to the reference, if required.""" + if self.reference_dims: + # Ensure the layer is centered vertically + dims = get_layer_dimensions(self.layer) + self.layer.translate(0, self.reference_dims["center_y"] - dims["center_y"]) - # Ensure the layer is centered vertically - dims = get_layer_dimensions(self.layer) - self.layer.translate(0, self.reference_dims['center_y'] - dims['center_y']) - - # Ensure the layer is centered horizontally if needed - if self.contents_centered and self.flavor_centered: - self.layer.translate(0, self.reference_dims['center_x'] - dims['center_x']) + # Ensure the layer is centered horizontally if needed + if self.contents_centered and self.flavor_centered: + self.layer.translate( + 0, self.reference_dims["center_x"] - dims["center_x"] + ) def execute(self): - # Skip if both are empty if not self.input: return @@ -817,12 +874,10 @@ def execute(self): # Shift vertically if the text overlaps the PT box if self.pt_reference: - # Use newer methodology if top reference not provided delta = clear_reference_vertical( - layer=self.layer, - ref=self.pt_reference, - docsel=self.doc_selection) + layer=self.layer, ref=self.pt_reference, docsel=self.doc_selection + ) # Shift the divider if layer was moved if delta < 0 and self.divider: @@ -833,10 +888,10 @@ def execute(self): * Text Item Type """ -FormattedTextLayer = Union[ - TextField, - ScaledTextField, - FormattedTextArea, - FormattedTextField, - ScaledWidthTextField, -] +FormattedTextLayer = ( + TextField + | ScaledTextField + | FormattedTextArea + | FormattedTextField + | ScaledWidthTextField +) diff --git a/src/utils/adobe.py b/src/utils/adobe.py index cd05f39e..706e872d 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -6,21 +6,24 @@ from contextlib import suppress from ctypes import c_uint32 from functools import cache, cached_property -from typing import Union, Any, Optional, TypedDict, Callable +from typing import ParamSpec, TypeVar, Any, TypedDict +from collections.abc import Callable # Third Party -from comtypes.client.lazybind import Dispatch from packaging.version import parse from photoshop.api import ( ActionDescriptor, ActionReference, Application, DialogModes, + ElementPlacement, PhotoshopPythonAPIError, + TypeUnits, Units) from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop from photoshop.api._document import Document +from photoshop.api._layer import Layer from photoshop.api._layerSet import LayerSet from win32api import FormatMessage @@ -31,14 +34,16 @@ """ * Types & Definitions """ +T = TypeVar("T") +P = ParamSpec("P") # Common Layer Objects -LayerContainer = LayerSet, Document, Dispatch -LayerObject = LayerSet, ArtLayer, Dispatch +LayerContainer = LayerSet, Document +LayerObject = LayerSet, ArtLayer # Common Layer Types -LayerContainerTypes = Union[LayerSet, Document, Dispatch] -LayerObjectTypes = Union[ArtLayer, LayerSet, Dispatch] +LayerContainerTypes = LayerSet | Document +LayerObjectTypes = ArtLayer | LayerSet # Common Photoshop Exceptions PS_EXCEPTIONS = ( @@ -87,20 +92,19 @@ # Layer bounds: left, top, right, bottom -LayerBounds = tuple[int, int, int, int] +LayerBounds = tuple[float, float, float, float] class LayerDimensions(TypedDict): """Calculated layer dimension info for a layer.""" - width: int - height: int - center_x: int - center_y: int - left: int - right: int - top: int - bottom: int - + width: float + height: float + center_x: float + center_y: float + left: float + right: float + top: float + bottom: float """ * Util Classes @@ -110,7 +114,7 @@ class LayerDimensions(TypedDict): class ApplicationHandler(Application): """Wrapper for the Photoshop Application class.""" - def __init__(self, env: Optional[AppEnvironment] = None): + def __init__(self, env: AppEnvironment | None = None): version = env.PS_VERSION if env else None super().__init__(version=version) self._env = env @@ -118,7 +122,7 @@ def __init__(self, env: Optional[AppEnvironment] = None): # Set error dialog state with suppress(Exception): self.displayDialogs = DialogModes.DisplayErrorDialogs if ( - env.PS_ERROR_DIALOG + env and env.PS_ERROR_DIALOG ) else DialogModes.DisplayNoDialogs """ @@ -126,7 +130,7 @@ def __init__(self, env: Optional[AppEnvironment] = None): """ @cached_property - def _env(self) -> Optional[AppEnvironment]: + def _env(self) -> AppEnvironment | None: """AppEnvironment: Global app environment object.""" return @@ -154,7 +158,7 @@ def window_handle(self) -> int | None: ) return self._window_handle - def __new__(cls, env: Optional[Any] = None) -> 'PhotoshopHandler': + def __new__(cls, env: Any | None = None) -> 'PhotoshopHandler': """Always return the same Photoshop Application instance on successive calls. Args: @@ -182,9 +186,9 @@ def refresh_app(self): if not self.is_running(): try: # Load Photoshop and default preferences - super(PhotoshopHandler, self).__init__(env=self._env) + super().__init__(env=self._env) self.preferences.rulerUnits = Units.Pixels - self.preferences.typeUnits = Units.Points + self.preferences.typeUnits = TypeUnits.TypePoints except Exception as e: # Photoshop is either busy or unresponsive return OSError(get_photoshop_error_message(e)) @@ -205,8 +209,8 @@ def set_window_state(self, state: WindowState) -> None: def is_running(cls) -> bool: """Check if the current Photoshop Application instance is still valid.""" with suppress(Exception): - _ = cls._instance.version - return True + if cls._instance and cls._instance.version: + return True return False """ @@ -225,12 +229,6 @@ def charIDToTypeID(self, index: str) -> int: """ return super().charIDToTypeID(index) - @cache - def CharIDToTypeID(self, index: str) -> int: - """Uppercase redirect for charIDToTypeID.""" - return self.charIDToTypeID(index) - - @cache def cID(self, index: str) -> int: """Shorthand redirect for charIDToTypeID.""" return self.charIDToTypeID(index) @@ -247,7 +245,6 @@ def typeIDToCharID(self, index: int) -> str: """ return super().typeIDToCharID(index) - @cache def t2c(self, index: int) -> str: """Shorthand redirect for typeIDToCharID.""" return self.typeIDToCharID(index) @@ -268,12 +265,6 @@ def stringIDToTypeID(self, index: str) -> int: """ return super().stringIDToTypeID(index) - @cache - def StringIDToTypeID(self, index: str) -> int: - """Uppercase redirect for stringIDTotypeID.""" - return self.stringIDToTypeID(index) - - @cache def sID(self, index: str) -> int: """Shorthand redirect for stringIDToTypeID.""" return self.stringIDToTypeID(index) @@ -290,7 +281,6 @@ def typeIDToStringID(self, index: int) -> str: """ return super().typeIDToStringID(index) - @cache def t2s(self, index: int) -> str: """Shorthand redirect for typeIDToStringID.""" return self.typeIDToStringID(index) @@ -330,10 +320,11 @@ def stringIDToCharID(self, index: int) -> str: """ def executeAction( - self, event_id: int, - descriptor: ActionDescriptor, - dialogs: DialogModes = DialogModes.DisplayNoDialogs - ) -> Any: + self, + event_id: int, + descriptor: ActionDescriptor | None = None, + display_dialogs: DialogModes = DialogModes.DisplayNoDialogs + ) -> ActionDescriptor: """Middleware to allow all dialogs when an error occurs upon calling executeAction in development mode. Args: @@ -347,15 +338,7 @@ def executeAction( if self.is_error_dialog_enabled(): # Allow error dialogs if enabled in the app environment return super().executeAction(event_id, descriptor, DialogModes.DisplayErrorDialogs) - return super().executeAction(event_id, descriptor, dialogs) - - def ExecuteAction( - self, event_id: int, - descriptor: ActionDescriptor, - dialogs: DialogModes = DialogModes.DisplayNoDialogs - ) -> Any: - """Utility definition rerouting to original `executeAction`.""" - self.executeAction(event_id, descriptor, dialogs) + return super().executeAction(event_id, descriptor, display_dialogs) """ * Version Checks @@ -391,7 +374,7 @@ def version_meets_requirement(self, value: str) -> bool: """ @cache - def scale_by_dpi(self, value: Union[int, float]) -> int: + def scale_by_dpi(self, value: int | float) -> int: """Scales a value by comparing document DPI to ideal DPI. Args: @@ -407,7 +390,7 @@ class ReferenceLayer(ArtLayer): """A static ArtLayer whose properties such as width or height are not going to change. Most often used as a reference to position or size other layers.""" - def __init__(self, parent: Any = None, app: PhotoshopHandler = None): + def __init__(self, parent: Any = None, app: PhotoshopHandler | None = None): self._global_app = app if app else PhotoshopHandler() super().__init__(parent=parent) @@ -415,7 +398,11 @@ def __init__(self, parent: Any = None, app: PhotoshopHandler = None): * API Methods """ - def duplicate(self, relativeObject=None, insertionLocation=None): + def duplicate( + self, + relativeObject: Layer | None = None, + insertionLocation: ElementPlacement | None = None + ) -> ArtLayer: """Duplicates the layer and returns it as a `ReferenceLayer` object.""" return ReferenceLayer(self.app.duplicate(relativeObject, insertionLocation)) @@ -445,7 +432,7 @@ def id(self) -> int: return self.app.id @cached_property - def action_getter(self) -> ActionReference: + def action_getter(self) -> ActionDescriptor: """Gets action descriptor info object for this layer. Returns: @@ -514,7 +501,7 @@ def dims_no_effects(self) -> LayerDimensions: """ @staticmethod - def get_dimensions_from_bounds(bounds) -> LayerDimensions: + def get_dimensions_from_bounds(bounds: tuple[float,float,float,float]) -> LayerDimensions: """Compute width and height based on a set of bounds given. Args: @@ -539,7 +526,7 @@ def get_dimensions_from_bounds(bounds) -> LayerDimensions: """ -def try_photoshop(func) -> Callable: +def try_photoshop(func: Callable[P, T]) -> Callable[P, T | None]: """Decorator to handle trying to run a Photoshop action but allowing exceptions to fail silently. Args: @@ -548,10 +535,9 @@ def try_photoshop(func) -> Callable: Returns: The wrapped function. """ - def wrapper(self, *args, **kwargs): + def wrapper(*args: P.args, **kwargs: P.kwargs): try: - result = func(self, *args, **kwargs) - return result + return func(*args, **kwargs) except PS_EXCEPTIONS: return return wrapper diff --git a/src/utils/build.py b/src/utils/build.py index f14ae1a4..cbe9a289 100644 --- a/src/utils/build.py +++ b/src/utils/build.py @@ -8,7 +8,7 @@ from contextlib import suppress from glob import glob from pathlib import Path -from typing import Optional, Union, TypedDict, NotRequired +from typing import TypedDict, NotRequired from shutil import ( copy2, copytree, @@ -95,11 +95,11 @@ def make_directories(config: DistConfig) -> None: def copy_directory( - src: Union[str, os.PathLike], - dst: Union[str, os.PathLike], + src: str | os.PathLike[str], + dst: str | os.PathLike[str], x_files: list[str], x_dirs: list[str], - x_ext: Optional[list[str]] = None, + x_ext: list[str] | None = None, recursive: bool = True ) -> None: """Copy a directory from src to dst. @@ -208,7 +208,7 @@ def build_zip(filename: str) -> None: def build_release( - version: Optional[str] = None, + version: str | None = None, console: bool = False, beta: bool = False, zipped: bool = True @@ -282,7 +282,8 @@ def generate_mkdocs(path: str) -> None: directory = SRC / 'src' / path parent = 'temps' if path == 'templates' else path for module in get_python_modules(directory): - functions, classes = [], [] + functions: list[str] = [] + classes: list[str] = [] # Scan for functions and classes to document with open(Path(directory, module).with_suffix('.py')) as file: @@ -321,7 +322,7 @@ def generate_mkdocs(path: str) -> None: ) for func in functions] -def generate_nav(headers: list[str], paths: list[str]) -> list[dict]: +def generate_nav(headers: list[str], paths: list[str]) -> list[dict[str, list[str]]]: """Generates the nav menu data for mkdocs.yml containing scanned modules. Args: @@ -331,7 +332,7 @@ def generate_nav(headers: list[str], paths: list[str]) -> list[dict]: Returns: List of nav item objects. """ - nav = [] + nav: list[dict[str, list[str]]] = [] for i, path in enumerate(paths): parent = 'temps' if path == 'templates' else path md_files = sorted([f for f in os.listdir(Path(SRC, 'docs', parent)) if f.endswith('.md')]) @@ -340,7 +341,7 @@ def generate_nav(headers: list[str], paths: list[str]) -> list[dict]: return nav -def update_mkdocs_yml(nav: list[dict]) -> None: +def update_mkdocs_yml(nav: list[dict[str, list[str]]]) -> None: """Updates the mkdocs.yml file with the new nav list. Args: diff --git a/src/utils/download.py b/src/utils/download.py index f31d1139..39c09cb4 100644 --- a/src/utils/download.py +++ b/src/utils/download.py @@ -5,7 +5,7 @@ import shutil from dataclasses import dataclass from pathlib import Path -from typing import Callable, Optional +from collections.abc import Callable # Third Party Imports import requests @@ -30,7 +30,7 @@ class HEADERS: """ -def download_cloudfront(url: yarl.URL, path: Path, callback: Optional[Callable] = None) -> bool: +def download_cloudfront(url: yarl.URL, path: Path, callback: Callable[[int, int], None] | None = None) -> bool: """Download a template from cloudfront cached Amazon S3 bucket. Args: diff --git a/src/utils/fonts.py b/src/utils/fonts.py index e45559ab..9a226651 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -8,18 +8,20 @@ from ctypes import wintypes import os.path as osp import re -from typing import Optional, TypedDict +from typing import TypedDict # Third Party Imports +from photoshop.api._document import Document from photoshop.api.enumerations import LayerKind +from photoshop.api._layerSet import LayerSet from fontTools.ttLib import TTFont, TTLibError from packaging.version import parse # Local Imports -from src.utils.adobe import LayerContainer, PhotoshopHandler, PS_EXCEPTIONS +from src.utils.adobe import PhotoshopHandler, PS_EXCEPTIONS # Precompile font version pattern -REG_FONT_VER: re.Pattern = re.compile(r"\b(\d+\.\d+)\b") +REG_FONT_VER: re.Pattern[str] = re.compile(r"\b(\d+\.\d+)\b") """ * Types @@ -28,8 +30,8 @@ class FontDetails(TypedDict): """Font name and current version.""" - name: Optional[str] - version: Optional[str] + name: str | None + version: str | None """ @@ -105,19 +107,22 @@ def get_ps_font_dict(ps_app: PhotoshopHandler) -> dict[str, str]: Returns: Dictionary with postScriptName as key, display name as value. """ - fonts = {} + fonts: dict[str,str] = {} for f in ps_app.fonts: - with suppress(PS_EXCEPTIONS): + with suppress(*PS_EXCEPTIONS): fonts[f.name] = f.postScriptName return fonts +class FontInfo(TypedDict): + name: str | None + count: int def get_document_fonts( ps_app: PhotoshopHandler, - container: Optional[type[LayerContainer]] = None, - fonts: Optional[dict] = None, - ps_fonts: Optional[dict] = None -) -> dict: + container: LayerSet | Document | None = None, + fonts: dict[str, FontInfo] | None = None, + ps_fonts: dict[str, str] | None = None +) -> dict[str,FontInfo]: """Get a list of all fonts used in a given Photoshop Document or LayerSet. Args: @@ -161,7 +166,7 @@ def get_document_fonts( """ -def get_font_details(path: str) -> Optional[tuple[str, FontDetails]]: +def get_font_details(path: str) -> tuple[str, FontDetails] | None: """Gets the font name and postscript name for a given font file. Args: @@ -170,7 +175,7 @@ def get_font_details(path: str) -> Optional[tuple[str, FontDetails]]: Returns: Tuple containing name and postscript name. """ - with suppress(PS_EXCEPTIONS, TTLibError): + with suppress(*PS_EXCEPTIONS, TTLibError): with TTFont(path) as font: font_name = font['name'].getName(4, 3, 1, 1033).toUnicode() font_postscript = font['name'].getDebugName(6) @@ -220,7 +225,7 @@ def get_installed_fonts_dict() -> dict[str, FontDetails]: def get_outdated_fonts( fonts: dict[str, FontDetails], - missing: Optional[dict[str, FontDetails]] = None + missing: dict[str, FontDetails] | None = None ) -> dict[str, FontDetails]: """Compares the version of each font given against installed fonts. @@ -240,13 +245,21 @@ def get_outdated_fonts( # Check fonts for any outdated for name, data in fonts.items(): if name in installed and installed[name].get('version'): - if parse(installed[name]['version']) < parse(data['version']): + version = data['version'] + installed_version = installed[name]['version'] + if version and installed_version and parse(installed_version) < parse(version): outdated[name] = data # Check missing fonts to see if found in installed dict, if so check for version change for k in list(missing.keys()): if k in installed and installed[k].get('version'): - if parse(installed[k]['version']) < parse(missing[k]['version']): + installed_version = installed[k]['version'] + missing_version = missing[k]['version'] + if ( + installed_version + and missing_version + and parse(installed_version) < parse(missing_version) + ): outdated[k] = missing[k] del missing[k] diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 171ab963..46abb086 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -5,19 +5,20 @@ from contextlib import suppress from functools import cache from pathlib import Path -from typing import Any, Callable, Optional +from typing import Any, ParamSpec, TypeVar +from collections.abc import Callable # Third Party Imports +from pydantic import BaseModel import requests from requests import RequestException from ratelimit import RateLimitDecorator, sleep_and_retry from backoff import on_exception, expo -from omnitils.exceptions import log_on_exception, return_on_exception +from omnitils.exceptions import log_on_exception, return_on_exception, ExceptionLogger from omnitils.fetch import download_file from omnitils.files.archive import unpack_zip from omnitils.files import dump_data_file -from omnitils.schema import Schema -from hexproof.hexapi import schema as Hexproof +from hexproof.hexapi.schema.meta import Meta # Local Imports from src import CON, CONSOLE, PATH @@ -28,14 +29,17 @@ * Types """ +T = TypeVar("T") +P = ParamSpec("P") -class HexproofSet(Schema): + +class HexproofSet(BaseModel): """Cached 'Set' object data from Hexproof.io.""" code_symbol: str - code_parent: Optional[str] = None + code_parent: str | None = None count_cards: int count_tokens: int - count_printed: Optional[int] = None + count_printed: int | None = None """ @@ -53,7 +57,7 @@ class HexproofSet(Schema): """ -def hexproof_request_wrapper(logr: Any = None) -> Callable: +def hexproof_request_wrapper(fallback: T, logr: ExceptionLogger | None = None) -> Callable[[Callable[P,T]], Callable[P, T]]: """Wrapper for a Hexproof.io request function to handle retries, rate limits, and a final exception catch. Args: @@ -64,13 +68,13 @@ def hexproof_request_wrapper(logr: Any = None) -> Callable: """ logr = logr or CONSOLE - def decorator(func): - @return_on_exception({}) + def decorator(func: Callable[P,T]): + @return_on_exception(fallback) @log_on_exception(logr) @sleep_and_retry @hexproof_rate_limit @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) - def wrapper(*args, **kwargs): + def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) return wrapper return decorator @@ -95,7 +99,7 @@ def get_api_key(key: str) -> str: RequestException if request was unsuccessful. """ url = HexURL.API.Keys.All / key - res = requests.get(url, headers=hexproof_http_header, timeout=(3, 3)) + res = requests.get(str(url), headers=hexproof_http_header, timeout=(3, 3)) if res.status_code == 200: return res.json().get('key', '') raise RequestException( @@ -103,8 +107,8 @@ def get_api_key(key: str) -> str: response=res) -@hexproof_request_wrapper() -def get_metadata() -> dict[str, Hexproof.Meta]: +@hexproof_request_wrapper({}) +def get_metadata() -> dict[str, Meta]: """Return a manifest of all resource metadata. Returns: @@ -113,16 +117,16 @@ def get_metadata() -> dict[str, Hexproof.Meta]: Raises: RequestException if request was unsuccessful. """ - res = requests.get(HexURL.API.Meta.All, headers=hexproof_http_header, timeout=(3, 3)) + res = requests.get(str(HexURL.API.Meta.All), headers=hexproof_http_header, timeout=(3, 3)) if res.status_code == 200: - return {k: Hexproof.Meta(**v) for k, v in res.json().items()} + return {k: Meta(**v) for k, v in res.json().items()} raise RequestException( - res.json().get('details', f"Failed to get metadata!"), + res.json().get('details', "Failed to get metadata!"), response=res) -@hexproof_request_wrapper() -def get_sets() -> dict: +@hexproof_request_wrapper({}) +def get_sets() -> dict[str,dict[str,Any]]: """Retrieve the current 'Set' data manifest from https://api.hexproof.io. Returns: @@ -131,11 +135,11 @@ def get_sets() -> dict: Raises: RequestException if request was unsuccessful. """ - res = requests.get(HexURL.API.Sets.All, headers=hexproof_http_header, timeout=(10, 30)) + res = requests.get(str(HexURL.API.Sets.All), headers=hexproof_http_header, timeout=(5, 5)) if res.status_code == 200: return res.json() raise RequestException( - res.json().get('details', f'Failed to get set data!'), + res.json().get('details', 'Failed to get set data!'), response=res) @@ -144,7 +148,7 @@ def get_sets() -> dict: """ -def process_data_sets(data: dict) -> dict[str, HexproofSet]: +def process_data_sets(data: dict[str,dict[str,Any]]) -> dict[str, HexproofSet]: """Process bulk 'Set' data retrieved from the Hexproof API into a smaller dataset. Args: @@ -169,16 +173,16 @@ def process_data_sets(data: dict) -> dict[str, HexproofSet]: """ -def update_hexproof_cache() -> tuple[bool, Optional[str]]: +def update_hexproof_cache() -> tuple[bool, str | None]: """Check for a hexproof.io data update. Returns: tuple: A tuple containing the boolean success state of the update, and a string message explaining the error if one occurred. """ - meta, set_data, updated = {}, {}, False + meta, updated = {}, False with suppress(Exception): - meta: dict[str, Hexproof.Meta] = get_metadata() + meta: dict[str, Meta] = get_metadata() # Check against current metadata _current, _next = CON.metadata.get('sets'), meta.get('sets') @@ -225,7 +229,7 @@ def update_hexproof_cache() -> tuple[bool, Optional[str]]: @cache -def get_set_data(code: str) -> dict: +def get_set_data(code: str) -> dict[str,Any] | None: """Returns a specific 'Set' object by set code. Args: @@ -237,7 +241,7 @@ def get_set_data(code: str) -> dict: return CON.set_data.get(code.lower(), None) -def get_watermark_svg_from_set(code: str) -> Optional[Path]: +def get_watermark_svg_from_set(code: str) -> Path | None: """Look for a watermark SVG in the 'Set' symbol catalog. Args: @@ -262,7 +266,7 @@ def get_watermark_svg_from_set(code: str) -> Optional[Path]: return p if p.is_file() else None -def get_watermark_svg(wm: str) -> Optional[Path]: +def get_watermark_svg(wm: str) -> Path | None: """Look for a watermark SVG in the watermark symbol catalog. If not found, look for a 'set' watermark. Args: diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index a0d0d8c2..767f28da 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -1,15 +1,26 @@ """ * Scryfall API Module """ + # Standard Library Imports from pathlib import Path from shutil import copyfileobj -from typing import Optional, Union, Callable, Any, TypedDict, Literal, NotRequired +from typing import ( + Any, + ParamSpec, + SupportsInt, + TypeVar, + TypedDict, + Literal, + NotRequired, + Unpack, +) +from collections.abc import Sequence, Callable # Third Party Imports from backoff import on_exception, expo from hexproof.scryfall.enums import ScryURL -from omnitils.exceptions import log_on_exception, return_on_exception +from omnitils.exceptions import log_on_exception, return_on_exception, ExceptionLogger from ratelimit import sleep_and_retry, RateLimitDecorator import requests from requests.exceptions import RequestException @@ -24,6 +35,9 @@ * Types """ +T = TypeVar("T") +P = ParamSpec("P") + class ScryfallError(TypedDict): """Error object outlined in Scryfall's API docs. @@ -31,7 +45,8 @@ class ScryfallError(TypedDict): Notes: https://scryfall.com/docs/api/errors """ - object: Literal['error'] + + object: Literal["error"] code: str status: int details: str @@ -54,10 +69,24 @@ class ScryfallError(TypedDict): """ +class ScryfallExceptionKwargs(TypedDict): + exception: NotRequired[Exception | None] + card_name: NotRequired[str | None] + card_set: NotRequired[str | None] + card_number: NotRequired[str | None] + lang: NotRequired[str | None] + + class ScryfallException(RequestException): """Exception representing a failure to retrieve Scryfall data.""" - def __init__(self, **kwargs): + def __init__( + self, + *args: object, + request: requests.Request | requests.PreparedRequest | None = None, + response: requests.Response | None = None, + **kwargs: Unpack[ScryfallExceptionKwargs], + ) -> None: """Allow details relating to the exception to be passed. Keyword Args: @@ -69,31 +98,33 @@ def __init__(self, **kwargs): """ # Check for our kwargs - e = kwargs.pop('exception', None) + exception = kwargs.get("exception", None) params = { - 'Name': kwargs.pop('card_name', None), - 'Set': kwargs.pop('card_set', None), - 'Num': kwargs.pop('card_number', None), - 'Lang': kwargs.pop('lang', None) + "Name": kwargs.get("card_name", None), + "Set": kwargs.get("card_set", None), + "Num": kwargs.get("card_number", None), + "Lang": kwargs.get("lang", None), } # Compile error message - msg = 'Scryfall request failed!' + msg = "Scryfall request failed!" if any(params.values()): # List the params provided - msg += f'\nParams: ' + msg += "\nParams: " p = [f"{k}: '{v}'" for k, v in params.items() if v] - msg += ', '.join(p) - if e and isinstance(e, RequestException) and e.request: + msg += ", ".join(p) + if exception and isinstance(exception, RequestException) and exception.request: # Provide the URL which failed - msg += f'\nAPI URL: {e.request.url}' - if e and isinstance(e, Exception): + msg += f"\nAPI URL: {exception.request.url}" + if exception: # Provide the exception cause - msg += f'\nReason: {str(e)}' + msg += f"\nReason: {exception}" super().__init__(msg) -def scryfall_request_wrapper(logr: Any = None) -> Callable: +def scryfall_request_wrapper( + logger: ExceptionLogger | None = None, +) -> Callable[[Callable[P, T]], Callable[P, T]]: """Wrapper for a Scryfall request function to handle retries, rate limits, and a final exception catch. Args: @@ -102,23 +133,27 @@ def scryfall_request_wrapper(logr: Any = None) -> Callable: Returns: Wrapped function. """ - logr = logr or CONSOLE + logr = logger or CONSOLE - def decorator(func): + def decorator(func: Callable[P, T]): @log_on_exception(logr) - @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) + @on_exception( + expo, requests.exceptions.RequestException, max_tries=2, max_time=1 + ) @sleep_and_retry @scryfall_rate_limit - def wrapper(*args, **kwargs): + def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) + return wrapper + return decorator def get_error( error: ScryfallError, - response: Optional[requests.Response] = None, - **kwargs + response: requests.Response | None = None, + **kwargs: Unpack[ScryfallExceptionKwargs], ) -> ScryfallException: """Returns a ScryfallException object created using data from a ScryfallError object. @@ -130,12 +165,11 @@ def get_error( Returns: A ScryfallException object. """ - msg = error['details'] - if error.get('warnings'): - msg += get_bullet_points(error['warnings'], ' -') - return ScryfallException( - exception=RequestException(msg, response=response), - **kwargs) + msg = error["details"] + if warns := error.get("warnings"): + msg += get_bullet_points(warns, " -") + kwargs["exception"] = RequestException(msg, response=response) + return ScryfallException(**kwargs) """ @@ -145,10 +179,8 @@ def get_error( @scryfall_request_wrapper() def get_card_unique( - card_set: str, - card_number: str, - lang: str = 'en' -) -> Union[dict, ScryfallException]: + card_set: str, card_number: str, lang: str = "en" +) -> dict[str, Any]: """Get card using /cards/:code/:number(/:lang) Scryfall API endpoint. Notes: @@ -164,37 +196,37 @@ def get_card_unique( """ # Establish API pathing url = ScryURL.API.Cards.Main / card_set.lower() / card_number - url = url / lang if lang != 'en' else url + url = url / lang if lang != "en" else url # Track the params - params = { - 'card_set': card_set, - 'card_number': card_number, - 'lang': lang} + params: ScryfallExceptionKwargs = { + "card_set": card_set, + "card_number": card_number, + "lang": lang, + } # Request the data - res = requests.get(url=url, headers=scryfall_http_header) + res = requests.get(url=str(url), headers=scryfall_http_header) card = res.json() # Ensure playable card was returned - if card.get('object' == 'error'): + if card.get("object") == "error": raise get_error(error=card, response=res, **params) - if card.get('object') == 'card' and is_playable_card(card): + if card.get("object") == "card" and is_playable_card(card): return card - raise ScryfallException( - exception=RequestException( - 'No card found with the provided set and number.', - response=res), - **params) + params["exception"] = RequestException( + "No card found with the provided set and number.", response=res + ) + raise ScryfallException(**params) @scryfall_request_wrapper() def get_card_search( card_name: str, - card_set: Optional[str] = None, - lang: str = 'en', - **kwargs -) -> Union[dict, ScryfallException]: + card_set: str | None = None, + lang: str = "en", + **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], +) -> dict[str, Any]: """Get card using /cards/search Scryfall API endpoint. Notes: @@ -220,41 +252,47 @@ def get_card_search( """ # Query Scryfall res = requests.get( - url=ScryURL.API.Cards.Search.with_query({ - 'q': f'!"{card_name}"' - f' lang:{lang}' - f"{f' set:{card_set.lower()}' if card_set else ''}", - **kwargs - }), headers=scryfall_http_header) + url=str( + ScryURL.API.Cards.Search.with_query( + { + "q": f'!"{card_name}"' + f" lang:{lang}" + f"{f' set:{card_set.lower()}' if card_set else ''}", + **kwargs, + } + ) + ), + headers=scryfall_http_header, + ) data = res.json() # Check for a playable card - if data.get('object') == 'error': + if data.get("object") == "error": raise get_error( - error=data, response=res, **{ - 'card_name': card_name, 'card_set': card_set, 'lang': lang - }) - for c in data.get('data', []): + error=data, response=res, card_name=card_name, card_set=card_set, lang=lang + ) + for c in data.get("data", []): if is_playable_card(c): return c # No playable results - return ScryfallException( + raise ScryfallException( exception=RequestException( - 'No card found with the provided search terms.', - response=res), - name=card_name, - code=card_set, - lang=lang) + "No card found with the provided search terms.", response=res + ), + card_name=card_name, + card_set=card_set, + lang=lang, + ) @scryfall_request_wrapper() @return_on_exception([]) def get_cards_paged( - url: Union[yarl.URL, ScryURL, None] = None, + url: yarl.URL | None = None, all_pages: bool = True, - **kwargs -) -> list[dict]: + **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], +) -> list[dict[str, Any]]: """Grab paginated card list from a Scryfall API endpoint. Args: @@ -266,27 +304,27 @@ def get_cards_paged( url = url or ScryURL.API.Cards.Search # Query Scryfall - req = requests.get(url=url.with_query(kwargs), headers=scryfall_http_header) + req = requests.get(url=str(url.with_query(kwargs)), headers=scryfall_http_header) res = req.json() - cards = res.get('data', []) + cards = res.get("data", []) # Check for an error object - if res.get('object') == 'error': + if res.get("object") == "error": raise get_error(error=res, response=req) # Add additional pages if any exist if all_pages and res.get("has_more") and res.get("next_page"): - cards.extend( - get_cards_paged( - url=res.get['next_page'], - all_pages=all_pages - )) + cards.extend(get_cards_paged(url=res.get["next_page"], all_pages=all_pages)) return cards @scryfall_request_wrapper() @return_on_exception([]) -def get_cards_oracle(oracle_id: str, all_pages: bool = False, **kwargs) -> list[dict]: +def get_cards_oracle( + oracle_id: str, + all_pages: bool = False, + **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], +) -> list[dict[str, Any]]: """Grab paginated card list from a Scryfall API endpoint using the Oracle ID of the card. Args: @@ -300,13 +338,12 @@ def get_cards_oracle(oracle_id: str, all_pages: bool = False, **kwargs) -> list[ return get_cards_paged( url=ScryURL.API.Cards.Search, all_pages=all_pages, - **{ - 'q': f'oracleid:{oracle_id}', - 'dir': kwargs.pop('dir', 'asc'), - 'order': kwargs.pop('order', 'released'), - 'unique': kwargs.pop('unique', 'prints'), - **kwargs - }) + q=f"oracleid:{oracle_id}", + dir=kwargs.pop("dir", "asc"), + order=kwargs.pop("order", "released"), + unique=kwargs.pop("unique", "prints"), + **kwargs, + ) """ @@ -316,7 +353,7 @@ def get_cards_oracle(oracle_id: str, all_pages: bool = False, **kwargs) -> list[ @scryfall_request_wrapper() @return_on_exception({}) -def get_set(card_set: str) -> dict: +def get_set(card_set: str) -> dict[str, Any]: """Grab Set data from Scryfall. Args: @@ -327,13 +364,14 @@ def get_set(card_set: str) -> dict: """ # Make the request res = requests.get( - ScryURL.API.Cards.Search.SCRY_SETS / card_set.upper(), - headers=scryfall_http_header) + str(ScryURL.API.Sets.All / card_set.upper()), + headers=scryfall_http_header, + ) data = res.json() # Check for an error object - if data.get('object') == 'error': - raise get_error(error=data, response=res, **{'card_set': card_set}) + if data.get("object") == "error": + raise get_error(error=data, response=res, **{"card_set": card_set}) return data or {} @@ -344,7 +382,10 @@ def get_set(card_set: str) -> dict: @scryfall_request_wrapper() @return_on_exception({}) -def get_uri_object(url: yarl.URL, **kwargs) -> dict: +def get_uri_object( + url: yarl.URL, + **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], +) -> dict[str, Any]: """Pull a single object from Scryfall using a URI from a previous Scryfall data set. Args: @@ -353,17 +394,17 @@ def get_uri_object(url: yarl.URL, **kwargs) -> dict: Returns: A Scryfall object, e.g. Card, Set, etc. """ - res = requests.get(url.with_query(kwargs), headers=scryfall_http_header) + res = requests.get(str(url.with_query(kwargs)), headers=scryfall_http_header) data = res.json() # Check for error object - if data.get('object') == 'error': + if data.get("object") == "error": raise get_error(error=data, response=res) return data @scryfall_request_wrapper() -@return_on_exception() +@return_on_exception(None) def get_card_scan(img_url: str) -> Path: """Downloads scryfall art from URL @@ -378,10 +419,8 @@ def get_card_scan(img_url: str) -> Path: """ res = requests.get(img_url, stream=True) if res.status_code != 200: - raise RequestException( - "Couldn't retrieve image from scryfall.", - response=res) - with open(PATH.LOGS_SCAN, 'wb') as f: + raise RequestException("Couldn't retrieve image from scryfall.", response=res) + with open(PATH.LOGS_SCAN, "wb") as f: copyfileobj(res.raw, f) return PATH.LOGS_SCAN @@ -391,7 +430,7 @@ def get_card_scan(img_url: str) -> Path: """ -def is_playable_card(card_json: dict) -> bool: +def is_playable_card(card_json: dict[str, Any]) -> bool: """Checks if this card object is a playable game piece. Args: @@ -400,14 +439,16 @@ def is_playable_card(card_json: dict) -> bool: Returns: Valid scryfall data if check passed, else None. """ - if card_json.get('set_type') in ["minigame"]: + if card_json.get("set_type") in ["minigame"]: # Ignore minigame insert cards return False - if card_json.get('layout') in ['art_series', 'reversible_card']: + if card_json.get("layout") in ["art_series", "reversible_card"]: # Ignore art series and reversible cards # TODO: Implement support for reversible return False - if card_json.get('set_type') in ['memorabilia'] and '(Theme)' in card_json.get('name', ''): + if card_json.get("set_type") in ["memorabilia"] and "(Theme)" in card_json.get( + "name", "" + ): # Ignore theme insert cards (Jumpstart) return False return True From 56546b5a9a73ed452aeb2e720b668126b36106d8 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 24 Aug 2025 12:31:52 +0300 Subject: [PATCH 028/190] feat(pyproject.toml): Update dependencies and make the configuration more PEP 621 compliant --- poetry.lock | 4098 +++++++++++++++++++++++++----------------------- pyproject.toml | 108 +- 2 files changed, 2179 insertions(+), 2027 deletions(-) diff --git a/poetry.lock b/poetry.lock index ba664f87..de901a3f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "aggdraw" @@ -62,14 +62,14 @@ files = [ [[package]] name = "argcomplete" -version = "3.5.1" +version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, - {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, + {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, + {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, ] [package.extras] @@ -77,68 +77,65 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "asyncgui" -version = "0.7.0" -description = "A thin layer that helps to wrap a callback-style API in an async/await-style API" +version = "0.9.3" +description = "A minimalistic async library that focuses on fast responsiveness" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "asyncgui-0.7.0-py3-none-any.whl", hash = "sha256:c1e53b5c0c4df8817b28aa58bda3bc13752ea65519b14c2d7fa1c1721c9bb362"}, - {file = "asyncgui-0.7.0.tar.gz", hash = "sha256:6a5cfdfee34f6478e857a55ff88981995479930c3ddc577c5ff9d85a79f12a4a"}, + {file = "asyncgui-0.9.3-py3-none-any.whl", hash = "sha256:ed73fc00c68d7e0d804eb239f2ad9c319dce57a5d31342c19aadc18f44270103"}, + {file = "asyncgui-0.9.3.tar.gz", hash = "sha256:46b283946a1814f600b502ee48082be9681097a72d8d011305e453d683a9064b"}, ] -[package.dependencies] -exceptiongroup = {version = ">=1.0.4,<2.0.0", markers = "python_version < \"3.11\""} - [[package]] name = "asynckivy" -version = "0.7.0" +version = "0.9.0" description = "Async library for Kivy" optional = false python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "asynckivy-0.7.0-py3-none-any.whl", hash = "sha256:4855d730ff3283203dcb325bc4018301737a307732f1075b6ef61c2db590bd10"}, - {file = "asynckivy-0.7.0.tar.gz", hash = "sha256:83396cecceb513393040b40a3cf6d920ed6190e78660aae5edf48c9d3fd1140b"}, + {file = "asynckivy-0.9.0-py3-none-any.whl", hash = "sha256:cfc9e3f606dfb95fcd9611ee7be69e1da3ce6974679fbb61a6250e3a43ae3d0c"}, + {file = "asynckivy-0.9.0.tar.gz", hash = "sha256:6358dbe49bffa3be8507395a256789e41e234cda8df5baa39747d66aae1f92e6"}, ] [package.dependencies] -asyncgui = ">=0.7,<0.8" +asyncgui = ">=0.9.3,<0.10" [[package]] name = "attrs" -version = "24.2.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "babel" -version = "2.16.0" +version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "backoff" @@ -152,20 +149,41 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "backrefs" +version = "5.9" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, +] + +[package.extras] +extras = ["regex"] + [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -176,14 +194,14 @@ lxml = ["lxml"] [[package]] name = "bracex" -version = "2.5.post1" +version = "2.6" description = "Bash style brace expander." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, - {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, + {file = "bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952"}, + {file = "bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7"}, ] [[package]] @@ -380,14 +398,14 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2024.8.30" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] @@ -485,129 +503,103 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [package.dependencies] @@ -628,122 +620,130 @@ markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "commitizen" -version = "3.30.0" +version = "4.8.3" description = "Python commitizen client tool" optional = false -python-versions = ">=3.8" +python-versions = "<4.0,>=3.9" groups = ["dev"] files = [ - {file = "commitizen-3.30.0-py3-none-any.whl", hash = "sha256:8dc226a136aee61207e396101fcd89e73de67a57c06e066db982310863caaf65"}, - {file = "commitizen-3.30.0.tar.gz", hash = "sha256:ae67a47c1a700b4f35ac12de0c35c7ba96f152b9377d22b6226bb87372c527b0"}, + {file = "commitizen-4.8.3-py3-none-any.whl", hash = "sha256:91f261387ca2bbb4ab6c79a1a6378dc1576ffb40e3b7dbee201724d95aceba38"}, + {file = "commitizen-4.8.3.tar.gz", hash = "sha256:303ebdc271217aadbb6a73a015612121291d180c8cdd05b5251c7923d4a14195"}, ] [package.dependencies] -argcomplete = ">=1.12.1,<3.6" +argcomplete = ">=1.12.1,<3.7" charset-normalizer = ">=2.1.0,<4" -colorama = ">=0.4.1,<0.5.0" -decli = ">=0.6.0,<0.7.0" +colorama = ">=0.4.1,<1.0" +decli = ">=0.6.0,<1.0" +importlib-metadata = {version = ">=8.0.0,<9.0.0", markers = "python_version != \"3.9\""} jinja2 = ">=2.10.3" packaging = ">=19" pyyaml = ">=3.08" questionary = ">=2.0,<3.0" -termcolor = ">=1.1,<3" +termcolor = ">=1.1.0,<4.0.0" tomlkit = ">=0.5.3,<1.0.0" [[package]] name = "comtypes" -version = "1.4.8" +version = "1.4.11" description = "Pure Python COM package" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "comtypes-1.4.8-py3-none-any.whl", hash = "sha256:773109b12aa0bec630d5b2272dd983cbaa25605a12fc1319f99730c9d0b72f79"}, - {file = "comtypes-1.4.8.zip", hash = "sha256:bb2286cfb3b96f838307200a85b00b98e1bdebf1e58ec3c28b36b1ccfafac01f"}, + {file = "comtypes-1.4.11-py3-none-any.whl", hash = "sha256:1760d5059ca7ca1d61b574c998378d879c271a86c41f88926619ea97497592bb"}, + {file = "comtypes-1.4.11.zip", hash = "sha256:0a4259370ec48b685fe4483b0944ba1df0aa45163922073fe9b7df1d187db09e"}, ] [[package]] name = "contourpy" -version = "1.3.0" +version = "1.3.3" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, - {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, - {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, - {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, - {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, - {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, - {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, - {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, - {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, - {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, - {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, - {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, - {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, - {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, - {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, - {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, - {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, - {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, - {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, - {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, - {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, - {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, - {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, - {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, - {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, - {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, - {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, - {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, - {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, - {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, - {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, - {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, ] [package.dependencies] -numpy = ">=1.23" +numpy = ">=1.25" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] @@ -776,26 +776,26 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "decli" -version = "0.6.2" +version = "0.6.3" description = "Minimal, easy-to-use, declarative cli tool" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed"}, - {file = "decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f"}, + {file = "decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3"}, + {file = "decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656"}, ] [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] @@ -811,26 +811,26 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.22" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e"}, + {file = "docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f"}, ] [[package]] name = "dynaconf" -version = "3.2.6" +version = "3.2.11" description = "The dynamic configurator for your Python Project" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "dynaconf-3.2.6-py2.py3-none-any.whl", hash = "sha256:3911c740d717df4576ed55f616c7cbad6e06bc8ef23ffca444b6e2a12fb1c34c"}, - {file = "dynaconf-3.2.6.tar.gz", hash = "sha256:74cc1897396380bb957730eb341cc0976ee9c38bbcb53d3307c50caed0aedfb8"}, + {file = "dynaconf-3.2.11-py2.py3-none-any.whl", hash = "sha256:660de90879d4da236f79195692a7d197957224d7acf922bcc6899187dc7b4a27"}, + {file = "dynaconf-3.2.11.tar.gz", hash = "sha256:4cfc6a730c533bf1a1d0bf266ae202133a22236bb3227d23eff4b8542d4034a5"}, ] [package.dependencies] @@ -847,98 +847,99 @@ vault = ["hvac"] yaml = ["ruamel.yaml"] [[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" +name = "filelock" +version = "3.19.1" +description = "A platform independent file lock." optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version == \"3.10\"" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -test = ["pytest (>=6)"] - [[package]] -name = "filelock" -version = "3.16.1" -description = "A platform independent file lock." +name = "filetype" +version = "1.2.0" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = "*" +groups = ["main"] files = [ - {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, - {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, + {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, + {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] - [[package]] name = "fonttools" -version = "4.54.1" +version = "4.59.1" description = "Tools to manipulate font files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, - {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, - {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, - {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, - {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, - {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, - {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, - {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, - {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, - {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, - {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, - {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, - {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, - {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, - {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, - {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, - {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, - {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, + {file = "fonttools-4.59.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e90a89e52deb56b928e761bb5b5f65f13f669bfd96ed5962975debea09776a23"}, + {file = "fonttools-4.59.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d29ab70658d2ec19422b25e6ace00a0b0ae4181ee31e03335eaef53907d2d83"}, + {file = "fonttools-4.59.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f9721a564978a10d5c12927f99170d18e9a32e5a727c61eae56f956a4d118b"}, + {file = "fonttools-4.59.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c8758a7d97848fc8b514b3d9b4cb95243714b2f838dde5e1e3c007375de6214"}, + {file = "fonttools-4.59.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2aeb829ad9d41a2ef17cab8bb5d186049ba38a840f10352e654aa9062ec32dc1"}, + {file = "fonttools-4.59.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac216a2980a2d2b3b88c68a24f8a9bfb203e2490e991b3238502ad8f1e7bfed0"}, + {file = "fonttools-4.59.1-cp310-cp310-win32.whl", hash = "sha256:d31dc137ed8ec71dbc446949eba9035926e6e967b90378805dcf667ff57cabb1"}, + {file = "fonttools-4.59.1-cp310-cp310-win_amd64.whl", hash = "sha256:5265bc52ed447187d39891b5f21d7217722735d0de9fe81326566570d12851a9"}, + {file = "fonttools-4.59.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4909cce2e35706f3d18c54d3dcce0414ba5e0fb436a454dffec459c61653b513"}, + {file = "fonttools-4.59.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbec204fa9f877641747f2d9612b2b656071390d7a7ef07a9dbf0ecf9c7195c"}, + {file = "fonttools-4.59.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39dfd42cc2dc647b2c5469bc7a5b234d9a49e72565b96dd14ae6f11c2c59ef15"}, + {file = "fonttools-4.59.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b11bc177a0d428b37890825d7d025040d591aa833f85f8d8878ed183354f47df"}, + {file = "fonttools-4.59.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b9b4c35b3be45e5bc774d3fc9608bbf4f9a8d371103b858c80edbeed31dd5aa"}, + {file = "fonttools-4.59.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:01158376b8a418a0bae9625c476cebfcfcb5e6761e9d243b219cd58341e7afbb"}, + {file = "fonttools-4.59.1-cp311-cp311-win32.whl", hash = "sha256:cf7c5089d37787387123f1cb8f1793a47c5e1e3d1e4e7bfbc1cc96e0f925eabe"}, + {file = "fonttools-4.59.1-cp311-cp311-win_amd64.whl", hash = "sha256:c866eef7a0ba320486ade6c32bfc12813d1a5db8567e6904fb56d3d40acc5116"}, + {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43ab814bbba5f02a93a152ee61a04182bb5809bd2bc3609f7822e12c53ae2c91"}, + {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4f04c3ffbfa0baafcbc550657cf83657034eb63304d27b05cff1653b448ccff6"}, + {file = "fonttools-4.59.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d601b153e51a5a6221f0d4ec077b6bfc6ac35bfe6c19aeaa233d8990b2b71726"}, + {file = "fonttools-4.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c735e385e30278c54f43a0d056736942023c9043f84ee1021eff9fd616d17693"}, + {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1017413cdc8555dce7ee23720da490282ab7ec1cf022af90a241f33f9a49afc4"}, + {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c6d8d773470a5107052874341ed3c487c16ecd179976d81afed89dea5cd7406"}, + {file = "fonttools-4.59.1-cp312-cp312-win32.whl", hash = "sha256:2a2d0d33307f6ad3a2086a95dd607c202ea8852fa9fb52af9b48811154d1428a"}, + {file = "fonttools-4.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b9e4fa7eaf046ed6ac470f6033d52c052481ff7a6e0a92373d14f556f298dc0"}, + {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:89d9957b54246c6251345297dddf77a84d2c19df96af30d2de24093bbdf0528b"}, + {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8156b11c0d5405810d216f53907bd0f8b982aa5f1e7e3127ab3be1a4062154ff"}, + {file = "fonttools-4.59.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8387876a8011caec52d327d5e5bca705d9399ec4b17afb8b431ec50d47c17d23"}, + {file = "fonttools-4.59.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb13823a74b3a9204a8ed76d3d6d5ec12e64cc5bc44914eb9ff1cdac04facd43"}, + {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e1ca10da138c300f768bb68e40e5b20b6ecfbd95f91aac4cc15010b6b9d65455"}, + {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2beb5bfc4887a3130f8625349605a3a45fe345655ce6031d1bac11017454b943"}, + {file = "fonttools-4.59.1-cp313-cp313-win32.whl", hash = "sha256:419f16d750d78e6d704bfe97b48bba2f73b15c9418f817d0cb8a9ca87a5b94bf"}, + {file = "fonttools-4.59.1-cp313-cp313-win_amd64.whl", hash = "sha256:c536f8a852e8d3fa71dde1ec03892aee50be59f7154b533f0bf3c1174cfd5126"}, + {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d5c3bfdc9663f3d4b565f9cb3b8c1efb3e178186435b45105bde7328cfddd7fe"}, + {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ea03f1da0d722fe3c2278a05957e6550175571a4894fbf9d178ceef4a3783d2b"}, + {file = "fonttools-4.59.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57a3708ca6bfccb790f585fa6d8f29432ec329618a09ff94c16bcb3c55994643"}, + {file = "fonttools-4.59.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:729367c91eb1ee84e61a733acc485065a00590618ca31c438e7dd4d600c01486"}, + {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f8ef66ac6db450193ed150e10b3b45dde7aded10c5d279968bc63368027f62b"}, + {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:075f745d539a998cd92cb84c339a82e53e49114ec62aaea8307c80d3ad3aef3a"}, + {file = "fonttools-4.59.1-cp314-cp314-win32.whl", hash = "sha256:c2b0597522d4c5bb18aa5cf258746a2d4a90f25878cbe865e4d35526abd1b9fc"}, + {file = "fonttools-4.59.1-cp314-cp314-win_amd64.whl", hash = "sha256:e9ad4ce044e3236f0814c906ccce8647046cc557539661e35211faadf76f283b"}, + {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:652159e8214eb4856e8387ebcd6b6bd336ee258cbeb639c8be52005b122b9609"}, + {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:43d177cd0e847ea026fedd9f099dc917da136ed8792d142298a252836390c478"}, + {file = "fonttools-4.59.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e54437651e1440ee53a95e6ceb6ee440b67a3d348c76f45f4f48de1a5ecab019"}, + {file = "fonttools-4.59.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6065fdec8ff44c32a483fd44abe5bcdb40dd5e2571a5034b555348f2b3a52cea"}, + {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42052b56d176f8b315fbc09259439c013c0cb2109df72447148aeda677599612"}, + {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bcd52eaa5c4c593ae9f447c1d13e7e4a00ca21d755645efa660b6999425b3c88"}, + {file = "fonttools-4.59.1-cp314-cp314t-win32.whl", hash = "sha256:02e4fdf27c550dded10fe038a5981c29f81cb9bc649ff2eaa48e80dab8998f97"}, + {file = "fonttools-4.59.1-cp314-cp314t-win_amd64.whl", hash = "sha256:412a5fd6345872a7c249dac5bcce380393f40c1c316ac07f447bc17d51900922"}, + {file = "fonttools-4.59.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ab4c1fb45f2984b8b4a3face7cff0f67f9766e9414cbb6fd061e9d77819de98"}, + {file = "fonttools-4.59.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ee39da0227950f88626c91e219659e6cd725ede826b1c13edd85fc4cec9bbe6"}, + {file = "fonttools-4.59.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58a8844f96cff35860647a65345bfca87f47a2494bfb4bef754e58c082511443"}, + {file = "fonttools-4.59.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f021cea6e36410874763f4a517a5e2d6ac36ca8f95521f3a9fdaad0fe73dc"}, + {file = "fonttools-4.59.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf5fb864f80061a40c1747e0dbc4f6e738de58dd6675b07eb80bd06a93b063c4"}, + {file = "fonttools-4.59.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c29ea087843e27a7cffc78406d32a5abf166d92afde7890394e9e079c9b4dbe9"}, + {file = "fonttools-4.59.1-cp39-cp39-win32.whl", hash = "sha256:a960b09ff50c2e87864e83f352e5a90bcf1ad5233df579b1124660e1643de272"}, + {file = "fonttools-4.59.1-cp39-cp39-win_amd64.whl", hash = "sha256:e3680884189e2b7c3549f6d304376e64711fd15118e4b1ae81940cb6b1eaa267"}, + {file = "fonttools-4.59.1-py3-none-any.whl", hash = "sha256:647db657073672a8330608970a984d51573557f328030566521bc03415535042"}, + {file = "fonttools-4.59.1.tar.gz", hash = "sha256:74995b402ad09822a4c8002438e54940d9f1ecda898d2bb057729d7da983e4cb"}, ] [package.extras] -all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0)"] @@ -947,7 +948,6 @@ plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr ; sys_platform == \"darwin\""] -ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] @@ -971,14 +971,14 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "gitdb" -version = "4.0.11" +version = "4.0.12" description = "Git Object Database" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, ] [package.dependencies] @@ -986,33 +986,33 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.43" +version = "3.1.45" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, - {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, + {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, + {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "griffe" -version = "1.5.1" +version = "1.12.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860"}, - {file = "griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b"}, + {file = "griffe-1.12.1-py3-none-any.whl", hash = "sha256:2d7c12334de00089c31905424a00abcfd931b45b8b516967f224133903d302cc"}, + {file = "griffe-1.12.1.tar.gz", hash = "sha256:29f5a6114c0aeda7d9c86a570f736883f8a2c5b38b57323d56b3d1c000565567"}, ] [package.dependencies] @@ -1023,22 +1023,26 @@ name = "hexproof" version = "0.3.7" description = "A comprehensive library of Magic the Gathering API utilities." optional = false -python-versions = "<3.13,>=3.10" +python-versions = ">=3.10,<4.0" groups = ["main"] -files = [ - {file = "hexproof-0.3.7-py3-none-any.whl", hash = "sha256:bdfbe1d6107091ea5c74421af26f35874e5e36da050da7b3b2791dc78bec14fb"}, - {file = "hexproof-0.3.7.tar.gz", hash = "sha256:71a3db263c81ef2ccbce084a6b7ce2e78f1d35645f6204208773fa7f97c0eccf"}, -] +files = [] +develop = false [package.dependencies] -bs4 = ">=0.0.2,<0.0.3" -loguru = ">=0.7.2,<0.8.0" -omnitils = ">=1.4.3,<2.0.0" -pydantic = ">=2.9.2" -PyYAML = ">=6.0.1" -requests = ">=2.31.0,<3.0.0" -tomli = ">=2.0.2" -tomlkit = ">=0.13.2" +bs4 = "^0.0.2" +loguru = "^0.7.3" +omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} +pydantic = ">=2.11.7" +PyYAML = ">=6.0.2" +requests = "^2.32.4" +tomli = ">=2.2.1" +tomlkit = ">=0.13.3" + +[package.source] +type = "git" +url = "https://github.com/pappnu/hexproof.git" +reference = "dev" +resolved_reference = "4c56905b6f412d2266f0b0d7b6962f5829a19446" [[package]] name = "htmlmin2" @@ -1053,14 +1057,14 @@ files = [ [[package]] name = "identify" -version = "2.6.1" +version = "2.6.13" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, ] [package.extras] @@ -1083,14 +1087,14 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "imageio" -version = "2.36.0" +version = "2.37.0" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "imageio-2.36.0-py3-none-any.whl", hash = "sha256:471f1eda55618ee44a3c9960911c35e647d9284c68f077e868df633398f137f0"}, - {file = "imageio-2.36.0.tar.gz", hash = "sha256:1c8f294db862c256e9562354d65aa54725b8dafed7f10f02bb3ec20ec1678850"}, + {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"}, + {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"}, ] [package.dependencies] @@ -1115,96 +1119,108 @@ rawpy = ["numpy (>2)", "rawpy"] test = ["fsspec[github]", "pytest", "pytest-cov"] tifffile = ["tifffile"] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + [[package]] name = "inflate64" -version = "1.0.0" +version = "1.0.1" description = "deflate64 compression/decompression library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a90c0bdf4a7ecddd8a64cc977181810036e35807f56b0bcacee9abb0fcfd18dc"}, - {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe7c14aebf1c5a74fc3b70d355be1280a011521a76aa3895486e62454f4242"}, - {file = "inflate64-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d90730165f471d61a1a694a5e354f3ffa938227e8dcecb62d5d728e8069cee94"}, - {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543f400201f5c101141af3c79c82059e1aa6ef4f1584a7f1fa035fb2e465097f"}, - {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ceca14f7ec19fb44b047f56c50efb7521b389d222bba2b0a10286a0caeb03fa"}, - {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b559937a42f0c175b4d2dfc7eb53b97bdc87efa9add15ed5549c6abc1e89d02f"}, - {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ff8bd2a562343fcbc4eea26fdc368904a3b5f6bb8262344274d3d74a1de15bb"}, - {file = "inflate64-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0fe481f31695d35a433c3044ac8fd5d9f5069aaad03a0c04b570eb258ce655aa"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a45f6979ad5874d4d4898c2fc770b136e61b96b850118fdaec5a5af1b9123a"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:022ca1cc928e7365a05f7371ff06af143c6c667144965e2cf9a9236a2ae1c291"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46792ecf3565d64fd2c519b0a780c03a57e195613c9954ef94e739a057b3fd06"}, - {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a70ea2e456c15f7aa7c74b8ab8f20b4f8940ec657604c9f0a9de3342f280fff"}, - {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e243ea9bd36a035059f2365bd6d156ff59717fbafb0255cb0c75bf151bf6904"}, - {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4dc392dec1cd11cacda3d2637214ca45e38202e8a4f31d4a4e566d6e90625fc4"}, - {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8b402a50eda7ee75f342fc346d33a41bca58edc222a4b17f9be0db1daed459fa"}, - {file = "inflate64-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f5924499dc8800928c0ee4580fa8eb4ffa880b2cce4431537d0390e503a9c9ee"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0c644bf7208e20825ca3bbb5fb1f7f495cfcb49eb01a5f67338796d44a42f2bf"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9964a4eaf26a9d36f82a1d9b12c28e35800dd3d99eb340453ed12ac90c2976a8"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2cccded63865640d03253897be7232b2bbac295fe43914c61f86a57aa23bb61d"}, - {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d491f104fb3701926ebd82b8c9250dfba0ddcab584504e26f1e4adb26730378d"}, - {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ebad4a6cd2a2c1d81be0b09d4006479f3b258803c49a9224ef8ca0b649072fa"}, - {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6823b2c0cff3a8159140f3b17ec64fb8ec0e663b45a6593618ecdde8aeecb5b2"}, - {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:228d504239d27958e71fc77e3119a6ac4528127df38468a0c95a5bd3927204b8"}, - {file = "inflate64-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae2572e06bcfe15e3bbf77d4e4a6d6c55e2a70d6abceaaf60c5c3653ddb96dfd"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c10ca61212a753bbce6d341e7cfa779c161b839281f1f9fdc15cf1f324ce7c5b"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a982dc93920f9450da4d4f25c5e5c1288ef053b1d618cedc91adb67e035e35f5"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ca0310b2c55bc40394c5371db2a22f705fd594226cc09432e1eb04d3aed83930"}, - {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e95044ae55a161144445527a2efad550851fecc699066423d24b2634a6a83710"}, - {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34de6902c39d9225459583d5034182d371fc694bc3cfd6c0fc89aa62e9809faf"}, - {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ebafbd813213dc470719cd0a2bcb53aab89d9059f4e75386048b4c4dcdb2fd99"}, - {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75448c7b414dadaeeb11dab9f75e022aa1e0ee19b00f570e9f58e933603d71ac"}, - {file = "inflate64-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:2be4e01c1b04761874cb44b35b6103ca5846bc36c18fc3ff5e8cbcd8bfc15e9f"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf2981b95c1f26242bb084d9a07f3feb0cfe3d6d0a8d90f42389803bc1252c4a"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9373ccf0661cc72ac84a0ad622634144da5ce7d57c9572ed0723d67a149feed2"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4650c6f65011ec57cf5cd96b92d5b7c6f59e502930c86eb8227c93cf02dc270"}, - {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a475e8822f1a74c873e60b8f270773757ade024097ca39e43402d47c049c67d4"}, - {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4367480733ac8daf368f6fc704b7c9db85521ee745eb5bd443f4b97d2051acc"}, - {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c5775c91f94f5eced9160fb0af12a09f3e030194f91a6a46e706a79350bd056"}, - {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d76d205b844d78ce04768060084ef20e64dcc63a3e9166674f857acaf4d140ed"}, - {file = "inflate64-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f0dc6af0e8e97324981178dc442956cbff1247a56d1e201af8d865244653f8"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f79542478e49e471e8b23556700e6f688a40dc93e9a746f77a546c13251b59b1"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a270be6b10cde01258c0097a663a307c62d12c78eb8f62f8e29f205335942c9"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1616a87ff04f583e9558cc247ec0b72a30d540ee0c17cc77823be175c0ec92f0"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:137ca6b315f0157a786c3a755a09395ca69aed8bcf42ad3437cb349f5ebc86d2"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8140942d1614bdeb5a9ddd7559348c5c77f884a42424aef7ccf149ccfb93aa08"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe3f9051338bb7d07b5e7d88420d666b5109f33ae39aa55ecd1a053c0f22b1b"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36342338e957c790fc630d4afcdcc3926beb2ecaea0b302336079e8fa37e57a0"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9b65cc701ef33ab20dbfd1d64088ffd89a8c265b356d2c21ba0ec565661645ef"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd6d3e7d47df43210a995fd1f5989602b64de3f2a17cf4cbff553518b3577fd4"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f033b2879696b855200cde5ca4e293132c7499df790acb2c0dacb336d5e83b1"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f816d1c8a0593375c289e285c96deaee9c2d8742cb0edbd26ee05588a9ae657"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1facd35319b6a391ee4c3d709c7c650bcada8cd7141d86cd8c2257287f45e6e6"}, - {file = "inflate64-1.0.0.tar.gz", hash = "sha256:3278827b803cf006a1df251f3e13374c7d26db779e5a33329cc11789b804bc2d"}, + {file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5122a188995e47a735ab969edc9129d42bbd97b993df5a3f0819b87205ce81b4"}, + {file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:975ed694c680e46a5c0bb872380a9c9da271a91f9c0646561c58e8f3714347d4"}, + {file = "inflate64-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bcaf445d9cda5f7358e0c2b78144641560f8ce9e3e4351099754c49d26a34e8"}, + {file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daede09baba24117279109b30fdf935195e91957e31b995b86f8dd01711376ee"}, + {file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df40eaaba4fb8379d5c4fa5f56cc24741c4f1a91d4aef66438207473351ceaa"}, + {file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ef90855ff63d53c8fd3bfbf85b5280b22f82b9ab2e21a7eee45b8a19d9866c42"}, + {file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5daa4566c0b009c9ab8a6bf18ce407d14f5dbbb0d3068f3a43af939a17e117a7"}, + {file = "inflate64-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:d58a360b59685561a8feacee743479a9d7cc17c8d210aa1f2ae221f2513973cb"}, + {file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31198c5f156806cee05b69b149074042b7b7d39274ff4c259b898e617294ac17"}, + {file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ab693bb1cd92573a997f8fe7b90a2ec1e17a507884598f5640656257b95ef49"}, + {file = "inflate64-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:95b6a60e305e6e759e37d6c36691fcb87678922c56b3ddc2df06cd56e04f41f6"}, + {file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:711ef889bdb3b3b296881d1e49830a3a896938fba7033c4287f1aed9b9a20111"}, + {file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3178495970ecb5c6a32167a8b57fdeef3bf4e2843eaf8f2d8f816f523741e36"}, + {file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e8373b7feedf10236eb56d21598a19a3eb51077c3702d0ce3456b827374025e1"}, + {file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf026d5c885f2d2bbf233e9a0c8c6d046ec727e2467024ffe0ac76b5be308258"}, + {file = "inflate64-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:3aa7489241e6c6f6d34b9561efdf06031c35305b864267a5b8f406abcd3e85c5"}, + {file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b81b3d373190ecd82901f42afd90b7127e9bdef341032a94db381c750ed3ddb2"}, + {file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbfddc5dac975227c20997f0ac515917a15421767c6bff0c209ac6ff9d7b17cc"}, + {file = "inflate64-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2adeabe79cc2f90bca832673520c8cbad7370f86353e151293add7ca529bed34"}, + {file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b235c97a05dbe2f92f0f057426e4d05a449e1fccf8e9aa88075ea9c6a06a182"}, + {file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b74e30734dca5f1c83ca07074e1f25bf7b63f4a5ee7e074d9a4cb05af65cd5"}, + {file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b298feb85204b5ef148ccf807744c836fffed7c1ed3ec8bc9b4e323a03163291"}, + {file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a4c75241bc442267f79b8242135f2ded29405662c44b9353d34fbd4fa6e56b3"}, + {file = "inflate64-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:7b210392f0830ab27371e36478592f47757f5ea6c09ddb96e2125847b309eb5e"}, + {file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8dd58aa1adc4f98bf9b52baffa8f2ddf589e071a90db2f2bec9024328d4608cf"}, + {file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c108be2b87e88c966570f84f839eb37f489b45dc3fa3046dc228327af6e921bb"}, + {file = "inflate64-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63971c6b096c0d533c0e38b4257f5a7748501a8bc04d00cf239bd06467888703"}, + {file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d0077edb6b1cabfa2223b71a4a725e5755148f551a7a396c7d5698e45fb8828"}, + {file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05b5f2a6f1bf2f70e9c20d997261711cbc1ae477379662b05b36911da60a67"}, + {file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c7402165f7e15789caa0787e5a349465d9a454105d0c3a0ccf2e9cdfb8117"}, + {file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39bced168822e4bf2f545d1b6dbeded6db01c32629d9e4549ef2cd1604a12e1b"}, + {file = "inflate64-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:70bb6a22d300d8ca25c26bc60afb5662c5a96d97a801962874d0461568512789"}, + {file = "inflate64-1.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f3d5ea758358a1cc50f9e8e41de2134e9b5c5ca8bbcd88d1cd135d0e953d0fa8"}, + {file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fa102c834314c3d7edbf249d1be0bce5d12a9e122228a7ac3f861ee82c3dc5c"}, + {file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2ae56a34e6cc2a712418ac82332e5d550ef8599e0ffb64c19b86d63a7df0c5"}, + {file = "inflate64-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9808ae50b5db661770992566e51e648cac286c32bd80892b151e7b1eca81afe8"}, + {file = "inflate64-1.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:04b2788c6a26e1e525f53cc3d8c58782d41f18bef8d2a34a3d58beaaf0bfdd3b"}, + {file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67fd5b1f9e433b0abab8cb91f4da94d16223a5241008268a57f4729fdbfc4dbc"}, + {file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6f3b00c17ae365e82fc3d48ff9a7a566820a6c8c55b4e16c6cfbcbd46505a72"}, + {file = "inflate64-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:91c0c1d41c1655fb0189630baaa894a3b778d77062bb90ca11db878422948395"}, + {file = "inflate64-1.0.1.tar.gz", hash = "sha256:3b1c83c22651b5942b35829df526e89602e494192bf021e0d7d0b600e76c429d"}, ] [package.extras] -check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "isort (>=5.0.3)", "mypy (>=0.940)", "mypy-extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] +check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "flake8-isort", "mypy (>=1.10.0)", "mypy_extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] docs = ["docutils", "sphinx (>=5.0)"] -test = ["pyannotate", "pytest"] +test = ["pytest"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -1226,63 +1242,59 @@ files = [ [[package]] name = "kivy" -version = "2.3.0" +version = "2.3.1" description = "An open-source Python framework for developing GUI apps that work cross-platform, including desktop, mobile and embedded platforms." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "Kivy-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcd8fdc742ae10d27e578df2052b4c3e99a754e91baad77d1f2e4f4d1238917f"}, - {file = "Kivy-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7492593a5d5d916c48b14a06fbe177341b1efb5753c9984be2fb84e3b3313c89"}, - {file = "Kivy-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:199c30e8daeace61392329766eeb68daa49631cd9793bec9440dda5cf30d68d5"}, - {file = "Kivy-2.3.0-cp310-cp310-win32.whl", hash = "sha256:03fc4b26c7d6a5ecee2c97ffa8d622e97ac8a8c4e0a00d333c156d64e09e4e19"}, - {file = "Kivy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa5d57494cab405d395d65570d8481ab87869ba6daf4efb6c985bd16b32e7abf"}, - {file = "Kivy-2.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ec36ab3b74a525fa463b61895d3a2d76e9e4d206641233defae0d604e75df7ad"}, - {file = "Kivy-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3e923397779776ac97ad87a1b9dd603b7f1c911a6ae04f1d1658712eaaf7cb"}, - {file = "Kivy-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7766baac2509d699df84b284579fa25ee31383d48893660cd8dba62081453a29"}, - {file = "Kivy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:d654aaec6ddf9ca0edf73abd79e6aea423299c825a7ac432df17b031adaa7900"}, - {file = "Kivy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:33dca85a520fe958e7134b96025b0625eb769adfb8829359959c8b314b7bc8d4"}, - {file = "Kivy-2.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7b1307521843d316265481d963344e85870ae5fa0c7d0881129749acfe61da7b"}, - {file = "Kivy-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:521105a4ca1db3e1203c3cdba4abe737533874d9c29bbfb1e1ae941238507440"}, - {file = "Kivy-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6672959894f652856d1dfcbcdcc09263de5f1cbed768b997dc8dcecab4385a4f"}, - {file = "Kivy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:cf0bccc95b1344b79fbfdf54155d40438490f9801fd77279f068a4f66db72e4e"}, - {file = "Kivy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:710648c987a63e37c723e6622853efe0278767596631a38728a54474b2cb77f2"}, - {file = "Kivy-2.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d2c6a411e2d837684d91b46231dd12db74fb1db6a2628e9f27581ce1583e5c8a"}, - {file = "Kivy-2.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8757f189d8a41a4b164150144037359405906a46b07572e8e1c602a782cacebf"}, - {file = "Kivy-2.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06c9b0a4bff825793e150e2cdbc823b59f635ce51e575d470d0fc3a06159596c"}, - {file = "Kivy-2.3.0-cp37-cp37m-win32.whl", hash = "sha256:d72599b80c8a7c2698769b4129ff52f2c4e28b6a75f9401180052c7d80763f19"}, - {file = "Kivy-2.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0c42cf3c33e1aa3dee9c8acb6f91f8a4ad6c9de76064dcb8fdb1c60809643788"}, - {file = "Kivy-2.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:da8dd7ade7b7859642f53c3f32e10513877ce650367b68591b3aaacb46dcf012"}, - {file = "Kivy-2.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb6191bb51983f9e8257356aa53a71ccff5b6cf92f0bdcd5756973a6ac4b4446"}, - {file = "Kivy-2.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa3e7ce4fbd22284b303939676c5ae5448bb1e4d405f066dfc76c7cf56595cd"}, - {file = "Kivy-2.3.0-cp38-cp38-win32.whl", hash = "sha256:221f809220e518ae8b88a9b31310f9fef73727569e5cb13436572674fce4507b"}, - {file = "Kivy-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:8d2c3e5927fcf021d32124f915d56ae29e3a126c4f53db098436ea3959758a4c"}, - {file = "Kivy-2.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e8b91dfa2ad83739cc12d0f7bbe6410a3af2c2b3afd7b1d08919d9ec92826d61"}, - {file = "Kivy-2.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d79cb4a8649c476db18a079c447e57f8dbd4ad41459dc2162133a45cbb8cae96"}, - {file = "Kivy-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3be8db1eecc2d18859a7324b5cea79afb44095ccd73671987840afa26c68b0c9"}, - {file = "Kivy-2.3.0-cp39-cp39-win32.whl", hash = "sha256:5e6c431088584132d685696592e281fac217a5fd662f92cc6c6b48316e30b9c2"}, - {file = "Kivy-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:c332ff319db7648004d486c40fc4e700972f8e79a882d698e18eb238b2009e98"}, - {file = "Kivy-2.3.0.tar.gz", hash = "sha256:e8b8610c7f8ef6db908a139d369b247378f18105c96981e492eab2b4706c79d5"}, + {file = "Kivy-2.3.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ace93c166c9400f9435cfd3bd179b5ef9fdd40d69ee8171a6b8beba08c402d09"}, + {file = "Kivy-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d6215762510b463b0461d173f8a0b22e449beb12ba79cf151e18aa1d3d12a40"}, + {file = "Kivy-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba83dd8266fc2b1247de18c5e8114fa47ea20eb33eb7c3a9e2eb6202b9778088"}, + {file = "Kivy-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:d28ad14162554abd0324ae8f66ce2f374c05456d2656d60cfa80814f715d62c0"}, + {file = "Kivy-2.3.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:acb58843763075818de919989a73657307f4d833a7cc5547c1b16c226e260e5d"}, + {file = "Kivy-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a1799b19f6ab3bcfcef1e729a0229cee646167a1633e067c2add6978f928bb"}, + {file = "Kivy-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f180280df46a8c2f9988159938aa1a3e5a0094060d9586ea79df4b4ead9cad98"}, + {file = "Kivy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:002de19fef53955c48108758beea3092cf281326642d2e71eca1c443f4227cce"}, + {file = "Kivy-2.3.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:3f74679ef305f0ed0d8bb3599a2dddc80ffc81157bdc07947498dd689fc9a5d9"}, + {file = "Kivy-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e9b2fe5002f53371b3ad3712dccdaaa96905bbeaa83d7c7e64f3c44fec94e"}, + {file = "Kivy-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be79fe1494b6e60cb5aa5f124c37961530417cf27a53171b5a72c9e4c7d41cf"}, + {file = "Kivy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:2046f6608d17b6c1a0530ac9aa127307fa25f6f75764f1d60428a1c0f6c0af88"}, + {file = "Kivy-2.3.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:d8d9e57501961c5d45e5a2c5af0caef24e48f43a0cd88f607eb3b517198cfec4"}, + {file = "Kivy-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfe25296e9612cbfa2b68cfb0ccd3c80db1441c11261a9e131d5f8fed7618c2c"}, + {file = "Kivy-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:950d17e275f817ca34cc7c9d55f9d229067e2f7fbd0fad985a74c94893f7e739"}, + {file = "Kivy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:b5127af11c2fc1299f2331402fe4f6edb0985711c2841fbfdf509830c058c78e"}, + {file = "Kivy-2.3.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:d9e92c4894f99685d822ab7d059a3912bbff17d812e64a12ed3cf0acd37924cb"}, + {file = "Kivy-2.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b6054afcd08fd271b75e5552a72a5ffb122b05e8511e46bf69e3b5e344d31"}, + {file = "Kivy-2.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e473b10e9b9a49a6475760fd1f7d674873852f9561505ff6b4d8e5f1691d4f9"}, + {file = "Kivy-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:ee628e5dbe5e397ceeeda7b49cf4c800b79a695c6345fee1a8f1b71d3fc530bb"}, + {file = "Kivy-2.3.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:38a265ff95120694ab7dfc29ed2ccdec40a8a47344387b886f498449b0c3c66c"}, + {file = "Kivy-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae8168549c822a7122044965715d9f953a1862fdef132ea7725df8c1d2f19e5c"}, + {file = "Kivy-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748163206ce95aab5aaad1ada772a79e422a80b6308623510e74a1b7baf80f0a"}, + {file = "Kivy-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:91c836b7c2b4958fb4b3839f63b1724435bd617548baace0602c122d39756746"}, + {file = "Kivy-2.3.1.tar.gz", hash = "sha256:0833949e3502cdb4abcf9c1da4384674045ad7d85644313aa1ee7573f3b4f9d9"}, ] [package.dependencies] docutils = "*" +filetype = "*" "kivy-deps.angle" = {version = ">=0.4.0,<0.5.0", markers = "sys_platform == \"win32\""} "kivy-deps.glew" = {version = ">=0.3.1,<0.4.0", markers = "sys_platform == \"win32\""} -"kivy-deps.sdl2" = {version = ">=0.7.0,<0.8.0", markers = "sys_platform == \"win32\""} +"kivy-deps.sdl2" = {version = ">=0.8.0,<0.9.0", markers = "sys_platform == \"win32\""} Kivy-Garden = ">=0.1.4" pygments = "*" pypiwin32 = {version = "*", markers = "sys_platform == \"win32\""} +requests = "*" [package.extras] angle = ["kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\""] -base = ["docutils", "kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\"", "kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32 ; sys_platform == \"win32\"", "requests"] -dev = ["flake8", "funcparserlib (==1.0.0a0)", "kivy-deps.glew-dev (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer-dev (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2-dev (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pre-commit", "pyinstaller", "pytest (>=3.6)", "pytest-asyncio (!=0.11.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout", "responses", "sphinx (<=6.2.1)", "sphinxcontrib-actdiag", "sphinxcontrib-blockdiag", "sphinxcontrib-jquery", "sphinxcontrib-nwdiag", "sphinxcontrib-seqdiag"] -full = ["docutils", "ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\"", "kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)", "pygments", "pypiwin32 ; sys_platform == \"win32\""] +base = ["pillow (>=9.5.0,<11)"] +dev = ["flake8", "kivy-deps.glew-dev (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer-dev (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2-dev (>=0.8.0,<0.9.0) ; sys_platform == \"win32\"", "pre-commit", "pyinstaller", "pytest (>=3.6)", "pytest-asyncio (!=0.11.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout", "responses", "sphinx (>=6.2.1,<6.3.0)", "sphinxcontrib-jquery (>=4.1,<5.0)"] +full = ["ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)"] glew = ["kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\""] gstreamer = ["kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] media = ["ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] -sdl2 = ["kivy-deps.sdl2 (>=0.7.0,<0.8.0) ; sys_platform == \"win32\""] +sdl2 = ["kivy-deps.sdl2 (>=0.8.0,<0.9.0) ; sys_platform == \"win32\""] tuio = ["oscpy"] [[package]] @@ -1335,25 +1347,19 @@ files = [ [[package]] name = "kivy-deps-sdl2" -version = "0.7.0" +version = "0.8.0" description = "Repackaged binary dependency of Kivy." optional = false python-versions = "*" groups = ["main"] markers = "sys_platform == \"win32\"" files = [ - {file = "kivy_deps.sdl2-0.7.0-cp310-cp310-win32.whl", hash = "sha256:3c4b2bf1e473e6124563e1ff58cf3475c4f19fe9248940872c9e3c248bac3cb4"}, - {file = "kivy_deps.sdl2-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac0f4a6fe989899a60bbdb39516f45e4d90e2499864ab5d63e3706001cde48e8"}, - {file = "kivy_deps.sdl2-0.7.0-cp311-cp311-win32.whl", hash = "sha256:b727123d059c0c00c7d13cc1db8c8cfd0e48388cf24c11ec71cc6783811063c8"}, - {file = "kivy_deps.sdl2-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd946ca4e36a403bcafbe202033948c17f54bd5d28a343d98efd61f976822855"}, - {file = "kivy_deps.sdl2-0.7.0-cp312-cp312-win32.whl", hash = "sha256:2a8f23fe201dea368b47adfecf8fb9133315788d314ad32f33000254aa2388e4"}, - {file = "kivy_deps.sdl2-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:e56d5d651f81545c24f920f6f6e5d67b4100802152521022ccde53e822c507a2"}, - {file = "kivy_deps.sdl2-0.7.0-cp37-cp37m-win32.whl", hash = "sha256:c75626f6a3f8979b1c6a59e5070c7a547bb7c379a8e03f249af6b4c399305fc1"}, - {file = "kivy_deps.sdl2-0.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:95005fb3ae5b9e1d5edd32a6c0cfae9019efa2aeb3d909738dd73c5b9eea9dc1"}, - {file = "kivy_deps.sdl2-0.7.0-cp38-cp38-win32.whl", hash = "sha256:9728eaf70af514e0df163b062944fec008a5ceb73e53897ac89e62fcd2b0bac2"}, - {file = "kivy_deps.sdl2-0.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:a23811df7359e62acf4002fe5240d968a25e7aeaf7989b78b59cd6437f34f7b9"}, - {file = "kivy_deps.sdl2-0.7.0-cp39-cp39-win32.whl", hash = "sha256:ecbbcbd562a14a4a3870c8b6a0b1612eda24e9435df74fbb8e5f670560f0a9d6"}, - {file = "kivy_deps.sdl2-0.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:a5ef494d2f57224b93649df5f7a20c4f4cbc22416167732bf9f62d1cb263fef4"}, + {file = "kivy_deps.sdl2-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5af0a3b318a6ec9e0f0c1d476a4af4b2d0cbcce4dbfd89bc4681c33bcd6b3bcd"}, + {file = "kivy_deps.sdl2-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae3735480841ec9a57c0fb26e8647adee474a3d746147e3d75a1fc177c0fbc01"}, + {file = "kivy_deps.sdl2-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:bfe0cfca77883dde7e297b3b6039fa9cd7ee8df6b0d12516b38addb0551a574c"}, + {file = "kivy_deps.sdl2-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:56b1c44565b5e8cfc510585db13396edfc605965254f49ed8931189c546d481f"}, + {file = "kivy_deps.sdl2-0.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e9f8c0c1e76eb43f0bad8f36c5b92a46fb5696f733ec441db45e6864b1d4065"}, + {file = "kivy_deps.sdl2-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dbaa6718e66e8cd4967c2d4021e05114c558342e2468a86c0bce917bea10003f"}, ] [[package]] @@ -1373,126 +1379,113 @@ requests = "*" [[package]] name = "kiwisolver" -version = "1.4.7" +version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, - {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, - {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, - {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, - {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, - {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, - {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, - {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, - {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, - {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, - {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, - {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, - {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, - {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, - {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, - {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, - {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, - {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, - {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, - {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, - {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, - {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, - {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, - {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, - {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, - {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, - {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, - {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] [[package]] @@ -1517,14 +1510,14 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "loguru" -version = "0.7.2" +version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false -python-versions = ">=3.5" +python-versions = "<4.0,>=3.5" groups = ["main"] files = [ - {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, - {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, ] [package.dependencies] @@ -1532,7 +1525,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5) ; python_version >= \"3.9\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.2.2) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "mypy (==v1.5.1) ; python_version >= \"3.8\"", "pre-commit (==3.4.0) ; python_version >= \"3.8\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==7.4.0) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==4.1.0) ; python_version >= \"3.8\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.0.0) ; python_version >= \"3.8\"", "sphinx-autobuild (==2021.3.14) ; python_version >= \"3.9\"", "sphinx-rtd-theme (==1.3.0) ; python_version >= \"3.9\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.11.0) ; python_version >= \"3.8\""] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] [[package]] name = "macholib" @@ -1552,30 +1545,30 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.7" +version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, - {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, + {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, + {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, ] [package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, ] [package.dependencies] @@ -1583,13 +1576,12 @@ mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] +plugins = ["mdit-py-plugins (>=0.5.0)"] profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] [[package]] name = "markupsafe" @@ -1664,52 +1656,67 @@ files = [ [[package]] name = "matplotlib" -version = "3.9.2" +version = "3.10.5" description = "Python plotting package" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, - {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, - {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, - {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, - {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, - {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, - {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, - {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, - {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, - {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, - {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, - {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, - {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, - {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, - {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, - {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, - {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, - {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, - {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, - {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, - {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, - {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, - {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, - {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, - {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, - {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, + {file = "matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f"}, + {file = "matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a"}, + {file = "matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512"}, + {file = "matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343"}, + {file = "matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6"}, + {file = "matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467"}, + {file = "matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf"}, + {file = "matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf"}, + {file = "matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a"}, + {file = "matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc"}, + {file = "matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89"}, + {file = "matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903"}, + {file = "matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420"}, + {file = "matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2"}, + {file = "matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389"}, + {file = "matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea"}, + {file = "matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468"}, + {file = "matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369"}, + {file = "matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b"}, + {file = "matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2"}, + {file = "matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed"}, + {file = "matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1"}, + {file = "matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7"}, + {file = "matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f"}, + {file = "matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4"}, + {file = "matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe"}, + {file = "matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674"}, + {file = "matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c"}, + {file = "matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e"}, + {file = "matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b"}, + {file = "matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea"}, + {file = "matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124"}, + {file = "matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce"}, + {file = "matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154"}, + {file = "matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715"}, + {file = "matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837"}, + {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202"}, + {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb"}, + {file = "matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975"}, + {file = "matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667"}, + {file = "matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516"}, + {file = "matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e"}, + {file = "matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a"}, + {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569"}, + {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012"}, + {file = "matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592"}, + {file = "matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959"}, + {file = "matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61"}, + {file = "matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076"}, ] [package.dependencies] @@ -1724,7 +1731,7 @@ pyparsing = ">=2.3.1" python-dateutil = ">=2.7" [package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] [[package]] name = "mdurl" @@ -1813,14 +1820,14 @@ mkdocs = ">=1.2.3" [[package]] name = "mkdocs-autorefs" -version = "1.2.0" +version = "1.4.2" description = "Automatically link across pages in MkDocs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"}, - {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"}, + {file = "mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13"}, + {file = "mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749"}, ] [package.dependencies] @@ -1878,14 +1885,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-include-markdown-plugin" -version = "6.2.2" +version = "7.1.6" description = "Mkdocs Markdown includer plugin." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_include_markdown_plugin-6.2.2-py3-none-any.whl", hash = "sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7"}, - {file = "mkdocs_include_markdown_plugin-6.2.2.tar.gz", hash = "sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d"}, + {file = "mkdocs_include_markdown_plugin-7.1.6-py3-none-any.whl", hash = "sha256:7975a593514887c18ecb68e11e35c074c5499cfa3e51b18cd16323862e1f7345"}, + {file = "mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677"}, ] [package.dependencies] @@ -1897,31 +1904,32 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.5.42" +version = "9.6.18" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.5.42-py3-none-any.whl", hash = "sha256:452a7c5d21284b373f36b981a2cbebfff59263feebeede1bc28652e9c5bbe316"}, - {file = "mkdocs_material-9.5.42.tar.gz", hash = "sha256:92779b5e9b5934540c574c11647131d217dc540dce72b05feeda088c8eb1b8f2"}, + {file = "mkdocs_material-9.6.18-py3-none-any.whl", hash = "sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701"}, + {file = "mkdocs_material-9.6.18.tar.gz", hash = "sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05"}, ] [package.dependencies] babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" +click = "<8.2.2" colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" +jinja2 = ">=3.1,<4.0" markdown = ">=3.2,<4.0" mkdocs = ">=1.6,<2.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pygments = ">=2.16,<3.0" pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" requests = ">=2.26,<3.0" [package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] @@ -1939,14 +1947,14 @@ files = [ [[package]] name = "mkdocs-minify-plugin" -version = "0.7.2" +version = "0.8.0" description = "An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs-minify-plugin-0.7.2.tar.gz", hash = "sha256:6a551e22d6517eaef9e1890afd60021dc1dcd1255de02d266f588d1ace040713"}, - {file = "mkdocs_minify_plugin-0.7.2-py3-none-any.whl", hash = "sha256:ae8bfc4a68806883e990ea025938b3f989da7b9fa08ea8390dba47adf25e0c5b"}, + {file = "mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d"}, + {file = "mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6"}, ] [package.dependencies] @@ -1957,14 +1965,14 @@ mkdocs = ">=1.4.1" [[package]] name = "mkdocs-pymdownx-material-extras" -version = "2.6" +version = "2.8" description = "Plugin to extend MkDocs Material theme." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_pymdownx_material_extras-2.6-py3-none-any.whl", hash = "sha256:9a005c933c70fdfd2bdb320022b23ddce0b3fc5595aeef7084c8b7613804190b"}, - {file = "mkdocs_pymdownx_material_extras-2.6.tar.gz", hash = "sha256:2aae99cd91a604811ec8b750cc9526f8a404961d90cee30fd43ff112f651d8b1"}, + {file = "mkdocs_pymdownx_material_extras-2.8-py3-none-any.whl", hash = "sha256:81b68789420c51b9b15514180d0f3ab7136d56ee512c830c998d2edb77ca3d77"}, + {file = "mkdocs_pymdownx_material_extras-2.8.tar.gz", hash = "sha256:7b22bb119cd9592f98d6c6d4d269506d9a68d7038355c71525aadc88169ee9fe"}, ] [package.dependencies] @@ -1987,151 +1995,167 @@ mkdocs = ">=1.0.3" [[package]] name = "mkdocstrings" -version = "0.23.0" +version = "0.30.0" description = "Automatic documentation from sources, for MkDocs." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocstrings-0.23.0-py3-none-any.whl", hash = "sha256:051fa4014dfcd9ed90254ae91de2dbb4f24e166347dae7be9a997fe16316c65e"}, - {file = "mkdocstrings-0.23.0.tar.gz", hash = "sha256:d9c6a37ffbe7c14a7a54ef1258c70b8d394e6a33a1c80832bce40b9567138d1c"}, + {file = "mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2"}, + {file = "mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444"}, ] [package.dependencies] Jinja2 = ">=2.11.1" -Markdown = ">=3.3" +Markdown = ">=3.6" MarkupSafe = ">=1.1" -mkdocs = ">=1.2" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +mkdocs = ">=1.6" +mkdocs-autorefs = ">=1.4" +mkdocstrings-python = {version = ">=1.16.2", optional = true, markers = "extra == \"python\""} pymdown-extensions = ">=6.3" [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] +python = ["mkdocstrings-python (>=1.16.2)"] python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.8.0" +version = "1.17.0" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocstrings_python-1.8.0-py3-none-any.whl", hash = "sha256:4209970cc90bec194568682a535848a8d8489516c6ed4adbe58bbc67b699ca9d"}, - {file = "mkdocstrings_python-1.8.0.tar.gz", hash = "sha256:1488bddf50ee42c07d9a488dddc197f8e8999c2899687043ec5dd1643d057192"}, + {file = "mkdocstrings_python-1.17.0-py3-none-any.whl", hash = "sha256:49903fa355dfecc5ad0b891e78ff5d25d30ffd00846952801bbe8331e123d4b0"}, + {file = "mkdocstrings_python-1.17.0.tar.gz", hash = "sha256:c6295962b60542a9c7468a3b515ce8524616ca9f8c1a38c790db4286340ba501"}, ] [package.dependencies] -griffe = ">=0.37" -mkdocstrings = ">=0.20" +griffe = ">=1.12.1" +mkdocs-autorefs = ">=1.4" +mkdocstrings = ">=0.30" [[package]] name = "multidict" -version = "6.1.0" +version = "6.6.4" description = "multidict implementation" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, + {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, + {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, + {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, + {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, + {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, + {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, + {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, + {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, + {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, + {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, + {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, + {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, + {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, + {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, + {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, + {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, + {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, + {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, + {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, + {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, + {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, + {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, + {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, + {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, + {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, + {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, + {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, + {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, + {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, + {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, + {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, + {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, + {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, + {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, + {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, + {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, + {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, + {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "multivolumefile" version = "0.2.3" @@ -2151,50 +2175,56 @@ type = ["mypy", "mypy-extensions"] [[package]] name = "mypy" -version = "1.13.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, - {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, - {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, - {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, - {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, - {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, - {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, - {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, - {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, - {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, - {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, - {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, - {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, - {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, - {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, - {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, - {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, - {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, - {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, - {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, - {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, - {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, - {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, - {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, - {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, - {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, - {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -2205,35 +2235,36 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "networkx" -version = "3.4.2" +version = "3.5" description = "Python package for creating and manipulating graphs and networks" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, - {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, + {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, + {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, ] [package.extras] -default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] -example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"] +developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"] +test-extras = ["pytest-mpl", "pytest-randomly"] [[package]] name = "nodeenv" @@ -2249,65 +2280,86 @@ files = [ [[package]] name = "numpy" -version = "2.1.2" +version = "2.3.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, - {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, - {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, - {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, - {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, - {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, - {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, - {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, - {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, - {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, - {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, - {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, - {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, - {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, - {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, - {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, - {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, - {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, - {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, - {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, - {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, - {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, - {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, - {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, - {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, - {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, + {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, + {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, + {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, + {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, + {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, + {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, + {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, + {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, + {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, + {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, + {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, + {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, + {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, + {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, + {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, + {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, + {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, + {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, + {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, ] [[package]] @@ -2315,40 +2367,44 @@ name = "omnitils" version = "1.4.6" description = "Universal reusable Python utils for the modern human." optional = false -python-versions = "<3.13,>=3.10" +python-versions = ">=3.10,<4.0" groups = ["main"] -files = [ - {file = "omnitils-1.4.6-py3-none-any.whl", hash = "sha256:47028f3e73c450715ca3b187534617fd7feecab50a6f19f0ff5ee6616c462d20"}, - {file = "omnitils-1.4.6.tar.gz", hash = "sha256:b70b39c89f32dd038d858b4285dabb8c86e9c97b684f2af8be76236c633a58dc"}, -] +files = [] +develop = false [package.dependencies] -backoff = ">=2.2.1,<3.0.0" -click = ">=8.1.7,<9.0.0" -loguru = ">=0.7.2,<0.8.0" -pathvalidate = ">=3.2.0,<4.0.0" -pillow = ">=10.4.0,<11.0.0" -py7zr = ">=0.22.0,<0.23.0" -pydantic = ">=2.9.2,<3.0.0" -python-dateutil = ">=2.9.0.post0,<3.0.0" -PyYAML = ">=6.0.1,<7.0.0" -ratelimit = ">=2.2.1,<3.0.0" -requests = ">=2.32.3,<3.0.0" -tomli = ">=2.0.1,<3.0.0" -tomlkit = ">=0.13.2,<0.14.0" -tqdm = ">=4.66.1,<5.0.0" -yarl = ">=1.9.4,<2.0.0" +backoff = "^2.2.1" +click = "^8.2.1" +loguru = "^0.7.3" +pathvalidate = "^3.3.1" +pillow = "^11.3.0" +py7zr = "^1.0.0" +pydantic = "^2.11.7" +python-dateutil = "^2.9.0.post0" +PyYAML = "^6.0.2" +ratelimit = "^2.2.1" +requests = "^2.32.4" +tomli = "^2.2.1" +tomlkit = "^0.13.3" +tqdm = "^4.67.1" +yarl = "^1.20.1" + +[package.source] +type = "git" +url = "https://github.com/pappnu/omnitils.git" +reference = "dev" +resolved_reference = "73cd55b7495b348a4a85380396e741214ea13a69" [[package]] name = "packaging" -version = "24.1" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -2381,191 +2437,222 @@ files = [ [[package]] name = "pathvalidate" -version = "3.2.1" +version = "3.3.1" description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pathvalidate-3.2.1-py3-none-any.whl", hash = "sha256:9a6255eb8f63c9e2135b9be97a5ce08f10230128c4ae7b3e935378b82b22c4c9"}, - {file = "pathvalidate-3.2.1.tar.gz", hash = "sha256:f5d07b1e2374187040612a1fcd2bcb2919f8db180df254c9581bb90bf903377d"}, + {file = "pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f"}, + {file = "pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177"}, ] [package.extras] -docs = ["Sphinx (>=2.4)", "sphinx-rtd-theme (>=1.2.2)", "urllib3 (<2)"] -readme = ["path (>=13,<17)", "readmemaker (>=1.1.0)"] +docs = ["Sphinx (>=2.4)", "sphinx_rtd_theme (>=1.2.2)", "urllib3 (<2)"] +readme = ["path (>=13,<18)", "readmemaker (>=1.2.0)"] test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-md-report (>=0.6.2)"] [[package]] name = "pefile" -version = "2024.8.26" +version = "2023.2.7" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" groups = ["dev"] markers = "sys_platform == \"win32\"" files = [ - {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, - {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, + {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, + {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, ] [[package]] name = "photoshop-python-api" -version = "0.22.4" +version = "0.24.1" description = "Python API for Photoshop." optional = false python-versions = ">=3.8,<4.0" groups = ["main"] -files = [ - {file = "photoshop_python_api-0.22.4-py3-none-any.whl", hash = "sha256:18e206f615a648427c884daa2c0a4b02159cd461a1779a64d69e831730e4dff9"}, - {file = "photoshop_python_api-0.22.4.tar.gz", hash = "sha256:ad080c61d4c850f71165dbf75c6fc23b8b0a1695d3192c0d2439e4101523e5bb"}, -] +files = [] +develop = false [package.dependencies] -comtypes = ">=1.1.11,<2.0.0" -wheel = ">=0.42.0,<0.43.0" +comtypes = "^1.1.11" +wheel = "^0.45.0" + +[package.source] +type = "git" +url = "https://github.com/pappnu/photoshop-python-api.git" +reference = "type-annotation" +resolved_reference = "e790d52e8aa4a753d2a1b6044afe062af545e4c4" [[package]] name = "pillow" -version = "10.4.0" +version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +test-arrow = ["pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.8.0" +version = "4.3.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, ] [package.dependencies] @@ -2577,14 +2664,14 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.36" +version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, - {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -2592,166 +2679,166 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.2.0" +version = "0.3.2" description = "Accelerated property cache" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, ] [[package]] name = "psd-tools" -version = "1.10.2" +version = "1.10.9" description = "Python package for working with Adobe Photoshop PSD files" optional = false -python-versions = "*" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "psd_tools-1.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0d905987d593b0a15647235418cf993ab1d228b7705fe2ebf3b9362af96b51e"}, - {file = "psd_tools-1.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0f6c7ffa7685d174232aefbb39448b0b6aaeba9e85b28e0b1c16f0bc92890c"}, - {file = "psd_tools-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68f208b5e9c744c7ec3b7ce843698ee1db71cd15695699d83ed88b0aa6c112e"}, - {file = "psd_tools-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88eda1b4dea2dd3c1e4f3fce1b2b305aa39979a4ec3258ab22a32768a145ab26"}, - {file = "psd_tools-1.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:999823284f585fbeb9b415dc4a66d9321a34b49fd20aec1874515d156462f775"}, - {file = "psd_tools-1.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d77c11f3f1382f618580adfefd5f39f6a1a90f652f410279461f9f1e76e86534"}, - {file = "psd_tools-1.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1391b8749ad439ebfd331fdb02ac50ef59b7a8a9c28c75b63a217dbc18abf61a"}, - {file = "psd_tools-1.10.2-cp310-cp310-win32.whl", hash = "sha256:5bd2ba02a1ee57cd152ae7184eafb432fb96d4fd3cc2e1911aaebf1a78516fe5"}, - {file = "psd_tools-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cbc043609cdbf31c8c5d69d5e74b9d647cb29bc6bc6e4006330d061ac7d5907"}, - {file = "psd_tools-1.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7287bbaeb94d6a660bdeeaa1802224a142923ec088e3d120fc356a4b76c7db2a"}, - {file = "psd_tools-1.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045ef92857d50591b49b788b2f9541db4ffd256056751bebb41e0887d629cf77"}, - {file = "psd_tools-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a18cd870320f88bf521dcb59be73aed5ecaaf2f51e00f2e5a8de2e2d64e43928"}, - {file = "psd_tools-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93d634cda00d0e6338da86fb967d8c449b13641061d7043cbb6ec019c93c04bc"}, - {file = "psd_tools-1.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b2707edaac31c8f0dd30aa7dd4f1befd23f4f5f23bec63209ced023cb715f21"}, - {file = "psd_tools-1.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:477cb09d6c35798177bc7456b0c30fac2d99bf5f78d34592dc331986f8a6a080"}, - {file = "psd_tools-1.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a3aa5e74b4ffd2036b53110a1709284c5649a775e04ecd4d000e9ae956ce634"}, - {file = "psd_tools-1.10.2-cp311-cp311-win32.whl", hash = "sha256:378071e6c11ffdb1f715439a41134bf3c0400518e2ee203d92ca91cebf3d5ce0"}, - {file = "psd_tools-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:7c649cc5234f12aa423bb8a7e3f637f8789e212557be2859c581743a17ba1804"}, - {file = "psd_tools-1.10.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2f65fe1eead47a5fae7014381a30c0f72fbb98425165f4e6b9971756a0e9c27e"}, - {file = "psd_tools-1.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e00b70418da991b457fa1e177832bc59674bd66e281ddd1acad21daf485d38"}, - {file = "psd_tools-1.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c50c142372bb8ec3053aebdf0f28ec2426b7bfd459ce525612e5bd6e20cca41e"}, - {file = "psd_tools-1.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca7f92026c0bcb4e076be77d2bfd6e1f9585129c4399a03ac565f8c02c929c6"}, - {file = "psd_tools-1.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7611b5854f2b3de1feb926fc4a1a7760c5de081037642bccff55ce323d56dcf9"}, - {file = "psd_tools-1.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:11a2b8ffd4e72dd71f1e7f56c017c082b9f5bd6b400638c98cc9cb353bac48a3"}, - {file = "psd_tools-1.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7934c4c76d44e0ec2331cf3ef9605d6058f33cdd243ebee0ee4708c8113a833a"}, - {file = "psd_tools-1.10.2-cp312-cp312-win32.whl", hash = "sha256:1d57f1544eb97d97fff6f1d720417ac65c2005f5eceed9139be787b7c90921df"}, - {file = "psd_tools-1.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:8590940a655ec34821a02684f1f90c0aa27b677e5404a29b744f46ed7cb168ed"}, - {file = "psd_tools-1.10.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:55227eee2789c013ac3b9dd15077010365851f4c441d1cdd58489dfdb6440913"}, - {file = "psd_tools-1.10.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a21c0f767a4557428516e7336ba460fe865e1e1d44bb7a3e2b0ea110786dda8"}, - {file = "psd_tools-1.10.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2535871f25e8d59157f4d851d49b020e172ada7cd89350f67e943f645926b1e"}, - {file = "psd_tools-1.10.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5ed40f2447caa51dba48b4d9319860bcf21178dbcf1216833aa6ad9b30072e8"}, - {file = "psd_tools-1.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f7831b32674e22f7784624d93ecb319b50542c9ccb3f977c855928b2a7d8e53"}, - {file = "psd_tools-1.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:872f29979220268091b3e6a68bdda1228daf15d6ae9ca703c2c0a87b2eedb703"}, - {file = "psd_tools-1.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5dafd296cf295160ba45f0320af5cc5b90d6f4fec374fe071c819cd80dd7bbc3"}, - {file = "psd_tools-1.10.2-cp313-cp313-win32.whl", hash = "sha256:89129831048e092011c6c3f6046a05fbe066f88f9071827f288f3b6117ac3e13"}, - {file = "psd_tools-1.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:8b32282a9a218cb3b05d708ce10e4331f0d5f1ca06c11044536f0552222bf53f"}, - {file = "psd_tools-1.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cc5b317b6fabf954a1d3c1feb04c8f51625182375c65fb0467e6ef9ecb73795d"}, - {file = "psd_tools-1.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3ee2f95adaff7414409ea210e19ccc9a8c6bf2b711916e3a53959b8054c13a"}, - {file = "psd_tools-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:135221df2705f6e1d5e3d6458e5524f4582bc190499735087aafa07a083103f0"}, - {file = "psd_tools-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f4f8c700825fc637b6d4d37420c83efe1fa487eb6a29703e7d629ae216843f"}, - {file = "psd_tools-1.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ee58b0242f0ded58a05d638112b8fc75c0d0f97b49caaac6f830d5b6764557e7"}, - {file = "psd_tools-1.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f0faf7771b6c9d181a70f04aa8e79fad9d0a447b7afcb6bbab295b701152ee01"}, - {file = "psd_tools-1.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a1474712cf226e6d2a911b2d9a9431b1cdcdf1861497573bff1f60d7fe014906"}, - {file = "psd_tools-1.10.2-cp39-cp39-win32.whl", hash = "sha256:61e8c7f7d03036a0cac096be9993a180c00639fbd9a188d23793553e75535ea7"}, - {file = "psd_tools-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c4f6a75aa55f07d20aa4af292084f00861fc0a1f88790cddd9c97df841846e14"}, - {file = "psd_tools-1.10.2.tar.gz", hash = "sha256:5499e97a4d4b072ea0b23c5fccd328c009e053947820ae60dd934612f674d64b"}, + {file = "psd_tools-1.10.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8519b7849569f3d44eeff9c3c93462be5ef329bc4ce03bb501d69f23844eb9d"}, + {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ad40c36a86182d6dc09ac6f8743df585cc238e50a88db0d2000e703cb59bded"}, + {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe420a47219fd371f7e0b49fdef2c46c87c628900df0c793c01d863fd702f451"}, + {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57b85e8a2ac7ee4b28ed24fd335867c4454731ce6437d9610d0211809a40706"}, + {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d3c3669ddcc04a6562738e5347169f9c60d4cc57c4561f72e4ee0be6c1122f9"}, + {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e3afbc4d2be2c21c05ae2889088eac80376a8a909ffda6910b0b533f3d1de3f"}, + {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2f962f84581f8751d1e6b933affa4e8372c44e8895e2b98c6c05a240c88776e5"}, + {file = "psd_tools-1.10.9-cp310-cp310-win32.whl", hash = "sha256:01cbf330500fed71021dcf7cfb5c536d0215bc50174a6feee25df0f76f756756"}, + {file = "psd_tools-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:9511046b59b01e261339d261eecb8bf43dc0d296400fbebf614b8e599c285774"}, + {file = "psd_tools-1.10.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d26de0e49102a7a2aa8b48021c1f5f00d51a5059df5944a1e71bcb04a75b04d"}, + {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fc042be70363325c68e5ef4326d6318b3cc069f271cd8f21d1def4fa1a941a6"}, + {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16c672ddacba67a41aa1dcc294bbfaeca4f907c609adc6459c9d9568b7c0b95f"}, + {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bca2420d12a8f3847a814a8afe8aeaa33906e36ff43fc606201a1584d001613"}, + {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a9b9d95bd5bd31d4280e6b70ff020933267e99fe9240ad0ac537f0b63217c8a"}, + {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:15900fbd009f2953e971635297a5de8d4e4fc5f58a9b219ff3b8eba0e8f71d0d"}, + {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e840c2eb48c0f1a02c7614039980835e71924fdabffda8f53c433e83d10ca0f8"}, + {file = "psd_tools-1.10.9-cp311-cp311-win32.whl", hash = "sha256:e5142d9d2f58f02e547da7b096d0f2cceb1c7a34119e14eed3f2fbd972038220"}, + {file = "psd_tools-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:c6fccb9f6cbf2ca137fc6cfba2592fcb65a30a0b312cbd9200695ea45cc64815"}, + {file = "psd_tools-1.10.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d319ae67e6cf86c9fdc6f27cf598f378fae1e754ecf77fe4e928289b0823a060"}, + {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd8258210a4299e56f0ca5b9fe0145ae7d2034fbb790f43e19918c35be693d37"}, + {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9514641cc50e973847c135075cfa1de2340bc9f7e67cfba52b7f65005a15f0"}, + {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c8a6cb9e50c5c0f970b354fa01950000f7fd7a03dfae53fd4550fcd8b2211a4"}, + {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:20960fefe641ce1b907287110ea8fb1ebfc47e67874f2855069721ed91e272da"}, + {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d4eb712a64267fe08f4d590dcb5c08c5ab8216618ff011e002e8b1856f97ee3"}, + {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a75b29a55e97193e79ad4696bb4fa0850f3016472a9fa0bcaf175189b9b156e4"}, + {file = "psd_tools-1.10.9-cp312-cp312-win32.whl", hash = "sha256:eecc632efa73d1f6731ea24563b675458b2f70849ace98e3d1835d83b2993a00"}, + {file = "psd_tools-1.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:27e03bc376ec977fe48d54894711c66b2084d08837ed93a29ded77b3ab1036cf"}, + {file = "psd_tools-1.10.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:140d683cbd758239f9bce5b78d35eb5c657c89c3b0825aa09e5c1eaeb5b3c746"}, + {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8726132b535da6364eaaf2da089ebbe399fbfa88c0e3ff571e74d9372eb49a01"}, + {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da063395a6e2d7a1b3979c44ac8e7dc93f21eb179c67e6d0d9fc86aa3b646be"}, + {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec02bec6a895fe5aa54799717328dfcf57d156bc6dd1f54c00b48c8781380ea9"}, + {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:69f3b8b88af985d961a64029f154ba5593b757d61b7152ad39ea62bd11b6e788"}, + {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aaef0f8eed0bd9c042a3b4bb4be3bc8ebb1b0825fbe74d41574133b252b5c782"}, + {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:887f05134315ea338c9bbbaa4e3289b42fb2cf6b3a55524833ca80d83517c058"}, + {file = "psd_tools-1.10.9-cp313-cp313-win32.whl", hash = "sha256:dcd5e3606142c17653706f3619c650ed8717f340a1ef4eb047800d76cc7b7072"}, + {file = "psd_tools-1.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe8e6ac8ece6805d0bba105516eec50079fd600d9e884510fa7edea327a603ae"}, + {file = "psd_tools-1.10.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:43bd172b19fbcaa6fdbc05b8907a4f272066ffae38600d75416b115b21707bf6"}, + {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b33efb9358aeadb3aed554097b1cd4a0afa80156e8292159066a7bcc6b0b977"}, + {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca88c09380cc78847f7592c363c1faabaddbd29f95fdc81472c2917ddf9d4c8"}, + {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82a089d063c68b69aa42e49d6efaaf5c6127f098f9a323cb3cbd23b5f80f0a0b"}, + {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d65c12e05fd85085cc2b8ba269aedb6f99d070701f29fa98daee72f3c050a3d7"}, + {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08b4cd769bacbdadc33f54fd6614934f10ff31219666a2e4be5c236902332b19"}, + {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e74268102ea68dc4be2326e9792a5ca26196ee6de757d2cbdf6501a901876087"}, + {file = "psd_tools-1.10.9-cp39-cp39-win32.whl", hash = "sha256:f4f839745dab89ad241221d532fb15c3e5c7c35670bc97c0dfff671614f688e7"}, + {file = "psd_tools-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:7e39ad46ddcf1a7857673805d18c62fb65d0fa90525cdfaa8c1e37ca7b7fb912"}, + {file = "psd_tools-1.10.9.tar.gz", hash = "sha256:292a26888cf389f702312417242c8ed0e919bce4ea318feea20a233e9dc11dd5"}, ] [package.dependencies] @@ -2759,56 +2846,49 @@ aggdraw = "*" attrs = ">=23.0.0" docopt = ">=0.6.0" numpy = "*" -Pillow = ">=10.0.0" +Pillow = ">=10.3.0" scikit-image = "*" scipy = "*" [package.extras] dev = ["ipython", "pytest", "pytest-cov", "ruff", "types-docopt"] -docs = ["sphinx", "sphinx-rtd-theme"] +docs = ["sphinx", "sphinx_rtd_theme"] [[package]] name = "psutil" -version = "6.1.0" -description = "Cross-platform lib for process and system monitoring in Python." +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, - {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, - {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, - {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, - {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, - {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, - {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, - {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, - {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, - {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, - {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, - {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, ] markers = {main = "sys_platform != \"cygwin\""} [package.extras] -dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "py7zr" -version = "0.22.0" +version = "1.0.0" description = "Pure python 7-zip library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "py7zr-0.22.0-py3-none-any.whl", hash = "sha256:993b951b313500697d71113da2681386589b7b74f12e48ba13cc12beca79d078"}, - {file = "py7zr-0.22.0.tar.gz", hash = "sha256:c6c7aea5913535184003b73938490f9a4d8418598e533f9ca991d3b8e45a139e"}, + {file = "py7zr-1.0.0-py3-none-any.whl", hash = "sha256:6f42d2ff34c808e9026ad11b721c13b41b0673cf2b4e8f8fb34f9d65ae143dd1"}, + {file = "py7zr-1.0.0.tar.gz", hash = "sha256:f6bfee81637c9032f6a9f0eb045a4bfc7a7ff4138becfc42d7cb89b54ffbfef1"}, ] [package.dependencies] @@ -2818,83 +2898,88 @@ inflate64 = ">=1.0.0,<1.1.0" multivolumefile = ">=0.2.3" psutil = {version = "*", markers = "sys_platform != \"cygwin\""} pybcj = ">=1.0.0,<1.1.0" -pycryptodomex = ">=3.16.0" -pyppmd = ">=1.1.0,<1.2.0" -pyzstd = ">=0.15.9" +pycryptodomex = ">=3.20.0" +pyppmd = ">=1.1.0,<1.3.0" +pyzstd = ">=0.16.1" texttable = "*" [package.extras] -check = ["black (>=23.1.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=5.0.3)", "lxml", "mypy (>=0.940)", "mypy-extensions (>=0.4.1)", "pygments", "readme-renderer", "twine", "types-psutil"] +check = ["black (>=24.8.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=5.13.2)", "lxml", "mypy (>=1.10.0)", "mypy_extensions (>=1.0.0)", "pygments", "pylint", "readme-renderer", "twine", "types-psutil"] debug = ["pytest", "pytest-leaks", "pytest-profiling"] -docs = ["docutils", "sphinx (>=5.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] -test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-remotedata", "pytest-timeout"] +docs = ["docutils", "sphinx (>=7.0.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] +test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-httpserver", "pytest-remotedata", "pytest-timeout", "requests"] test-compat = ["libarchive-c"] [[package]] name = "pybcj" -version = "1.0.2" +version = "1.0.6" description = "bcj filter library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bff28d97e47047d69a4ac6bf59adda738cf1d00adde8819117fdb65d966bdbc"}, - {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:198e0b4768b4025eb3309273d7e81dc53834b9a50092be6e0d9b3983cfd35c35"}, - {file = "pybcj-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa26415b4a118ea790de9d38f244312f2510a9bb5c65e560184d241a6f391a2d"}, - {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabb2be57e4ca28ea36c13146cdf97d73abd27c51741923fc6ba1e8cd33e255c"}, - {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d6d613bae6f27678d5e44e89d61018779726aa6aa950c516d33a04b8af8c59"}, - {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ffae79ef8a1ea81ea2748ad7b7ad9b882aa88ddf65ce90f9e944df639eccc61"}, - {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdb4d8ff5cba3e0bd1adee7d20dbb2b4d80cb31ac04d6ea1cd06cfc02d2ecd0d"}, - {file = "pybcj-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a29be917fbc99eca204b08407e0971e0205bfdad4b74ec915930675f352b669d"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2562ebe5a0abec4da0229f8abb5e90ee97b178f19762eb925c1159be36828b3"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af19bc61ded933001cd68f004ae2042bf1a78eb498a3c685ebd655fa1be90dbe"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3f4a447800850aba7724a2274ea0a4800724520c1caf38f7d0dabf2f89a5e15"}, - {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1c8af7a4761d2b1b531864d84113948daa0c4245775c63bd9874cb955f4662"}, - {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8007371f6f2b462f5aa05d5c2135d0a1bcf5b7bdd9bd15d86c730f588d10b7d3"}, - {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1079ca63ff8da5c936b76863690e0bd2489e8d4e0a3a340e032095dae805dd91"}, - {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e9a785eb26884429d9b9f6326e68c3638828c83bf6d42d2463c97ad5385caff2"}, - {file = "pybcj-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:9ea46e2d45469d13b7f25b08efcdb140220bab1ac5a850db0954591715b8caaa"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:21b5f2460629167340403d359289a173e0729ce8e84e3ce99462009d5d5e01a4"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2940fb85730b9869254559c491cd83cf777e56c76a8a60df60e4be4f2a4248d7"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f40f3243139d675f43793a4e35c410c370f7b91ccae74e70c8b2f4877869f90e"}, - {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c2b3e60b65c7ac73e44335934e1e122da8d56db87840984601b3c5dc0ae4c19"}, - {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746550dc7b5af4d04bb5fa4d065f18d39c925bcb5dee30db75747cd9a58bb6e8"}, - {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8ce9b62b6aaa5b08773be8a919ecc4e865396c969f982b685eeca6e80c82abb7"}, - {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:493eab2b1f6f546730a6de0c5ceb75ce16f3767154e8ae30e2b70d41b928b7d2"}, - {file = "pybcj-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ef55b96b7f2ed823e0b924de902065ec42ade856366c287dbb073fabd6b90ec1"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ed5b3dd9c209fe7b90990dee4ef21870dca39db1cd326553c314ee1b321c1cc"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22a94885723f8362d4cb468e68910eef92d3e2b1293de82b8eacb4198ef6655f"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b8f9368036c9e658d8e3b3534086d298a5349c864542b34657cbe57c260daa49"}, - {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87108181c7a6ac4d3fc1e4551cab5db5eea7f9fdca611175243234cd94bcc59b"}, - {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db57f26b8c0162cfddb52b869efb1741b8c5e67fc536994f743074985f714c55"}, - {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bdf5bcac4f1da36ad43567ea6f6ef404347658dbbe417c87cdb1699f327d6337"}, - {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c3171bb95c9b45cbcad25589e1ae4f4ca4ea99dc1724c4e0671eb6b9055514e"}, - {file = "pybcj-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9a2585e0da9cf343ea27421995b881736a1eb604a7c1d4ca74126af94c3d4a8"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fdb7cd8271471a5979d84915c1ee57eea7e0a69c893225fc418db66883b0e2a7"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e96ae14062bdcddc3197300e6ee4efa6fbc6749be917db934eac66d0daaecb68"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a54ebdc8423ba99d75372708a882fcfc3b14d9d52cf195295ad53e5a47dab37f"}, - {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3602be737c6e9553c45ae89e6b0e556f64f34dabf27d5260317d1824d31b79d3"}, - {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dd2ca52a48841f561bfec0fa3f208d375b0a8dcd3d7b236459e683ae29221d"}, - {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8204a714029784b1a08a3d790430d80b423b68615c5b1e67aabca5bd5419b77d"}, - {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fde2376b180ae2620c102fbc3ef06638d306feae83964aaa5051ecbdda54845a"}, - {file = "pybcj-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:3b8d7810fb587adbffba025330cf212d9bbed8f29559656d05cb6609673f306a"}, - {file = "pybcj-1.0.2.tar.gz", hash = "sha256:c7f5bef7f47723c53420e377bc64d2553843bee8bcac5f0ad076ab1524780018"}, + {file = "pybcj-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0fc8eda59e9e52d807f411de6db30aadd7603aa0cb0a830f6f45226b74be1926"}, + {file = "pybcj-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0495443e8691510129f0c589ed956af4962c22b7963c5730b0c80c9c5b818c06"}, + {file = "pybcj-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c7998b546c3856dbe9ae879cb90393df80507f65097e7019785852769f4a990"}, + {file = "pybcj-1.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c859f85e718924f48b3ac967cda5528ccbef1e448a4462652cca688eee477"}, + {file = "pybcj-1.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:186fbb849883ac80764d96dbd253503dd9cecbcf6133504a0c9d6a2df81d5746"}, + {file = "pybcj-1.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:437bd5f5e6579bde404404ad2de915d1306c389595c68d0eb8933fee1408e951"}, + {file = "pybcj-1.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:933d6be8f07c653ff3eba16900376b3212249be1c71caf9db17f4cd52da5076c"}, + {file = "pybcj-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:90e169b669bbed30e22d36ba97d23dcfc71e044d3be41c8010fd6a53950725e5"}, + {file = "pybcj-1.0.6-cp310-cp310-win_arm64.whl", hash = "sha256:06441026c773f8abeb7816566acfffe7cd65a9b69094197a9de64d0496cd4c3c"}, + {file = "pybcj-1.0.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0275564a1afc4b2d1a6ff465384fb73a64622a88b6e4856cb7964ba2335a06e"}, + {file = "pybcj-1.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa794b134b4ee183a4ceb739e9c3a445a24ee12e7e3231c37820f66848db4c52"}, + {file = "pybcj-1.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d8945e8157c7fa469db110fc78579d154a31d121d14705b26d7d3ec3a471c8e"}, + {file = "pybcj-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7109177b4f77526a6ce4b565ee37483f5a5dd29bc92eaea6739b3c58618aeb7"}, + {file = "pybcj-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c48cbc9ebed137ac8759d0f2c3d12b999581dae7b4f84d974888c402f00fdb78"}, + {file = "pybcj-1.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6dccff82008e3cb5e5e639737320c02341b8718e189b9ece13f0230e0d57e7af"}, + {file = "pybcj-1.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4e68cfc4fb099e8200386ac2255a9f514b8bb056189273bcce874bda3597459"}, + {file = "pybcj-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:13747c01b60bf955878267718f28c36e2bbb81fb8495b0173b21083c7d08a4a4"}, + {file = "pybcj-1.0.6-cp311-cp311-win_arm64.whl", hash = "sha256:6f81d6106c50c5e91c16ad58584fd7ab9eb941360188547e0184b1ede9e47f1d"}, + {file = "pybcj-1.0.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f5d1dbc76f615595d7d8f3846c07f607fb1e2305d085c34556b32dacf8e88d12"}, + {file = "pybcj-1.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1398f556ed2afe16ae363a2b6e8cf6aeda3aa21861757286bc6c498278886c60"}, + {file = "pybcj-1.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e269cfc7b6286af87c5447c9f8c685f19cff011cac64947ffb4cd98919696a7f"}, + {file = "pybcj-1.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7393d0b0dcaa0b1a7850245def78fa14438809e9a3f73b1057a975229d623fd3"}, + {file = "pybcj-1.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252891698d3e01d0f60eb5adfe849038cd2d429cb9510f915a0759301f1884d"}, + {file = "pybcj-1.0.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ae5c891fcda9d5a6826a1b8e843b1e52811358594121553e6683e65b13eccce7"}, + {file = "pybcj-1.0.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eac3cb317df1cefed2783ce9cafdae61899dd02f2f4749dc0f4494a7c425745f"}, + {file = "pybcj-1.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:72ebec5cda5a48de169c2d7548ea2ce7f48732de0175d7e0e665ca7360eaa4c4"}, + {file = "pybcj-1.0.6-cp312-cp312-win_arm64.whl", hash = "sha256:8f1f75a01e45d01ecf88d31910ca1ace5d345e3bfb7c18db0af3d0c393209b63"}, + {file = "pybcj-1.0.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3e6800eb599ce766e588095eedb2a2c45a93928d1880420e8ecfad7eff0c73dc"}, + {file = "pybcj-1.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69a841ca0d3df978a2145488cec58460fa4604395321178ba421384cff26062f"}, + {file = "pybcj-1.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:887521da03302c96048803490073bd0423ff408a3adca2543c6ee86bc0af7578"}, + {file = "pybcj-1.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39a5a9a2d0e1fa4ddd2617a549c11e5022888af86dc8e29537cfee7f5761127d"}, + {file = "pybcj-1.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57757bc382f326bd93eb277a9edfc8dff6c22f480da467f0c5a5d63b9d092a41"}, + {file = "pybcj-1.0.6-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb1872b24b30d8473df433f3364e828b021964229d47a07f7bfc08496dbfd23e"}, + {file = "pybcj-1.0.6-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:5fedfeed96ab0e34207097f663b94e8c7076025c2c7af6a482e670e808ea5bb0"}, + {file = "pybcj-1.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:caefc3109bf172ad37b52e21dc16c84cf495b2ea2890cc7256cdf0188914508d"}, + {file = "pybcj-1.0.6-cp313-cp313-win_arm64.whl", hash = "sha256:b24367175528da452a19e4c55368d5c907f4584072dc6aeee8990e2a5e6910fc"}, + {file = "pybcj-1.0.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:558128fbc201c9f11c1b1df30377fab3821ebb736c28e5eaf9fff9cc9e56b806"}, + {file = "pybcj-1.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d05f4026154d77c97486d5ce04261b473e3ec8c2f7cf0f937b7baa439c616559"}, + {file = "pybcj-1.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96ce9c428800ecc0d52cec9947ee167f3a7f913cc2ba58b9a462e7f19c52ac4b"}, + {file = "pybcj-1.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05038a58d78ab15a847ed90c17d924be5b7848f27a43517dc88a5589fba1ca78"}, + {file = "pybcj-1.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:591f58891ff52585a38894b28c8b952e4c7be93f65d6d43751672cde8edeff36"}, + {file = "pybcj-1.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1f416250101631ac04705a19d78ec407d261da9dffa0e1fa1f1f2d9409ec70d"}, + {file = "pybcj-1.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27c489dd9e0d9745ebf7cd4344f23b6cb655edb2dea879ca63a0558a993e0d4b"}, + {file = "pybcj-1.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:56fe3ff939653c6b0e35aa105170af3494ee9e2469494ef1d0fa2bac3fdd99d0"}, + {file = "pybcj-1.0.6-cp39-cp39-win_arm64.whl", hash = "sha256:6c88e1a04b90547f0470e4d2bd190bbe6b73c8666d4f7196c3ca43a379a15de5"}, + {file = "pybcj-1.0.6.tar.gz", hash = "sha256:70bbe2dc185993351955bfe8f61395038f96f5de92bb3a436acb01505781f8f2"}, ] [package.extras] -check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] +check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=1.10.0)", "pygments", "readme-renderer"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pyclean" -version = "2.7.6" +version = "3.1.0" description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." optional = false -python-versions = "*" +python-versions = ">=3.5" groups = ["dev"] files = [ - {file = "pyclean-2.7.6-py3-none-any.whl", hash = "sha256:db5f74f6d2fedb79366e3260b260f5ab0053e1df28af14fc2c860800bf00fe96"}, - {file = "pyclean-2.7.6.tar.gz", hash = "sha256:cde95ad4fbc194e970fd7ad009e82718f6c1d1309b195be9018d334d79661cca"}, + {file = "pyclean-3.1.0-py3-none-any.whl", hash = "sha256:46652f7416816924cab565cd045d0c341237aced0368a3727d66b5192c16728c"}, + {file = "pyclean-3.1.0.tar.gz", hash = "sha256:d23fa900558696e08c79401c79f242fe6f7f38fd58ccd035f875a01c5bc7a6fc"}, ] [[package]] @@ -2912,164 +2997,184 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.21.0" +version = "3.23.0" description = "Cryptographic library for Python" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main"] files = [ - {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1233443f19d278c72c4daae749872a4af3787a813e05c3561c73ab0c153c7b0f"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbb07f88e277162b8bfca7134b34f18b400d84eac7375ce73117f865e3c80d4c"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e859e53d983b7fe18cb8f1b0e29d991a5c93be2c8dd25db7db1fe3bd3617f6f9"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:ef046b2e6c425647971b51424f0f88d8a2e0a2a63d3531817968c42078895c00"}, - {file = "pycryptodomex-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da76ebf6650323eae7236b54b1b1f0e57c16483be6e3c1ebf901d4ada47563b6"}, - {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c07e64867a54f7e93186a55bec08a18b7302e7bee1b02fd84c6089ec215e723a"}, - {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:56435c7124dd0ce0c8bdd99c52e5d183a0ca7fdcd06c5d5509423843f487dd0b"}, - {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65d275e3f866cf6fe891411be9c1454fb58809ccc5de6d3770654c47197acd65"}, - {file = "pycryptodomex-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5241bdb53bcf32a9568770a6584774b1b8109342bd033398e4ff2da052123832"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e"}, - {file = "pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0"}, - {file = "pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8"}, - {file = "pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c"}, - {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31"}, - {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3"}, - {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37"}, - {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e"}, - {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ef436cdeea794015263853311f84c1ff0341b98fc7908e8a70595a68cefd971"}, - {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1058e6dfe827f4209c5cae466e67610bcd0d66f2f037465daa2a29d92d952b"}, - {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba09a5b407cbb3bcb325221e346a140605714b5e880741dc9a1e9ecf1688d42"}, - {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8a9d8342cf22b74a746e3c6c9453cb0cfbb55943410e3a2619bd9164b48dc9d9"}, - {file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"}, + {file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"}, ] [[package]] name = "pydantic" -version = "2.9.2" +version = "2.11.7" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] @@ -3077,14 +3182,14 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -3092,48 +3197,49 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyinstaller" -version = "5.13.2" +version = "6.15.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false -python-versions = "<3.13,>=3.7" +python-versions = "<3.15,>=3.8" groups = ["dev"] files = [ - {file = "pyinstaller-5.13.2-py3-none-macosx_10_13_universal2.whl", hash = "sha256:16cbd66b59a37f4ee59373a003608d15df180a0d9eb1a29ff3bfbfae64b23d0f"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8f6dd0e797ae7efdd79226f78f35eb6a4981db16c13325e962a83395c0ec7420"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_i686.whl", hash = "sha256:65133ed89467edb2862036b35d7c5ebd381670412e1e4361215e289c786dd4e6"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:7d51734423685ab2a4324ab2981d9781b203dcae42839161a9ee98bfeaabdade"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:2c2fe9c52cb4577a3ac39626b84cf16cf30c2792f785502661286184f162ae0d"}, - {file = "pyinstaller-5.13.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c63ef6133eefe36c4b2f4daf4cfea3d6412ece2ca218f77aaf967e52a95ac9b8"}, - {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:aadafb6f213549a5906829bb252e586e2cf72a7fbdb5731810695e6516f0ab30"}, - {file = "pyinstaller-5.13.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b2e1c7f5cceb5e9800927ddd51acf9cc78fbaa9e79e822c48b0ee52d9ce3c892"}, - {file = "pyinstaller-5.13.2-py3-none-win32.whl", hash = "sha256:421cd24f26144f19b66d3868b49ed673176765f92fa9f7914cd2158d25b6d17e"}, - {file = "pyinstaller-5.13.2-py3-none-win_amd64.whl", hash = "sha256:ddcc2b36052a70052479a9e5da1af067b4496f43686ca3cdda99f8367d0627e4"}, - {file = "pyinstaller-5.13.2-py3-none-win_arm64.whl", hash = "sha256:27cd64e7cc6b74c5b1066cbf47d75f940b71356166031deb9778a2579bb874c6"}, - {file = "pyinstaller-5.13.2.tar.gz", hash = "sha256:c8e5d3489c3a7cc5f8401c2d1f48a70e588f9967e391c3b06ddac1f685f8d5d2"}, + {file = "pyinstaller-6.15.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:9f00c71c40148cd1e61695b2c6f1e086693d3bcf9bfa22ab513aa4254c3b966f"}, + {file = "pyinstaller-6.15.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cbcc8eb77320c60722030ac875883b564e00768fe3ff1721c7ba3ad0e0a277e9"}, + {file = "pyinstaller-6.15.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c33e6302bc53db2df1104ed5566bd980b3e0ee7f18416a6e3caa908c12a54542"}, + {file = "pyinstaller-6.15.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:eb902d0fed3bb1f8b7190dc4df5c11f3b59505767e0d56d1ed782b853938bbf3"}, + {file = "pyinstaller-6.15.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b4df862adae7cf1f08eff53c43ace283822447f7f528f72e4f94749062712f15"}, + {file = "pyinstaller-6.15.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b9ebf16ed0f99016ae8ae5746dee4cb244848a12941539e62ce2eea1df5a3f95"}, + {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:22193489e6a22435417103f61e7950363bba600ef36ec3ab1487303668c81092"}, + {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:18f743069849dbaee3e10900385f35795a5743eabab55e99dcc42f204e40a0db"}, + {file = "pyinstaller-6.15.0-py3-none-win32.whl", hash = "sha256:60da8f1b5071766b45c0f607d8bc3d7e59ba2c3b262d08f2e4066ba65f3544a2"}, + {file = "pyinstaller-6.15.0-py3-none-win_amd64.whl", hash = "sha256:cbea297e16eeda30b41c300d6ec2fd2abea4dbd8d8a32650eeec36431c94fcd9"}, + {file = "pyinstaller-6.15.0-py3-none-win_arm64.whl", hash = "sha256:f43c035621742cf2d19b84308c60e4e44e72c94786d176b8f6adcde351b5bd98"}, + {file = "pyinstaller-6.15.0.tar.gz", hash = "sha256:a48fc4644ee4aa2aa2a35e7b51f496f8fbd7eecf6a2150646bbf1613ad07bc2d"}, ] [package.dependencies] altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} -pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2021.4" +packaging = ">=22.0" +pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2025.8" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" [package.extras] -encryption = ["tinyaes (>=1.0.0)"] +completion = ["argcomplete"] hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2024.9" +version = "2025.8" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2024.9-py3-none-any.whl", hash = "sha256:1ddf9ba21d586afa84e505bb5c65fca10b22500bf3fdb89ee2965b99da53b891"}, - {file = "pyinstaller_hooks_contrib-2024.9.tar.gz", hash = "sha256:4793869f370d1dc4806c101efd2890e3c3e703467d8d27bb5a3db005ebfb008d"}, + {file = "pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064"}, + {file = "pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c"}, ] [package.dependencies] @@ -3142,14 +3248,14 @@ setuptools = ">=42.0.0" [[package]] name = "pymdown-extensions" -version = "10.11.2" +version = "10.16.1" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf"}, - {file = "pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049"}, + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] @@ -3157,18 +3263,18 @@ markdown = ">=3.6" pyyaml = "*" [package.extras] -extra = ["pygments (>=2.12)"] +extra = ["pygments (>=2.19.1)"] [[package]] name = "pyparsing" -version = "3.2.0" +version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, - {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, ] [package.extras] @@ -3192,113 +3298,98 @@ pywin32 = ">=223" [[package]] name = "pyppmd" -version = "1.1.0" +version = "1.2.0" description = "PPMd compression/decompression library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5cd428715413fe55abf79dc9fc54924ba7e518053e1fc0cbdf80d0d99cf1442"}, - {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e96cc43f44b7658be2ea764e7fa99c94cb89164dbb7cdf209178effc2168319"}, - {file = "pyppmd-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd20142869094bceef5ab0b160f4fff790ad1f612313a1e3393a51fc3ba5d57e"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4f9b51e45c11e805e74ea6f6355e98a6423b5bbd92f45aceee24761bdc3d3b8"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459f85e928fb968d0e34fb6191fd8c4e710012d7d884fa2b317b2e11faac7c59"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f73cf2aaf60477eef17f5497d14b6099d8be9748390ad2b83d1c88214d050c05"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea3ae0e92c0b5345cd3a4e145e01bbd79c2d95355481ea5d833b5c0cb202a2d"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:775172c740133c0162a01c1a5443d0e312246881cdd6834421b644d89a634b91"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14421030f1d46f69829698bdd960698a3b3df0925e3c470e82cfcdd4446b7bc1"}, - {file = "pyppmd-1.1.0-cp310-cp310-win32.whl", hash = "sha256:b691264f9962532aca3bba5be848b6370e596d0a2ca722c86df388be08d0568a"}, - {file = "pyppmd-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:216b0d969a3f06e35fbfef979706d987d105fcb1e37b0b1324f01ee143719c4a"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f8c51044ee4df1b004b10bf6b3c92f95ea86cfe1111210d303dca44a56e4282"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac25b3a13d1ac9b8f0bde46952e10848adc79d932f2b548a6491ef8825ae0045"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8d3003eebe6aabe22ba744a38a146ed58a25633420d5da882b049342b7c8036"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c520656bc12100aa6388df27dd7ac738577f38bf43f4a4bea78e1861e579ea5"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c2a3e807028159a705951f5cb5d005f94caed11d0984e59cc50506de543e22d"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8a2447e69444703e2b273247bfcd4b540ec601780eff07da16344c62d2993d"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9e0c8053e69cad6a92a0889b3324f567afc75475b4f54727de553ac4fc85780"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5938d256e8d2a2853dc3af8bb58ae6b4a775c46fc891dbe1826a0b3ceb624031"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1ce5822d8bea920856232ccfb3c26b56b28b6846ea1b0eb3d5cb9592a026649e"}, - {file = "pyppmd-1.1.0-cp311-cp311-win32.whl", hash = "sha256:2a9e894750f2a52b03e3bc0d7cf004d96c3475a59b1af7e797d808d7d29c9ffe"}, - {file = "pyppmd-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:969555c72e72fe2b4dd944127521a8f2211caddb5df452bbc2506b5adfac539e"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d6ef8fd818884e914bc209f7961c9400a4da50d178bba25efcef89f09ec9169"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95f28e2ecf3a9656bd7e766aaa1162b6872b575627f18715f8b046e8617c124a"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f3557ea65ee417abcdf5f49d35df00bb9f6f252639cae57aeefcd0dd596133"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e84b25d088d7727d50218f57f92127cdb839acd6ec3de670b6680a4cf0b2d2a"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99ed42891986dac8c2ecf52bddfb777900233d867aa18849dbba6f3335600466"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6fe69b82634488ada75ba07efb90cd5866fa3d64a2c12932b6e8ae207a14e5f"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60981ffde1fe6ade750b690b35318c41a1160a8505597fda2c39a74409671217"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46e8240315476f57aac23d71e6de003e122b65feba7c68f4cc46a089a82a7cd4"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0308e2e76ecb4c878a18c2d7a7c61dbca89b4ef138f65d5f5ead139154dcdea"}, - {file = "pyppmd-1.1.0-cp312-cp312-win32.whl", hash = "sha256:b4fa4c27dc1314d019d921f2aa19e17f99250557e7569eeb70e180558f46af74"}, - {file = "pyppmd-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c269d21e15f4175df27cf00296476097af76941f948734c642d7fb6e85b9b3b9"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a04ef5fd59818b035855723af85ce008c8191d31216706ffcbeedc505efca269"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e3ebcf5f95142268afa5cc46457d9dab2d29a3ccfd020a1129dd9d6bd021be1"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad046a9525d1f52e93bc642a4cec0bf344a3ba1a15923e424e7a50f8ca003d8"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169e5023c86ed1f7587961900f58aa78ad8a3d59de1e488a2228b5ba3de52402"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baf798e76edd9da975cc536f943756a1b1755eb8ed87371f86f76d7c16e8d034"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63be8c068879194c1e7548d0c57f54a4d305ba204cd0c7499b678f0aee893ef"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5fc178a3c21af78858acbac9782fca6a927267694c452e0882c55fec6e78319"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:28a1ab1ef0a31adce9b4c837b7b9acb01ce8f1f702ff3ff884f03d21c2f6b9bb"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5fef43bfe98ada0a608adf03b2d205e071259027ab50523954c42eef7adcef67"}, - {file = "pyppmd-1.1.0-cp38-cp38-win32.whl", hash = "sha256:6b980902797eab821299a1c9f42fa78eff2826a6b0b0f6bde8a621f9765ffd55"}, - {file = "pyppmd-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:80cde69013f357483abe0c3ff30c55dc5e6b4f72b068f91792ce282c51dc0bff"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aeea1bf585c6b8771fa43a6abd704da92f8a46a6d0020953af15d7f3c82e48c"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7759bdb137694d4ab0cfa5ff2c75c212d90714c7da93544694f68001a0c38e12"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db64a4fe956a2e700a737a1d019f526e6ccece217c163b28b354a43464cc495b"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f788ae8f5a9e79cd777b7969d3401b2a2b87f47abe306c2a03baca30595e9bd"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:324a178935c140210fca2043c688b77e79281da8172d2379a06e094f41735851"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363030bbcb7902fb9eeb59ffc262581ca5dd7790ba950328242fd2491c54d99b"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:31b882584f86440b0ff7906385c9f9d9853e5799197abaafdae2245f87d03f01"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b991b4501492ec3380b605fe30bee0b61480d305e98519d81c2a658b2de01593"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6108044d943b826f97a9e79201242f61392d6c1fadba463b2069c4e6bc961e1"}, - {file = "pyppmd-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c45ce2968b7762d2cacf622b0a8f260295c6444e0883fd21a21017e3eaef16ed"}, - {file = "pyppmd-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5289f32ab4ec5f96a95da51309abd1769f928b0bff62047b3bc25c878c16ccb"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad5da9f7592158e6b6b51d7cd15e536d8b23afbb4d22cba4e5744c7e0a3548b1"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc6543e7d12ef0a1466d291d655e3d6bca59c7336dbb53b62ccdd407822fb52b"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5e4008a45910e3c8c227f6f240de67eb14454c015dc3d8060fc41e230f395d3"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9301fa39d1fb0ed09a10b4c5d7f0074113e96a1ead16ba7310bedf95f7ef660c"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:59521a3c6028da0cb5780ba16880047b00163432a6b975da2f6123adfc1b0be8"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d7ec02f1778dd68547e497625d66d7858ce10ea199146eb1d80ee23ba42954be"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f062ca743f9b99fe88d417b4d351af9b4ff1a7cbd3d765c058bb97de976d57f1"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088e326b180a0469ac936849f5e1e5320118c22c9d9e673e9c8551153b839c84"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:897fa9ab5ff588a1000b8682835c5acf219329aa2bbfec478100e57d1204eeab"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3af4338cc48cd59ee213af61d936419774a0f8600b9aa2013cd1917b108424f0"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cce8cd2d4ceebe2dbf41db6dfebe4c2e621314b3af8a2df2cba5eb5fa277f122"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e57927dbcb91fb6290a41cd83743b91b9d85858efb16a0dd34fac208ee1c6b"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:435317949a6f35e54cdf08e0af6916ace427351e7664ac1593980114668f0aaa"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f66b0d0e32b8fb8707f1d2552f13edfc2917e8ed0bdf4d62e2ce190d2c70834"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:650a663a591e06fb8096c213f4070b158981c8c3bf9c166ce7e4c360873f2750"}, - {file = "pyppmd-1.1.0.tar.gz", hash = "sha256:1d38ce2e4b7eb84b53bc8a52380b94f66ba6c39328b8800b30c2b5bf31693973"}, + {file = "pyppmd-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4a25d8b2a71e0cc6f34475c36450e905586b13d0c88fb28471655c215f370908"}, + {file = "pyppmd-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9dd8a6261152591a352d91e5e52c16b81fa760f64c361a7afb24a1f3b5e048"}, + {file = "pyppmd-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2cd2694f43720fa1304c1fa31b8a1e7d80162f045e91569fb93473277c2747b8"}, + {file = "pyppmd-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0354919ab0f4065d76c64ad0dc21f14116651a2124cf4915b96c4e76d9caf470"}, + {file = "pyppmd-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:416c15576924ff9d2852fbe53d162c946e0466ce79d8a03a058e6f09a54934f0"}, + {file = "pyppmd-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dcdd5bf53f562af2a9be76739be69c9de080dfa59a4c4a8bcc4a163f9c5cb53e"}, + {file = "pyppmd-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c67196af6cfcc68e72a8fffbc332d743327bb9323cb7f3709e27185e743c7272"}, + {file = "pyppmd-1.2.0-cp310-cp310-win32.whl", hash = "sha256:d529c78382a2315db22c93e1c831231ee3fd2ad5a352f61496d72474558c6b00"}, + {file = "pyppmd-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f19285ae4dd20bb917c4fd177f0911847feb3abada91cec5fd5d9d5f1b9f3e0"}, + {file = "pyppmd-1.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:30068ed6da301f6ba25219f96d828f3c3a80ca227647571d21c7704301e095e6"}, + {file = "pyppmd-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a5f0b78d68620ffb51c46c34c9e0ec02c40bb68e903e6c3ce02870c528164af"}, + {file = "pyppmd-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5f1ee49b88fd2e58a144b1ae0da9c2fe0dabc1962531da9475badbed6fba61fc"}, + {file = "pyppmd-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c98697fea3f3baf5ffc759fd41c766d708ff3fba7379776031077527873ce4ac"}, + {file = "pyppmd-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a3087d7ee6fc35db0bfecabd1df4615f2a9d58a56af61f5fc18b9ce2b379cbf"}, + {file = "pyppmd-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fe10feb24a92e673b68aca5d945564232d09e25a4e185899e0c657096ae695"}, + {file = "pyppmd-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aa40c982d1df515cd4cb366d3e1ae95ce22f3c20e6b8b2d31aa492679f7ad78c"}, + {file = "pyppmd-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a5c03dd85da64a237c601dd690d8eb95951b7c2eef91b89e110eb208010c6035"}, + {file = "pyppmd-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c577f3dadd514979255e9df6eb89a63409d0e91855bb8c0969ffcd67d5d4f124"}, + {file = "pyppmd-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29dfb7aaf4b49ebc09d726fcdeabbce1cb21e9cf3a055244bb1384b8b51dd3b"}, + {file = "pyppmd-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:bf26c2def22322135fbaaa3de3c0963062c1835bd43d595478e3a2a674466a1a"}, + {file = "pyppmd-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d28cc9febcf37f2ff08b9e25d472de529e8973119c0a3279603b1915c809dd45"}, + {file = "pyppmd-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0f07d5376e1f699d09fbb9139562e5b72a347100aecaa73b688fa08461b3c118"}, + {file = "pyppmd-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:874f52eae03647b653aa34476f4e23c4c30458245c0eb7aa7fb290940abbd5b9"}, + {file = "pyppmd-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abafffb3d5b292924eafd8214ad80487400cf358c4e9dc2ac6c21d2c651c5ee2"}, + {file = "pyppmd-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e955de43991346d4ccb28a74fb4c80cadecf72a6724705301fe1adb569689fe"}, + {file = "pyppmd-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14ed0846c3bcee506555cd943db950d5787a6ffa1089e05deced010759ef1fe5"}, + {file = "pyppmd-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3caef2fb93a63d696b21e5ff72cb2955687b5dfcbed1938936334f9f7737fcd3"}, + {file = "pyppmd-1.2.0-cp312-cp312-win32.whl", hash = "sha256:011c813389476e84387599ad4aa834100e888c6608a6e7d6f07ea7cbac8a8e65"}, + {file = "pyppmd-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:42c7c9050b44b713676d255f0c212b8ff5c0463821053960c89292cf6b6221cc"}, + {file = "pyppmd-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:5768bff11936047613bcb91ee466f21779efc24360bd7953bd338b32da88577a"}, + {file = "pyppmd-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4aa8ffca1727872923d2673045975bca571eb810cf14a21d048648da5237369b"}, + {file = "pyppmd-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6dc00f0ce9f79e1c1c87d9998220a714ab8216873b6c996776b88ab23efe05ac"}, + {file = "pyppmd-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d437881763ffd0d19079402f50e7f4aad5895e3cd5312d095edef0b32dac3aef"}, + {file = "pyppmd-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c763f2e3a011d5e96dfa0195f38accce9a14d489725a3d3641a74addbb5b72"}, + {file = "pyppmd-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38e3835a1951d18dd273000c870a4eb2804c20c400aa3c72231449f300cedf19"}, + {file = "pyppmd-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c76b8881fc087e70338b1cccd452bd12566206587a0d0d8266ba2833be902194"}, + {file = "pyppmd-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8b43e299310e27af5a4bc505bcc87d10cfc38ae28e5ed1f6a779d811705e5ad6"}, + {file = "pyppmd-1.2.0-cp313-cp313-win32.whl", hash = "sha256:4b3249256f8a7ecdd36477f277b232a46ee2c8ca280b23faaeacb7f50cab949a"}, + {file = "pyppmd-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:625896f5da7038fe7145907b68b0b58f7c13b88ad6bbfdc1c20c05654c17fa6c"}, + {file = "pyppmd-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:bec8abbf1edb0300c0a2e4f1bbad6a96154de3e507a2b054a0b1187f1c2e4982"}, + {file = "pyppmd-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b5c3284be4dccebb87d81c14b148c81e035356cd01a29889736c75672f6187d"}, + {file = "pyppmd-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40bfa26fdb3332a6a8d90fe1f6e0d9f489505a014911b470d66f2f79caea6d61"}, + {file = "pyppmd-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75b173bbc9164cdc6fb257d3480269cc26b1eb102ad72281a98cf90e0f7dc860"}, + {file = "pyppmd-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91534eb8c9c0bff9d6c6ec5eb5119a583d31bb9f8cf208d5a925b4e2293c9a7b"}, + {file = "pyppmd-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edc4fcd928bf6219bcddb8230a5830e33a35b684b16ca3e8d1357b17029a9ef7"}, + {file = "pyppmd-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5ff515c2c3544096fe524f341c244787d6449b36692d27131bf74d5075e5c83b"}, + {file = "pyppmd-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:af9be87228cba6b543531260f44675a23b4a1527158a44162dce186157cb13d9"}, + {file = "pyppmd-1.2.0-cp39-cp39-win32.whl", hash = "sha256:3674b5eba0e312b9af987ec7e6af59248f54db9a7f5ca63add5365d6c6639e9e"}, + {file = "pyppmd-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:cff27496fd164b587f150abba9524cae81629adbd2e9472f09e7b2b24b2d4939"}, + {file = "pyppmd-1.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:c9d0f5a903045ee6b399f5fb308e192e39f8f1f551b61441a595676d95dc76ad"}, + {file = "pyppmd-1.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86e252979fc5ae2492ebb46ed0eed0625a46a2cce519c4616b870eab58d77fb7"}, + {file = "pyppmd-1.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9095d8b098ce8cb5c1e404843a16e5167fb5bdebb4d6aed259d43dd2d73cfca3"}, + {file = "pyppmd-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:064307c7fec7bdf3da63f5e28c0f1c5cb5c9bf888c1b268c6df3c131391ab345"}, + {file = "pyppmd-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c012c17a53b6d9744e0514b17b0c4169c5f21fb54b4db7a0119bc2d7b3fcc609"}, + {file = "pyppmd-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0877758ffa73b2e9d2f93b698e17336a4d8acab8d9a3d17cd7960aec08347387"}, + {file = "pyppmd-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac0960d2d0a1738af3ca3f27c6ed6eead38518d77875a47b2b4aae90ae933f4"}, + {file = "pyppmd-1.2.0.tar.gz", hash = "sha256:cc04af92f1d26831ec96963439dfb27c96467b5452b94436a6af696649a121fd"}, ] [package.extras] -check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-isort", "isort (>=5.0.3)", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] -docs = ["sphinx (>=2.3)", "sphinx-rtd-theme"] +check = ["check-manifest", "flake8", "flake8-black", "flake8-isort", "mypy (>=1.10.0)", "pygments", "readme-renderer"] +docs = ["sphinx", "sphinx_rtd_theme"] fuzzer = ["atheris", "hypothesis"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" @@ -3317,28 +3408,32 @@ six = ">=1.5" [[package]] name = "pywin32" -version = "310" +version = "311" description = "Python for Window Extensions" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, - {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, - {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, - {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, - {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, - {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, - {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, - {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, - {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, - {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, - {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, - {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, - {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, - {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, - {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, - {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, ] [[package]] @@ -3419,14 +3514,14 @@ files = [ [[package]] name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, ] [package.dependencies] @@ -3434,111 +3529,125 @@ pyyaml = "*" [[package]] name = "pyzstd" -version = "0.16.2" +version = "0.17.0" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" groups = ["main"] files = [ - {file = "pyzstd-0.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:637376c8f8cbd0afe1cab613f8c75fd502bd1016bf79d10760a2d5a00905fe62"}, - {file = "pyzstd-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e7a7118cbcfa90ca2ddbf9890c7cb582052a9a8cf2b7e2c1bbaf544bee0f16a"}, - {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74cb1ba05876179525144511eed3bd5a509b0ab2b10632c1215a85db0834dfd"}, - {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c084dde218ffbf112e507e72cbf626b8f58ce9eb23eec129809e31037984662"}, - {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4646459ebd3d7a59ddbe9312f020bcf7cdd1f059a2ea07051258f7af87a0b31"}, - {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14bfc2833cc16d7657fc93259edeeaa793286e5031b86ca5dc861ba49b435fce"}, - {file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f27d488f19e5bf27d1e8aa1ae72c6c0a910f1e1ffbdf3c763d02ab781295dd27"}, - {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e134ca968ff7dcfa8b7d433318f01d309b74ee87e0d2bcadc117c08e1c80db"}, - {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6b5f64cd3963c58b8f886eb6139bb8d164b42a74f8a1bb95d49b4804f4592d61"}, - {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b4a8266871b9e0407f9fd8e8d077c3558cf124d174e6357b523d14f76971009"}, - {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1bb19f7acac30727354c25125922aa59f44d82e0e6a751df17d0d93ff6a73853"}, - {file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3008325b7368e794d66d4d98f2ee1d867ef5afd09fd388646ae02b25343c420d"}, - {file = "pyzstd-0.16.2-cp310-cp310-win32.whl", hash = "sha256:66f2d5c0bbf5bf32c577aa006197b3525b80b59804450e2c32fbcc2d16e850fd"}, - {file = "pyzstd-0.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fe5f5459ebe1161095baa7a86d04ab625b35148f6c425df0347ed6c90a2fd58"}, - {file = "pyzstd-0.16.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1bdbe7f01c7f37d5cd07be70e32a84010d7dfd6677920c0de04cf7d245b60d"}, - {file = "pyzstd-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1882a3ceaaf9adc12212d587d150ec5e58cfa9a765463d803d739abbd3ac0f7a"}, - {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea46a8b9d60f6a6eba29facba54c0f0d70328586f7ef0da6f57edf7e43db0303"}, - {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7865bc06589cdcecdede0deefe3da07809d5b7ad9044c224d7b2a0867256957"}, - {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52f938a65b409c02eb825e8c77fc5ea54508b8fc44b5ce226db03011691ae8cc"}, - {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97620d3f53a0282947304189deef7ca7f7d0d6dfe15033469dc1c33e779d5e5"}, - {file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c40e9983d017108670dc8df68ceef14c7c1cf2d19239213274783041d0e64c"}, - {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7cd4b3b2c6161066e4bde6af1cf78ed3acf5d731884dd13fdf31f1db10830080"}, - {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:454f31fd84175bb203c8c424f2255a343fa9bd103461a38d1bf50487c3b89508"}, - {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5ef754a93743f08fb0386ce3596780bfba829311b49c8f4107af1a4bcc16935d"}, - {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be81081db9166e10846934f0e3576a263cbe18d81eca06e6a5c23533f8ce0dc6"}, - {file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:738bcb2fa1e5f1868986f5030955e64de53157fa1141d01f3a4daf07a1aaf644"}, - {file = "pyzstd-0.16.2-cp311-cp311-win32.whl", hash = "sha256:0ea214c9b97046867d1657d55979021028d583704b30c481a9c165191b08d707"}, - {file = "pyzstd-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:c17c0fc02f0e75b0c7cd21f8eaf4c6ce4112333b447d93da1773a5f705b2c178"}, - {file = "pyzstd-0.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4081fd841a9efe9ded7290ee7502dbf042c4158b90edfadea3b8a072c8ec4e1"}, - {file = "pyzstd-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd3fa45d2aeb65367dd702806b2e779d13f1a3fa2d13d5ec777cfd09de6822de"}, - {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8b5f0d2c07994a5180d8259d51df6227a57098774bb0618423d7eb4a7303467"}, - {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60c9d25b15c7ae06ed5d516d096a0d8254f9bed4368b370a09cccf191eaab5cb"}, - {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29acf31ce37254f6cad08deb24b9d9ba954f426fa08f8fae4ab4fdc51a03f4ae"}, - {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec77612a17697a9f7cf6634ffcee616eba9b997712fdd896e77fd19ab3a0618"}, - {file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:313ea4974be93be12c9a640ab40f0fc50a023178aae004a8901507b74f190173"}, - {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e91acdefc8c2c6c3b8d5b1b5fe837dce4e591ecb7c0a2a50186f552e57d11203"}, - {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:929bd91a403539e72b5b5cb97f725ac4acafe692ccf52f075e20cd9bf6e5493d"}, - {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:740837a379aa32d110911ebcbbc524f9a9b145355737527543a884bd8777ca4f"}, - {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:adfc0e80dd157e6d1e0b0112c8ecc4b58a7a23760bd9623d74122ef637cfbdb6"}, - {file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79b183beae1c080ad3dca39019e49b7785391947f9aab68893ad85d27828c6e7"}, - {file = "pyzstd-0.16.2-cp312-cp312-win32.whl", hash = "sha256:b8d00631a3c466bc313847fab2a01f6b73b3165de0886fb03210e08567ae3a89"}, - {file = "pyzstd-0.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:c0d43764e9a60607f35d8cb3e60df772a678935ab0e02e2804d4147377f4942c"}, - {file = "pyzstd-0.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3ae9ae7ad730562810912d7ecaf1fff5eaf4c726f4b4dfe04784ed5f06d7b91f"}, - {file = "pyzstd-0.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2ce8d3c213f76a564420f3d0137066ac007ce9fb4e156b989835caef12b367a7"}, - {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2c14dac23c865e2d78cebd9087e148674b7154f633afd4709b4cd1520b99a61"}, - {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4527969d66a943e36ef374eda847e918077de032d58b5df84d98ffd717b6fa77"}, - {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd8256149b88e657e99f31e6d4b114c8ff2935951f1d8bb8e1fe501b224999c0"}, - {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bd1f1822d65c9054bf36d35307bf8ed4aa2d2d6827431761a813628ff671b1d"}, - {file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6733f4d373ec9ad2c1976cf06f973a3324c1f9abe236d114d6bb91165a397d"}, - {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7bec165ab6524663f00b69bfefd13a46a69fed3015754abaf81b103ec73d92c6"}, - {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4460fa6949aac6528a1ad0de8871079600b12b3ef4db49316306786a3598321"}, - {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75df79ea0315c97d88337953a17daa44023dbf6389f8151903d371513f503e3c"}, - {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:93e1d45f4a196afb6f18682c79bdd5399277ead105b67f30b35c04c207966071"}, - {file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:075e18b871f38a503b5d23e40a661adfc750bd4bd0bb8b208c1e290f3ceb8fa2"}, - {file = "pyzstd-0.16.2-cp313-cp313-win32.whl", hash = "sha256:9e4295eb299f8d87e3487852bca033d30332033272a801ca8130e934475e07a9"}, - {file = "pyzstd-0.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:18deedc70f858f4cf574e59f305d2a0678e54db2751a33dba9f481f91bc71c28"}, - {file = "pyzstd-0.16.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9892b707ef52f599098b1e9528df0e7849c5ec01d3e8035fb0e67de4b464839"}, - {file = "pyzstd-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbd647864341f3c174c4a6d7f20e6ea6b4be9d840fb900dc0faf0849561badc"}, - {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ac2c15656cc6194c4fed1cb0e8159f9394d4ea1d58be755448743d2ec6c9c4"}, - {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b239fb9a20c1be3374b9a2bd183ba624fd22ad7a3f67738c0d80cda68b4ae1d3"}, - {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc52400412cdae2635e0978b8d6bcc0028cc638fdab2fd301f6d157675d26896"}, - {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b766a6aeb8dbb6c46e622e7a1aebfa9ab03838528273796941005a5ce7257b1"}, - {file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd4b8676052f9d59579242bf3cfe5fd02532b6a9a93ab7737c118ae3b8509dc"}, - {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c6c0a677aac7c0e3d2d2605d4d68ffa9893fdeeb2e071040eb7c8750969d463"}, - {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:15f9c2d612e7e2023d68d321d1b479846751f792af89141931d44e82ae391394"}, - {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:11740bff847aad23beef4085a1bb767d101895881fe891f0a911aa27d43c372c"}, - {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b9067483ebe860e4130a03ee665b3d7be4ec1608b208e645d5e7eb3492379464"}, - {file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:988f0ba19b14c2fe0afefc444ac1edfb2f497b7d7c3212b2f587504cc2ec804e"}, - {file = "pyzstd-0.16.2-cp39-cp39-win32.whl", hash = "sha256:8855acb1c3e3829030b9e9e9973b19e2d70f33efb14ad5c474b4d086864c959c"}, - {file = "pyzstd-0.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:018e88378df5e76f5e1d8cf4416576603b6bc4a103cbc66bb593eaac54c758de"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b631117b97a42ff6dfd0ffc885a92fff462d7c34766b28383c57b996f863338"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:56493a3fbe1b651a02102dd0902b0aa2377a732ff3544fb6fb3f114ca18db52f"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1eae9bdba4a1e5d3181331f403114ff5b8ce0f4b569f48eba2b9beb2deef1e4"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1be6972391c8aeecc7e61feb96ffc8e77a401bcba6ed994e7171330c45a1948"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:761439d687e3a5687c2ff5c6a1190e1601362a4a3e8c6c82ff89719d51d73e19"}, - {file = "pyzstd-0.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f5fbdb8cf31b60b2dc586fecb9b73e2f172c21a0b320ed275f7b8d8a866d9003"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:183f26e34f9becf0f2db38be9c0bfb136753d228bcb47c06c69175901bea7776"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:88318b64b5205a67748148d6d244097fa6cf61fcea02ad3435511b9e7155ae16"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73142aa2571b6480136a1865ebda8257e09eabbc8bcd54b222202f6fa4febe1e"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d3f8877c29a97f1b1bba16f3d3ab01ad10ad3da7bad317aecf36aaf8848b37c"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f25754562473ac7de856b8331ebd5964f5d85601045627a5f0bb0e4e899990"}, - {file = "pyzstd-0.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6ce17e84310080c55c02827ad9bb17893c00a845c8386a328b346f814aabd2c1"}, - {file = "pyzstd-0.16.2.tar.gz", hash = "sha256:179c1a2ea1565abf09c5f2fd72f9ce7c54b2764cf7369e05c0bfd8f1f67f63d2"}, + {file = "pyzstd-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ac857abb4c4daea71f134e74af7fe16bcfeec40911d13cf9128ddc600d46d92"}, + {file = "pyzstd-0.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d84e8d1cbecd3b661febf5ca8ce12c5e112cfeb8401ceedfb84ab44365298ac"}, + {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f829fa1e7daac2e45b46656bdee13923150f329e53554aeaef75cceec706dd8c"}, + {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994de7a13bb683c190a1b2a0fb99fe0c542126946f0345360582d7d5e8ce8cda"}, + {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3eb213a22823e2155aa252d9093c62ac12d7a9d698a4b37c5613f99cb9de327"}, + {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c451cfa31e70860334cc7dffe46e5178de1756642d972bc3a570fc6768673868"}, + {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d66dc6f15249625e537ea4e5e64c195f50182556c3731f260b13c775b7888d6b"}, + {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:308d4888083913fac2b7b6f4a88f67c0773d66db37e6060971c3f173cfa92d1e"}, + {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a3b636f37af9de52efb7dd2d2f15deaeabdeeacf8e69c29bf3e7e731931e6d66"}, + {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4c07391c67b496d851b18aa29ff552a552438187900965df57f64d5cf2100c40"}, + {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e8bd12a13313ffa27347d7abe20840dcd2092852ab835a8e86008f38f11bd5ac"}, + {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e27bfab45f9cdab0c336c747f493a00680a52a018a8bb7a1f787ddde4b29410"}, + {file = "pyzstd-0.17.0-cp310-cp310-win32.whl", hash = "sha256:7370c0978edfcb679419f43ec504c128463858a7ea78cf6d0538c39dfb36fce3"}, + {file = "pyzstd-0.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:564f7aa66cda4acd9b2a8461ff0c6a6e39a977be3e2e7317411a9f7860d7338d"}, + {file = "pyzstd-0.17.0-cp310-cp310-win_arm64.whl", hash = "sha256:fccff3a37fa4c513fe1ebf94cb9dc0369c714da22b5671f78ddcbc7ec8f581cc"}, + {file = "pyzstd-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06d1e7afafe86b90f3d763f83d2f6b6a437a8d75119fe1ff52b955eb9df04eaa"}, + {file = "pyzstd-0.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc827657f644e4510211b49f5dab6b04913216bc316206d98f9a75214361f16e"}, + {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecffadaa2ee516ecea3e432ebf45348fa8c360017f03b88800dd312d62ecb063"}, + {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:596de361948d3aad98a837c98fcee4598e51b608f7e0912e0e725f82e013f00f"}, + {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd3a8d0389c103e93853bf794b9a35ac5d0d11ca3e7e9f87e3305a10f6dfa6b2"}, + {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1356f72c7b8bb99b942d582b61d1a93c5065e66b6df3914dac9f2823136c3228"}, + {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f514c339b013b0b0a2ed8ea6e44684524223bd043267d7644d7c3a70e74a0dd"}, + {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4de16306821021c2d82a45454b612e2a8683d99bfb98cff51a883af9334bea0"}, + {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:aeb9759c04b6a45c1b56be21efb0a738e49b0b75c4d096a38707497a7ff2be82"}, + {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a5b31ddeada0027e67464d99f09167cf08bab5f346c3c628b2d3c84e35e239a"}, + {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8338e4e91c52af839abcf32f1f65f3b21e2597ffe411609bdbdaf10274991bd0"}, + {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628e93862feb372b4700085ec4d1d389f1283ac31900af29591ae01019910ff3"}, + {file = "pyzstd-0.17.0-cp311-cp311-win32.whl", hash = "sha256:c27773f9c95ebc891cfcf1ef282584d38cde0a96cb8d64127953ad752592d3d7"}, + {file = "pyzstd-0.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:c043a5766e00a2b7844705c8fa4563b7c195987120afee8f4cf594ecddf7e9ac"}, + {file = "pyzstd-0.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:efd371e41153ef55bf51f97e1ce4c1c0b05ceb59ed1d8972fc9aa1e9b20a790f"}, + {file = "pyzstd-0.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ac330fc4f64f97a411b6f3fc179d2fe3050b86b79140e75a9a6dd9d6d82087f"}, + {file = "pyzstd-0.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:725180c0c4eb2e643b7048ebfb45ddf43585b740535907f70ff6088f5eda5096"}, + {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c20fe0a60019685fa1f7137cb284f09e3f64680a503d9c0d50be4dd0a3dc5ec"}, + {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d97f7aaadc3b6e2f8e51bfa6aa203ead9c579db36d66602382534afaf296d0db"}, + {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42dcb34c5759b59721997036ff2d94210515d3ef47a9de84814f1c51a1e07e8a"}, + {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6bf05e18be6f6c003c7129e2878cffd76fcbebda4e7ebd7774e34ae140426cbf"}, + {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f7c3a5144aa4fbccf37c30411f6b1db4c0f2cb6ad4df470b37929bffe6ca0"}, + {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9efd4007f8369fd0890701a4fc77952a0a8c4cb3bd30f362a78a1adfb3c53c12"}, + {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f8add139b5fd23b95daa844ca13118197f85bd35ce7507e92fcdce66286cc34"}, + {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:259a60e8ce9460367dcb4b34d8b66e44ca3d8c9c30d53ed59ae7037622b3bfc7"}, + {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:86011a93cc3455c5d2e35988feacffbf2fa106812a48e17eb32c2a52d25a95b3"}, + {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:425c31bc3de80313054e600398e4f1bd229ee61327896d5d015e2cd0283c9012"}, + {file = "pyzstd-0.17.0-cp312-cp312-win32.whl", hash = "sha256:7c4b88183bb36eb2cebbc0352e6e9fe8e2d594f15859ae1ef13b63ebc58be158"}, + {file = "pyzstd-0.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c31947e0120468342d74e0fa936d43f7e1dad66a2262f939735715aa6c730e8"}, + {file = "pyzstd-0.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1d0346418abcef11507356a31bef5470520f6a5a786d4e2c69109408361b1020"}, + {file = "pyzstd-0.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6cd1a1d37a7abe9c01d180dad699e3ac3889e4f48ac5dcca145cc46b04e9abd2"}, + {file = "pyzstd-0.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a44fd596eda06b6265dc0358d5b309715a93f8e96e8a4b5292c2fe0e14575b3"}, + {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a99b37453f92f0691b2454d0905bbf2f430522612f6f12bbc81133ad947eb97"}, + {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63d864e9f9e624a466070a121ace9d9cbf579eac4ed575dee3b203ab1b3cbeee"}, + {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e58bc02b055f96d1f83c791dd197d8c80253275a56cd84f917a006e9f528420d"}, + {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e62df7c0ba74618481149c849bc3ed7d551b9147e1274b4b3170bbcc0bfcc0a"}, + {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42ecdd7136294f1becb8e57441df00eaa6dfd7444a8b0c96a1dfba5c81b066e7"}, + {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:be07a57af75f99fc39b8e2d35f8fb823ecd7ef099cd1f6203829a5094a991ae2"}, + {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0d41e6f7ec2a70dab4982157a099562de35a6735c890945b4cebb12fb7eb0be0"}, + {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f482d906426756e7cc9a43f500fee907e1b3b4e9c04d42d58fb1918c6758759b"}, + {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:827327b35605265e1d05a2f6100244415e8f2728bb75c951736c9288415908d7"}, + {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a55008f80e3390e4f37bd9353830f1675f271d13d6368d2f1dc413b7c6022b3"}, + {file = "pyzstd-0.17.0-cp313-cp313-win32.whl", hash = "sha256:a4be186c0df86d4d95091c759a06582654f2b93690503b1c24d77f537d0cf5d0"}, + {file = "pyzstd-0.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:251a0b599bd224ec66f39165ddb2f959d0a523938e3bbfa82d8188dc03a271a2"}, + {file = "pyzstd-0.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce6d5fd908fd3ddec32d1c1a5a7a15b9d7737d0ef2ab20fe1e8261da61395017"}, + {file = "pyzstd-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5cb23c3c4ba4105a518cfbe8a566f9482da26f4bc8c1c865fd66e8e266be071"}, + {file = "pyzstd-0.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10b5d9215890a24f22505b68add26beeb2e3858bbe738a7ee339f0db8e29d033"}, + {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db1cff52fd24caf42a2cfb7e5d8dc822b93e9fac5dab505d0bd22e302061e2d2"}, + {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3caad3106e0e80f76acbb19c15e1e834ba6fd44dd4c82719ef8e3374f7fafd3"}, + {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e52e1de31b935e27568742145d8b4d0f204a1605e36f4e1e2846e0d39bed98"}, + {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaa046bc9e751c4083102f3624a52bbb66e20e7aa3e28673543b22e69d9b57cd"}, + {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cc9310bdb7cf2c70098aab40fb6bf68faaf0149110c6ef668996e7957e0147a"}, + {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3619075966456783818904f9d9e213c6fe2e583d5beb545fa1968b1848781e0f"}, + {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3844f8c7d7850580423b1b33601b016b3b913d18deb6fe14a7641b9c2714275c"}, + {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab53f91280b7b639c47bb2048e01182230e7cf3f0f0980bdb405b4241cfb705e"}, + {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:75252ee53e53a819ea7ac4271f66686018bc8b98ef12628269f099c10d881077"}, + {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0795afdaa34e1ed7f3d7552100cd57a1cef9d7310b386a893e0890e9a585b427"}, + {file = "pyzstd-0.17.0-cp39-cp39-win32.whl", hash = "sha256:f7316be5a5246b6bbdd807c7a4f10382b6b02c3afc5ae6acd2e266a84f715493"}, + {file = "pyzstd-0.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:121e8fac3e24b881fed59d638100b80c34f6347c02d2f24580f633451939f2d7"}, + {file = "pyzstd-0.17.0-cp39-cp39-win_arm64.whl", hash = "sha256:fe36ccda67f73e909ac305984fe13b7b5a79296706d095a80472ada4413174c2"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c56f99c697130f39702e07ab9fa0bb4c929c7bfe47c0a488dea732bd8a8752a"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:152bae1b2197bcd41fc143f93acd23d474f590162547484ca04ce5874c4847de"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2ddbbd7614922e52018ba3e7bb4cbe6f25b230096831d97916b8b89be8cd0cb"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6f3f152888825f71fd2cf2499f093fac252a5c1fa15ab8747110b3dc095f6b"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d00a2d2bddf51c7bf32c17dc47f0f49f47ebae07c2528b9ee8abf1f318ac193"}, + {file = "pyzstd-0.17.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d79e3eff07217707a92c1a6a9841c2466bfcca4d00fea0bea968f4034c27a256"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3ce6bac0c4c032c5200647992a8efcb9801c918633ebe11cceba946afea152d9"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:a00998144b35be7c485a383f739fe0843a784cd96c3f1f2f53f1a249545ce49a"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8521d7bbd00e0e1c1fd222c1369a7600fba94d24ba380618f9f75ee0c375c277"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da65158c877eac78dcc108861d607c02fb3703195c3a177f2687e0bcdfd519d0"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:226ca0430e2357abae1ade802585231a2959b010ec9865600e416652121ba80b"}, + {file = "pyzstd-0.17.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e3a19e8521c145a0e2cd87ca464bf83604000c5454f7e0746092834fd7de84d1"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:56ed2de4717844ffdebb5c312ec7e7b8eb2b69eb72883bbfe472ba2c872419e6"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc61c47ca631241081c0c99895a1feb56dab4beab37cac7d1f9f18aff06962eb"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd61757a4020590dad6c20fdbf37c054ed9f349591a0d308c3c03c0303ce221"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d6cce91a8ac8ae1aab06684a8bf0dee088405de7f451e1e89776ddc1f40074"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc668b67a13bf6213d0a9c09edc1f4842ed680b92fc3c9361f55a904903bfd1f"}, + {file = "pyzstd-0.17.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a67d7ef18715875b31127eb90075c03ced722fd87902b34bca4b807a2ce1e4d9"}, + {file = "pyzstd-0.17.0.tar.gz", hash = "sha256:d84271f8baa66c419204c1dd115a4dec8b266f8a2921da21b81764fa208c1db6"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.13\""} + [[package]] name = "questionary" -version = "2.0.1" +version = "2.1.0" description = "Python library to build pretty command line user prompts ⭐️" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "questionary-2.0.1-py3-none-any.whl", hash = "sha256:8ab9a01d0b91b68444dff7f6652c1e754105533f083cbe27597c8110ecc230a2"}, - {file = "questionary-2.0.1.tar.gz", hash = "sha256:bcce898bf3dbb446ff62830c86c5c6fb9a22a54146f0f5597d3da43b10d8fc8b"}, + {file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"}, + {file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"}, ] [package.dependencies] -prompt_toolkit = ">=2.0,<=3.0.36" +prompt_toolkit = ">=2.0,<4.0" [[package]] name = "ratelimit" @@ -3551,125 +3660,21 @@ files = [ {file = "ratelimit-2.2.1.tar.gz", hash = "sha256:af8a9b64b821529aca09ebaf6d8d279100d766f19e90b5059ac6a718ca6dee42"}, ] -[[package]] -name = "regex" -version = "2024.9.11" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, - {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, - {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, - {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, - {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, - {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, - {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, - {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, - {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, - {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, - {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, - {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, - {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, - {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, -] - [[package]] name = "requests" -version = "2.32.3" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -3679,38 +3684,37 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.9.3" +version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"}, - {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"}, + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruamel-yaml" -version = "0.18.6" +version = "0.18.15" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, - {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, + {file = "ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701"}, + {file = "ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700"}, ] [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""} [package.extras] docs = ["mercurial (>5.7)", "ryd"] @@ -3723,7 +3727,7 @@ description = "C version of reader, parser and emitter for ruamel.yaml derived f optional = false python-versions = ">=3.9" groups = ["main"] -markers = "platform_python_implementation == \"CPython\"" +markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, @@ -3775,167 +3779,194 @@ files = [ [[package]] name = "scikit-image" -version = "0.24.0" +version = "0.25.2" description = "Image processing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb3bc0264b6ab30b43c4179ee6156bc18b4861e78bb329dd8d16537b7bbf827a"}, - {file = "scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9c7a52e20cdd760738da38564ba1fed7942b623c0317489af1a598a8dedf088b"}, - {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93f46e6ce42e5409f4d09ce1b0c7f80dd7e4373bcec635b6348b63e3c886eac8"}, - {file = "scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39ee0af13435c57351a3397eb379e72164ff85161923eec0c38849fecf1b4764"}, - {file = "scikit_image-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ac7913b028b8aa780ffae85922894a69e33d1c0bf270ea1774f382fe8bf95e7"}, - {file = "scikit_image-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:272909e02a59cea3ed4aa03739bb88df2625daa809f633f40b5053cf09241831"}, - {file = "scikit_image-0.24.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:190ebde80b4470fe8838764b9b15f232a964f1a20391663e31008d76f0c696f7"}, - {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c98cc695005faf2b79904e4663796c977af22586ddf1b12d6af2fa22842dc2"}, - {file = "scikit_image-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa27b3a0dbad807b966b8db2d78da734cb812ca4787f7fbb143764800ce2fa9c"}, - {file = "scikit_image-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:dacf591ac0c272a111181afad4b788a27fe70d213cfddd631d151cbc34f8ca2c"}, - {file = "scikit_image-0.24.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6fccceb54c9574590abcddc8caf6cefa57c13b5b8b4260ab3ff88ad8f3c252b3"}, - {file = "scikit_image-0.24.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ccc01e4760d655aab7601c1ba7aa4ddd8b46f494ac46ec9c268df6f33ccddf4c"}, - {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18836a18d3a7b6aca5376a2d805f0045826bc6c9fc85331659c33b4813e0b563"}, - {file = "scikit_image-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8579bda9c3f78cb3b3ed8b9425213c53a25fa7e994b7ac01f2440b395babf660"}, - {file = "scikit_image-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:82ab903afa60b2da1da2e6f0c8c65e7c8868c60a869464c41971da929b3e82bc"}, - {file = "scikit_image-0.24.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef04360eda372ee5cd60aebe9be91258639c86ae2ea24093fb9182118008d009"}, - {file = "scikit_image-0.24.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e9aadb442360a7e76f0c5c9d105f79a83d6df0e01e431bd1d5757e2c5871a1f3"}, - {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e37de6f4c1abcf794e13c258dc9b7d385d5be868441de11c180363824192ff7"}, - {file = "scikit_image-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4688c18bd7ec33c08d7bf0fd19549be246d90d5f2c1d795a89986629af0a1e83"}, - {file = "scikit_image-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:56dab751d20b25d5d3985e95c9b4e975f55573554bd76b0aedf5875217c93e69"}, - {file = "scikit_image-0.24.0.tar.gz", hash = "sha256:5d16efe95da8edbeb363e0c4157b99becbd650a60b77f6e3af5768b66cf007ab"}, + {file = "scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78"}, + {file = "scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063"}, + {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99"}, + {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09"}, + {file = "scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054"}, + {file = "scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17"}, + {file = "scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0"}, + {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173"}, + {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641"}, + {file = "scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b"}, + {file = "scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb"}, + {file = "scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed"}, + {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d"}, + {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824"}, + {file = "scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2"}, + {file = "scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da"}, + {file = "scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc"}, + {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341"}, + {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147"}, + {file = "scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f"}, + {file = "scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd"}, + {file = "scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde"}, ] [package.dependencies] -imageio = ">=2.33" +imageio = ">=2.33,<2.35.0 || >2.35.0" lazy-loader = ">=0.4" -networkx = ">=2.8" -numpy = ">=1.23" +networkx = ">=3.0" +numpy = ">=1.24" packaging = ">=21" -pillow = ">=9.1" -scipy = ">=1.9" +pillow = ">=10.1" +scipy = ">=1.11.4" tifffile = ">=2022.8.12" [package.extras] -build = ["Cython (>=3.0.4)", "build", "meson-python (>=0.15)", "ninja", "numpy (>=2.0.0rc1)", "packaging (>=21)", "pythran", "setuptools (>=67)", "spin (==0.8)", "wheel"] +build = ["Cython (>=3.0.8)", "build (>=1.2.1)", "meson-python (>=0.16)", "ninja (>=1.11.1.1)", "numpy (>=2.0)", "pythran (>=0.16)", "spin (==0.13)"] data = ["pooch (>=1.6.0)"] developer = ["ipython", "pre-commit", "tomli ; python_version < \"3.11\""] -docs = ["PyWavelets (>=1.1.1)", "dask[array] (>=2022.9.2)", "ipykernel", "ipywidgets", "kaleido", "matplotlib (>=3.6)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=1.5)", "plotly (>=5.10)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.15.2)", "pytest-doctestplus", "pytest-runner", "scikit-learn (>=1.1)", "seaborn (>=0.11)", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-gallery (>=0.14)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] -optional = ["PyWavelets (>=1.1.1)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=0.2.1)", "dask[array] (>=2021.1.0)", "matplotlib (>=3.6)", "pooch (>=1.6.0)", "pyamg", "scikit-learn (>=1.1)"] -test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=7.0)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] +docs = ["PyWavelets (>=1.6)", "dask[array] (>=2023.2.0)", "intersphinx-registry (>=0.2411.14)", "ipykernel", "ipywidgets", "kaleido (==0.2.1)", "matplotlib (>=3.7)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=2.0)", "plotly (>=5.20)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.16)", "pytest-doctestplus", "scikit-learn (>=1.2)", "seaborn (>=0.11)", "sphinx (>=8.0)", "sphinx-copybutton", "sphinx-gallery[parallel] (>=0.18)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] +optional = ["PyWavelets (>=1.6)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=1.1.1)", "dask[array] (>=2023.2.0)", "matplotlib (>=3.7)", "pooch (>=1.6.0)", "pyamg (>=5.2)", "scikit-learn (>=1.2)"] +test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=8)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] [[package]] name = "scipy" -version = "1.14.1" +version = "1.16.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, - {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, - {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, - {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, - {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, - {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, - {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, - {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, - {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, - {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39"}, + {file = "scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"}, + {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"}, + {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"}, + {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"}, + {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"}, + {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"}, + {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"}, ] [package.dependencies] -numpy = ">=1.23.5,<2.3" +numpy = ">=1.25.2,<2.6" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" -version = "67.8.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, - {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf", "pytest-ruff ; sys_platform != \"cygwin\"", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main", "dev"] files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "smmap" -version = "5.0.1" +version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, ] [[package]] name = "soupsieve" -version = "2.6" +version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, ] [[package]] name = "termcolor" -version = "2.5.0" +version = "3.1.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, - {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, + {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"}, + {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"}, ] [package.extras] @@ -3955,106 +3986,167 @@ files = [ [[package]] name = "tifffile" -version = "2024.9.20" +version = "2025.6.11" description = "Read and write TIFF files" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "tifffile-2024.9.20-py3-none-any.whl", hash = "sha256:c54dc85bc1065d972cb8a6ffb3181389d597876aa80177933459733e4ed243dd"}, - {file = "tifffile-2024.9.20.tar.gz", hash = "sha256:3fbf3be2f995a7051a8ae05a4be70c96fc0789f22ed6f1c4104c973cf68a640b"}, + {file = "tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542"}, + {file = "tifffile-2025.6.11.tar.gz", hash = "sha256:0ece4c2e7a10656957d568a093b07513c0728d30c1bd8cc12725901fffdb7143"}, ] [package.dependencies] numpy = "*" [package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib", "zarr"] -codecs = ["imagecodecs (>=2023.8.12)"] +all = ["defusedxml", "fsspec", "imagecodecs (>=2024.12.30)", "kerchunk", "lxml", "matplotlib", "zarr (>=3)"] +codecs = ["imagecodecs (>=2024.12.30)"] plot = ["matplotlib"] -test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "roifile", "xarray", "zarr"] +test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "kerchunk", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "requests", "roifile", "xarray", "zarr (>=3)"] xml = ["defusedxml", "lxml"] -zarr = ["fsspec", "zarr"] +zarr = ["fsspec", "kerchunk", "zarr (>=3)"] [[package]] name = "tomli" -version = "2.0.2" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "tomlkit" -version = "0.13.2" +version = "0.13.3" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, + {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, + {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, ] [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] [[package]] name = "types-pywin32" -version = "310.0.0.20250516" +version = "311.0.0.20250822" description = "Typing stubs for pywin32" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_pywin32-310.0.0.20250516-py3-none-any.whl", hash = "sha256:f9ef83a1ec3e5aae2b0e24c5f55ab41272b5dfeaabb9a0451d33684c9545e41a"}, - {file = "types_pywin32-310.0.0.20250516.tar.gz", hash = "sha256:91e5bfc033f65c9efb443722eff8101e31d690dd9a540fa77525590d3da9cc9d"}, + {file = "types_pywin32-311.0.0.20250822-py3-none-any.whl", hash = "sha256:cbcfe0d247b599efcb30e4a4db60af9473a6f5a6b4aaf6c622e962ac420da6ea"}, + {file = "types_pywin32-311.0.0.20250822.tar.gz", hash = "sha256:ea1082e23a1ebc3f069be1d7eec911d247bf54b3f8feeef61385134b36c42c7c"}, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250809" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}, + {file = "types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}, ] +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "urllib3" -version = "2.2.3" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -4065,14 +4157,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.27.0" +version = "20.34.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, - {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, ] [package.dependencies] @@ -4082,46 +4174,46 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchdog" -version = "5.0.3" +version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"}, - {file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"}, - {file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"}, - {file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"}, - {file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] @@ -4129,14 +4221,14 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcmatch" -version = "10.0" +version = "10.1" description = "Wildcard/glob file name matcher." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "wcmatch-10.0-py3-none-any.whl", hash = "sha256:0dd927072d03c0a6527a20d2e6ad5ba8d0380e60870c383bc533b71744df7b7a"}, - {file = "wcmatch-10.0.tar.gz", hash = "sha256:e72f0de09bba6a04e0de70937b0cf06e55f36f37b3deb422dfaf854b867b840a"}, + {file = "wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a"}, + {file = "wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af"}, ] [package.dependencies] @@ -4156,14 +4248,14 @@ files = [ [[package]] name = "wheel" -version = "0.42.0" +version = "0.45.1" description = "A built-package format for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, - {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, + {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, + {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, ] [package.extras] @@ -4171,15 +4263,15 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "win32-setctime" -version = "1.1.0" +version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" groups = ["main"] markers = "sys_platform == \"win32\"" files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, ] [package.extras] @@ -4187,102 +4279,144 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "yarl" -version = "1.16.0" +version = "1.20.1" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32468f41242d72b87ab793a86d92f885355bcf35b3355aa650bfa846a5c60058"}, - {file = "yarl-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:234f3a3032b505b90e65b5bc6652c2329ea7ea8855d8de61e1642b74b4ee65d2"}, - {file = "yarl-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a0296040e5cddf074c7f5af4a60f3fc42c0237440df7bcf5183be5f6c802ed5"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de6c14dd7c7c0badba48157474ea1f03ebee991530ba742d381b28d4f314d6f3"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b140e532fe0266003c936d017c1ac301e72ee4a3fd51784574c05f53718a55d8"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:019f5d58093402aa8f6661e60fd82a28746ad6d156f6c5336a70a39bd7b162b9"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c42998fd1cbeb53cd985bff0e4bc25fbe55fd6eb3a545a724c1012d69d5ec84"}, - {file = "yarl-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7c30fb38c300fe8140df30a046a01769105e4cf4282567a29b5cdb635b66c4"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e49e0fd86c295e743fd5be69b8b0712f70a686bc79a16e5268386c2defacaade"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b9ca7b9147eb1365c8bab03c003baa1300599575effad765e0b07dd3501ea9af"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27e11db3f1e6a51081a981509f75617b09810529de508a181319193d320bc5c7"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8994c42f4ca25df5380ddf59f315c518c81df6a68fed5bb0c159c6cb6b92f120"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:542fa8e09a581bcdcbb30607c7224beff3fdfb598c798ccd28a8184ffc18b7eb"}, - {file = "yarl-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2bd6a51010c7284d191b79d3b56e51a87d8e1c03b0902362945f15c3d50ed46b"}, - {file = "yarl-1.16.0-cp310-cp310-win32.whl", hash = "sha256:178ccb856e265174a79f59721031060f885aca428983e75c06f78aa24b91d929"}, - {file = "yarl-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe8bba2545427418efc1929c5c42852bdb4143eb8d0a46b09de88d1fe99258e7"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d8643975a0080f361639787415a038bfc32d29208a4bf6b783ab3075a20b1ef3"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:676d96bafc8c2d0039cea0cd3fd44cee7aa88b8185551a2bb93354668e8315c2"}, - {file = "yarl-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9525f03269e64310416dbe6c68d3b23e5d34aaa8f47193a1c45ac568cecbc49"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37d5ec034e668b22cf0ce1074d6c21fd2a08b90d11b1b73139b750a8b0dd97"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f32c4cb7386b41936894685f6e093c8dfaf0960124d91fe0ec29fe439e201d0"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b8e265a0545637492a7e12fd7038370d66c9375a61d88c5567d0e044ded9202"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:789a3423f28a5fff46fbd04e339863c169ece97c827b44de16e1a7a42bc915d2"}, - {file = "yarl-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1d1f45e3e8d37c804dca99ab3cf4ab3ed2e7a62cd82542924b14c0a4f46d243"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:621280719c4c5dad4c1391160a9b88925bb8b0ff6a7d5af3224643024871675f"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed097b26f18a1f5ff05f661dc36528c5f6735ba4ce8c9645e83b064665131349"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2f1fe2b2e3ee418862f5ebc0c0083c97f6f6625781382f828f6d4e9b614eba9b"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87dd10bc0618991c66cee0cc65fa74a45f4ecb13bceec3c62d78ad2e42b27a16"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4199db024b58a8abb2cfcedac7b1292c3ad421684571aeb622a02f242280e8d6"}, - {file = "yarl-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:99a9dcd4b71dd5f5f949737ab3f356cfc058c709b4f49833aeffedc2652dac56"}, - {file = "yarl-1.16.0-cp311-cp311-win32.whl", hash = "sha256:a9394c65ae0ed95679717d391c862dece9afacd8fa311683fc8b4362ce8a410c"}, - {file = "yarl-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b9101f528ae0f8f65ac9d64dda2bb0627de8a50344b2f582779f32fda747c1d"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ffb7c129707dd76ced0a4a4128ff452cecf0b0e929f2668ea05a371d9e5c104"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1a5e9d8ce1185723419c487758d81ac2bde693711947032cce600ca7c9cda7d6"}, - {file = "yarl-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d743e3118b2640cef7768ea955378c3536482d95550222f908f392167fe62059"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26768342f256e6e3c37533bf9433f5f15f3e59e3c14b2409098291b3efaceacb"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1b0796168b953bca6600c5f97f5ed407479889a36ad7d17183366260f29a6b9"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:858728086914f3a407aa7979cab743bbda1fe2bdf39ffcd991469a370dd7414d"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5570e6d47bcb03215baf4c9ad7bf7c013e56285d9d35013541f9ac2b372593e7"}, - {file = "yarl-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66ea8311422a7ba1fc79b4c42c2baa10566469fe5a78500d4e7754d6e6db8724"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:649bddcedee692ee8a9b7b6e38582cb4062dc4253de9711568e5620d8707c2a3"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a91654adb7643cb21b46f04244c5a315a440dcad63213033826549fa2435f71"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b439cae82034ade094526a8f692b9a2b5ee936452de5e4c5f0f6c48df23f8604"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:571f781ae8ac463ce30bacebfaef2c6581543776d5970b2372fbe31d7bf31a07"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa7943f04f36d6cafc0cf53ea89824ac2c37acbdb4b316a654176ab8ffd0f968"}, - {file = "yarl-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a5cf32539373ff39d97723e39a9283a7277cbf1224f7aef0c56c9598b6486c3"}, - {file = "yarl-1.16.0-cp312-cp312-win32.whl", hash = "sha256:a5b6c09b9b4253d6a208b0f4a2f9206e511ec68dce9198e0fbec4f160137aa67"}, - {file = "yarl-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1208ca14eed2fda324042adf8d6c0adf4a31522fa95e0929027cd487875f0240"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5ace0177520bd4caa99295a9b6fb831d0e9a57d8e0501a22ffaa61b4c024283"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7118bdb5e3ed81acaa2095cba7ec02a0fe74b52a16ab9f9ac8e28e53ee299732"}, - {file = "yarl-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38fec8a2a94c58bd47c9a50a45d321ab2285ad133adefbbadf3012c054b7e656"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8791d66d81ee45866a7bb15a517b01a2bcf583a18ebf5d72a84e6064c417e64b"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cf936ba67bc6c734f3aa1c01391da74ab7fc046a9f8bbfa230b8393b90cf472"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1aab176dd55b59f77a63b27cffaca67d29987d91a5b615cbead41331e6b7428"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:995d0759004c08abd5d1b81300a91d18c8577c6389300bed1c7c11675105a44d"}, - {file = "yarl-1.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bc22e00edeb068f71967ab99081e9406cd56dbed864fc3a8259442999d71552"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35b4f7842154176523e0a63c9b871168c69b98065d05a4f637fce342a6a2693a"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7ace71c4b7a0c41f317ae24be62bb61e9d80838d38acb20e70697c625e71f120"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8f639e3f5795a6568aa4f7d2ac6057c757dcd187593679f035adbf12b892bb00"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e8be3aff14f0120ad049121322b107f8a759be76a6a62138322d4c8a337a9e2c"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:122d8e7986043d0549e9eb23c7fd23be078be4b70c9eb42a20052b3d3149c6f2"}, - {file = "yarl-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0fd9c227990f609c165f56b46107d0bc34553fe0387818c42c02f77974402c36"}, - {file = "yarl-1.16.0-cp313-cp313-win32.whl", hash = "sha256:595ca5e943baed31d56b33b34736461a371c6ea0038d3baec399949dd628560b"}, - {file = "yarl-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:921b81b8d78f0e60242fb3db615ea3f368827a76af095d5a69f1c3366db3f596"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab2b2ac232110a1fdb0d3ffcd087783edd3d4a6ced432a1bf75caf7b7be70916"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f8713717a09acbfee7c47bfc5777e685539fefdd34fa72faf504c8be2f3df4e"}, - {file = "yarl-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdcffe1dbcb4477d2b4202f63cd972d5baa155ff5a3d9e35801c46a415b7f71a"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a91217208306d82357c67daeef5162a41a28c8352dab7e16daa82e3718852a7"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ab3ed42c78275477ea8e917491365e9a9b69bb615cb46169020bd0aa5e2d6d3"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707ae579ccb3262dfaef093e202b4c3fb23c3810e8df544b1111bd2401fd7b09"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7a852d1cd0b8d8b37fc9d7f8581152add917a98cfe2ea6e241878795f917ae"}, - {file = "yarl-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3f1cc3d3d4dc574bebc9b387f6875e228ace5748a7c24f49d8f01ac1bc6c31b"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5ff96da263740779b0893d02b718293cc03400c3a208fc8d8cd79d9b0993e532"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3d375a19ba2bfe320b6d873f3fb165313b002cef8b7cc0a368ad8b8a57453837"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:62c7da0ad93a07da048b500514ca47b759459ec41924143e2ddb5d7e20fd3db5"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:147b0fcd0ee33b4b5f6edfea80452d80e419e51b9a3f7a96ce98eaee145c1581"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:504e1fe1cc4f170195320eb033d2b0ccf5c6114ce5bf2f617535c01699479bca"}, - {file = "yarl-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bdcf667a5dec12a48f669e485d70c54189f0639c2157b538a4cffd24a853624f"}, - {file = "yarl-1.16.0-cp39-cp39-win32.whl", hash = "sha256:e9951afe6557c75a71045148890052cb942689ee4c9ec29f5436240e1fcc73b7"}, - {file = "yarl-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:7d7aaa8ff95d0840e289423e7dc35696c2b058d635f945bf05b5cd633146b027"}, - {file = "yarl-1.16.0-py3-none-any.whl", hash = "sha256:e6980a558d8461230c457218bd6c92dfc1d10205548215c2c21d79dc8d0a96f3"}, - {file = "yarl-1.16.0.tar.gz", hash = "sha256:b6f687ced5510a9a2474bbae96a4352e5ace5fa34dc44a217b0537fec1db00b4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -propcache = ">=0.2.0" +propcache = ">=0.2.1" + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.1" -python-versions = ">=3.10,<3.13" -content-hash = "c655e1888fdc9e79d2520f88895b813e0f92968100b59793006dacf1eefc55de" +python-versions = ">=3.11,<3.15" +content-hash = "2e804e72c253b0b2bb6df8acbe3b93eb3c85ea5c4182ebdb8e57b1b7e292f92e" diff --git a/pyproject.toml b/pyproject.toml index b91bffec..9c4f83b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,22 @@ -[tool.poetry] +[project] name = "proxyshop" version = "1.13.2" description = "Photoshop automation tool for generating high quality Magic the Gathering card renders." -authors = ["Investigamer "] +authors = [{ name = "Investigamer", email = "freethoughtleft@gmail.com" }] license = "MPL-2.0" readme = "README.md" -keywords = ["proxyshop", "proxy", "mtg", "magic", "gathering", "cards", "photoshop", "magic the gathering", "playtest"] +keywords = [ + "proxyshop", + "proxy", + "mtg", + "magic", + "gathering", + "cards", + "photoshop", + "magic the gathering", + "playtest", +] +requires-python = ">=3.11,<3.15" [tool.poetry.urls] Changelog = "https://github.com/Investigamer/Proxyshop/blob/main/CHANGELOG.md" @@ -19,66 +30,73 @@ Sponsor = "https://patreon.com/mpcfill" include = 'src/../' [tool.poetry.dependencies] -python = ">=3.10,<3.13" -photoshop-python-api = "^0.22.4" -requests = "^2.28.1" -asynckivy = "^0.7.0" -Pillow = "^10.3.0" -kivy = "^2.3.0" -typing-extensions = "^4.5.0" +photoshop-python-api = { git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation" } +requests = "^2.32.5" +asynckivy = "^0.9.0" +Pillow = "^11.3.0" +kivy = "^2.3.1" +typing-extensions = "^4.14.1" ratelimit = "^2.2.1" backoff = "^2.2.1" -pathvalidate = "^3.2.0" -fonttools = "^4.39.3" -pyyaml = "^6.0" -tqdm = "^4.65.0" -click = "^8.1.7" -tomli = "^2.0.1" -yarl = "^1.9.3" -pydantic = "^2.5.2" -omnitils = "^1.4.4" -dynaconf = {extras = ["yaml"], version = "^3.2.6"} -hexproof = "^0.3.7" -rich = "^13.8.1" -pywin32 = "^310" +pathvalidate = "^3.3.1" +fonttools = "^4.59.1" +pyyaml = "^6.0.2" +tqdm = "^4.67.1" +click = "^8.2.1" +tomli = "^2.2.1" +yarl = "^1.20.1" +pydantic = "^2.11.7" +omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } +dynaconf = { extras = ["yaml"], version = "^3.2.11" } +hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } +rich = "^14.1.0" +pywin32 = "^311.0.0" [tool.poetry.group.dev.dependencies] -pytest = "^7.2.0" -mypy = "^1.6.0" -commitizen = "^3.12.0" -setuptools = "^67.6.0" -matplotlib = "^3.7.2" -psd-tools = "^1.9.28" -pyclean = "^2.2.0" -pyinstaller = "^5.12.0" -pre-commit = "^3.5.0" -mkdocs = "^1.5.3" -mkdocs-material = "^9.4.8" -mkdocs-include-markdown-plugin = "^6.0.4" -mkdocs-pymdownx-material-extras = "^2.5.5" -mkdocs-minify-plugin = "^0.7.1" -mkdocstrings-python = "^1.7.3" +pytest = "^8.4.1" +mypy = "^1.17.1" +commitizen = "^4.8.3" +setuptools = "^80.9.0" +matplotlib = "^3.10.5" +psd-tools = "^1.10.9" +pyclean = "^3.1.0" +pyinstaller = "^6.15.0" +pre-commit = "^4.3.0" +mkdocs = "^1.6.1" +mkdocs-material = "^9.6.18" +mkdocs-include-markdown-plugin = "^7.1.6" +mkdocs-pymdownx-material-extras = "^2.8.0" +mkdocs-minify-plugin = "^0.8.0" +mkdocstrings-python = "^1.17.0" mkdocs-gen-files = "^0.5.0" mkdocs-autolinks-plugin = "^0.7.1" -mkdocs-same-dir = "^0.1.2" +mkdocs-same-dir = "^0.1.3" mkdocs-git-revision-date-plugin = "^0.3.2" -mkdocstrings = {extras = ["python"], version = "^0.23.0"} +mkdocstrings = { extras = ["python"], version = "^0.30.0" } memory-profiler = "^0.61.0" -types-pywin32 = "^310.0.0.20250516" +types-pywin32 = "^311.0.0.20250809" +types-requests = "^2.32.4.20250809" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.13.2" +version_provider = "pep621" encoding = "utf-8" changelog_start_rev = 'v1.2.0' tag_format = "v$major.$minor.$patch" update_changelog_on_bump = true -version_files = [ - "pyproject.toml:version" -] [tool.poetry.scripts] proxyshop = 'main:launch_cli' + +[tool.ruff.lint] +extend-select = [ + "UP", # pyupgrade +] +ignore = ["F403", "UP015"] + +[tool.pyright] +typeCheckingMode = "strict" +reportMissingTypeStubs = "information" From 539ad2ac1a17ceb45143d2dbcb87330741ad70e7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 24 Aug 2025 13:01:15 +0300 Subject: [PATCH 029/190] docs(README.md): Clarify Python environment setup --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4baa6def..48e10f12 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou ![Photoshop](https://img.shields.io/badge/photoshop-CC_2017+-informational?style=plastic) -![Python](https://img.shields.io/badge/python-3.10_|_3.11_|_3.12-blue?style=plastic) +![Python](https://img.shields.io/badge/python-3.11_|_3.12_|_3.13-blue?style=plastic) [![Discord](https://img.shields.io/discord/889831317066358815?style=plastic&label=discord&color=brightgreen)](https://discord.gg/magicproxies) ![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/MrTeferi/Proxyshop?style=plastic&label=commits&color=brightgreen) [![Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dmpcfill%26type%3Dpatrons&style=plastic&color=red&logo=none)](https://patreon.com/mpcfill) @@ -106,13 +106,15 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou # 🐍 Setup Guide (Python Environment) Setting up the Python environment for Proxyshop is intended for advanced users, contributors, and anyone who wants to get their hands dirty making a plugin or custom template for the app! This guide assumes you already have Python installed. -See the badge above for supported Python versions. +See `pyproject.toml` for supported Python versions. 1. Install Poetry with pipx. ```bash # Install pipx and poetry python -m pip install --user pipx python -m pipx ensurepath pipx install poetry + # Store the virtual environment in the project directory + poetry config virtualenvs.in-project true ``` 2. Clone Proxyshop somewhere on your system, we'll call this the ***root directory***. ```bash @@ -133,9 +135,21 @@ See the badge above for supported Python versions. # OPTION 2) Enter the poetry environment, then execute with cli poetry shell proxyshop gui + + # OPTION 3) Activate the virtual environment and run the app's entrypoint with Python + ./.venv/Scripts/Activate + python main.py ``` 7. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. +# Development Environment + +If you want to contribute to Proxyshop you should ensure that your code plays well with the strict type checking of [Pyright](https://github.com/microsoft/pyright) or [Mypy](https://github.com/python/mypy). For example, using [VS Code](https://code.visualstudio.com/) with the extensions below will allow you to see type checking results and take advantage of code completions, such as auto-imports, while writing code, though you are free to use any other setup that suits you as well: + - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) + - [Python Debugger](https://marketplace.visualstudio.com/items?itemName=ms-python.debugpy) + - [Python Environments](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-python-envs) + - [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) + # 💾 Download Templates Manually If you wish to download the templates manually, visit [this link](https://drive.google.com/drive/u/1/folders/1sgJ3Xu4FabxNgDl0yeI7OjDZ7fqlI4p3). These archives must be extracted to the `/templates` directory. The archives found within the **Investigamer** and **SilvanMTG** drive folders must be extracted to From 4c0157b2929442a550e03c388a29123cd159ed47 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 29 Aug 2025 16:13:27 +0300 Subject: [PATCH 030/190] feat: Asynchronize startup checks in order to allow the user to interact with the GUI sooner BREAKING CHANGE: --- main.py | 3 + .../Investigamer/py/actions/pencilsketch.py | 2161 +++++++++-------- plugins/Investigamer/py/actions/sketch.py | 954 ++++---- src/__init__.py | 4 +- src/commands/test/utility.py | 352 +-- src/console.py | 7 +- src/enums/adobe.py | 2 +- src/gui/app.py | 203 +- src/gui/console.py | 7 +- src/gui/tabs/main.py | 18 +- src/helpers/actions.py | 21 +- src/helpers/adjustments.py | 131 +- src/helpers/bounds.py | 74 +- src/helpers/colors.py | 41 +- src/helpers/descriptors.py | 5 +- src/helpers/design.py | 163 +- src/helpers/document.py | 190 +- src/helpers/effects.py | 392 ++- src/helpers/layers.py | 229 +- src/helpers/masks.py | 182 +- src/helpers/position.py | 125 +- src/helpers/selection.py | 95 +- src/helpers/text.py | 303 ++- src/schema/colors.py | 4 +- src/templates/_core.py | 11 +- src/text_layers.py | 72 +- src/utils/event.py | 34 + src/utils/fonts.py | 4 +- src/utils/threading.py | 55 + 29 files changed, 3398 insertions(+), 2444 deletions(-) create mode 100644 src/utils/event.py create mode 100644 src/utils/threading.py diff --git a/main.py b/main.py index 2f959dda..bd57ccf4 100644 --- a/main.py +++ b/main.py @@ -41,6 +41,9 @@ def launch_gui(): from src.gui.console import GUIConsole if isinstance(CONSOLE, GUIConsole): + # Start Photoshop in the background + APP.initialize() + # Kivy Imported Last from kivy.resources import resource_add_path diff --git a/plugins/Investigamer/py/actions/pencilsketch.py b/plugins/Investigamer/py/actions/pencilsketch.py index 000dddd8..179d3f89 100644 --- a/plugins/Investigamer/py/actions/pencilsketch.py +++ b/plugins/Investigamer/py/actions/pencilsketch.py @@ -1,6 +1,7 @@ """ Pencil Sketchify Action Module """ + # Standard Library Imports from threading import Event from collections.abc import Iterable @@ -12,7 +13,6 @@ from src import APP, CONSOLE # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID dialog_mode = ps.DialogModes.DisplayNoDialogs """ @@ -21,139 +21,152 @@ def new_layer(index: int): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putClass(cID('Lyr ')) - desc1.putReference(cID('null'), ref1) - desc1.putInteger(cID('LyrI'), index) - APP.executeAction(cID('Mk '), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putClass(APP.instance.cID("Lyr ")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putInteger(APP.instance.cID("LyrI"), index) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) def select_bg(): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), "Background") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ps.ActionList() - list1.putInteger(1) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putName(APP.instance.cID("Lyr "), "Background") + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ps.ActionList() + list1.putInteger(1) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) def reset_colors(): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putProperty(cID('Clr '), cID('Clrs')) - desc1.putReference(cID('null'), ref1) - APP.executeAction(cID('Rset'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putProperty(APP.instance.cID("Clr "), APP.instance.cID("Clrs")) + desc1.putReference(APP.instance.cID("null"), ref1) + APP.instance.executeAction(APP.instance.cID("Rset"), desc1, dialog_mode) def move_layer(pos: int, index: int | list[int]): - if isinstance(index, int): - index = [index] - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - ref2 = ps.ActionReference() - ref2.putIndex(cID('Lyr '), pos) - desc1.putReference(cID('T '), ref2) - desc1.putBoolean(cID('Adjs'), False) - desc1.putInteger(cID('Vrsn'), 5) - list1 = ps.ActionList() - for i in index: - list1.putInteger(i) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('move'), desc1, dialog_mode) + if isinstance(index, int): + index = [index] + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + ref2 = ps.ActionReference() + ref2.putIndex(APP.instance.cID("Lyr "), pos) + desc1.putReference(APP.instance.cID("T "), ref2) + desc1.putBoolean(APP.instance.cID("Adjs"), False) + desc1.putInteger(APP.instance.cID("Vrsn"), 5) + list1 = ps.ActionList() + for i in index: + list1.putInteger(i) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("move"), desc1, dialog_mode) def set_opacity(opacity: float): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc2.putUnitDouble(cID('Opct'), cID('#Prc'), opacity) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), opacity) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) def select_layer(name: str, index: int): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), name) - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ps.ActionList() - list1.putInteger(index) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putName(APP.instance.cID("Lyr "), name) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ps.ActionList() + list1.putInteger(index) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) def select_layers(name: str, layers: list[int]): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), name) - desc1.putReference(cID('null'), ref1) - desc1.putEnumerated( - sID("selectionModifier"), sID("selectionModifierType"), sID("addToSelection")) - desc1.putBoolean(cID('MkVs'), False) - list1 = ps.ActionList() - for layer in layers: - list1.putInteger(layer) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putName(APP.instance.cID("Lyr "), name) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putEnumerated( + APP.instance.sID("selectionModifier"), + APP.instance.sID("selectionModifierType"), + APP.instance.sID("addToSelection"), + ) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ps.ActionList() + for layer in layers: + list1.putInteger(layer) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) def auto_tone(): - desc1 = ps.ActionDescriptor() - desc1.putBoolean(cID('Auto'), True) - APP.executeAction(cID('Lvls'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + desc1.putBoolean(APP.instance.cID("Auto"), True) + APP.instance.executeAction(APP.instance.cID("Lvls"), desc1, dialog_mode) def auto_contrast(): - desc1 = ps.ActionDescriptor() - desc1.putBoolean(cID('AuCo'), True) - APP.executeAction(cID('Lvls'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + desc1.putBoolean(APP.instance.cID("AuCo"), True) + APP.instance.executeAction(APP.instance.cID("Lvls"), desc1, dialog_mode) def hide_layer(name: str | None = None): - desc1 = ps.ActionDescriptor() - list1 = ps.ActionList() - ref1 = ps.ActionReference() - if name: - ref1.putName(cID('Lyr '), name) - else: - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - list1.putReference(ref1) - desc1.putList(cID('null'), list1) - APP.executeAction(cID('Hd '), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + list1 = ps.ActionList() + ref1 = ps.ActionReference() + if name: + ref1.putName(APP.instance.cID("Lyr "), name) + else: + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + list1.putReference(ref1) + desc1.putList(APP.instance.cID("null"), list1) + APP.instance.executeAction(APP.instance.cID("Hd "), desc1, dialog_mode) def show_layer(name: str | None = None): - desc1 = ps.ActionDescriptor() - list1 = ps.ActionList() - ref1 = ps.ActionReference() - if name: - ref1.putName(cID('Lyr '), name) - else: - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - list1.putReference(ref1) - desc1.putList(cID('null'), list1) - APP.executeAction(cID('Shw '), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + list1 = ps.ActionList() + ref1 = ps.ActionReference() + if name: + ref1.putName(APP.instance.cID("Lyr "), name) + else: + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + list1.putReference(ref1) + desc1.putList(APP.instance.cID("null"), list1) + APP.instance.executeAction(APP.instance.cID("Shw "), desc1, dialog_mode) def delete_layers(layers: Iterable[int]): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - list1 = ps.ActionList() - for layer in layers: - list1.putInteger(layer) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('Dlt '), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + list1 = ps.ActionList() + for layer in layers: + list1.putInteger(layer) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("Dlt "), desc1, dialog_mode) """ @@ -162,14 +175,18 @@ def delete_layers(layers: Iterable[int]): def blend(key: str): - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc2.putEnumerated(cID('Md '), cID('BlnM'), sID(key)) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.sID(key) + ) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) """ @@ -178,836 +195,1018 @@ def blend(key: str): def filter_photocopy(detail: int = 2, darken: int = 5): - """ - Apply photocopy filter. - @param detail: Level of detail. - @param darken: Darkness amount. - """ - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('Phtc')) - desc1.putInteger(sID('detail '), detail) - desc1.putInteger(sID('darken'), darken) - APP.executeAction(1195730531, desc1, dialog_mode) + """ + Apply photocopy filter. + @param detail: Level of detail. + @param darken: Darkness amount. + """ + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Phtc") + ) + desc1.putInteger(APP.instance.sID("detail "), detail) + desc1.putInteger(APP.instance.sID("darken"), darken) + APP.instance.executeAction(1195730531, desc1, dialog_mode) # Utility commands -def blend_multiply(): blend('multiply') -def blend_color_dodge(): blend('colorDodge') -def blend_linear_light(): blend("linearLight") -def blend_linear_burn(): blend("linearBurn") -def blend_soft_light(): blend('softLight') -def blend_screen(): blend('screen') -def blend_overlay(): blend('overlay') -def blend_color(): blend('color') - - -def run( - thr: Event, - draft_sketch: bool = False, - rough_sketch: bool = False, - black_and_white: bool = True, - manual_editing: bool = False -): - """ - Pencil Sketchify Steps - """ - - # Is the main layer "Layer 1" - APP.activeDocument.activeLayer.name = "Background" - - # Make - New Layer 1 - new_layer(139) - - # Select - select_bg() - - # Make - Solid Color Layer - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putClass(sID("contentLayer")) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc3 = ps.ActionDescriptor() - desc4 = ps.ActionDescriptor() - desc4.putInteger(cID('Rd '), 166) - desc4.putInteger(cID('Grn '), 166) - desc4.putInteger(cID('Bl '), 166) - desc3.putObject(cID('Clr '), sID("RGBColor"), desc4) - desc2.putObject(cID('Type'), sID("solidColorLayer"), desc3) - desc1.putObject(cID('Usng'), sID("contentLayer"), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Select - select_bg() - - # Layer Via Copy - Background copy - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(3, 240) - - # Reset Colors - reset_colors() - - # Filter Gallery - Photocopy - filter_photocopy(detail=2, darken=5) - - # Set - Blending Multiply - blend_multiply() - - # Select - select_bg() - - # Layer Via Copy - Background copy 2 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(4, 242) - - # Reset Colors - reset_colors() - - # Filter Gallery - Photocopy, Accented Edges - desc1 = ps.ActionDescriptor() - list1 = ps.ActionList() - desc2 = ps.ActionDescriptor() - desc3 = ps.ActionDescriptor() - desc2.putEnumerated(cID('GEfk'), cID('GEft'), cID('AccE')) - desc2.putInteger(cID('EdgW'), 3) - desc2.putInteger(cID('EdgB'), 20) - desc2.putInteger(cID('Smth'), 15) - list1.putObject(cID('GEfc'), desc2) - desc3.putEnumerated(cID('GEfk'), cID('GEft'), cID('Phtc')) - desc3.putInteger(cID('Dtl '), 1) - desc3.putInteger(cID('Drkn'), 49) - list1.putObject(cID('GEfc'), desc3) - desc1.putList(cID('GEfs'), list1) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Set - Blending Multiply - blend_multiply() - - # Select - select_bg() - - # Layer Via Copy - Background Copy 3 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(5, 244) - - # Reset Colors - reset_colors() - - # Filter Gallery - Photocopy, Stamp - desc1 = ps.ActionDescriptor() - list1 = ps.ActionList() - desc2 = ps.ActionDescriptor() - desc2.putEnumerated(cID('GEfk'), cID('GEft'), cID('Stmp')) - desc2.putInteger(cID('LgDr'), 25) - desc2.putInteger(cID('Smth'), 40) - list1.putObject(cID('GEfc'), desc2) - desc3 = ps.ActionDescriptor() - desc3.putEnumerated(cID('GEfk'), cID('GEft'), cID('Phtc')) - desc3.putInteger(cID('Dtl '), 1) - desc3.putInteger(cID('Drkn'), 49) - list1.putObject(cID('GEfc'), desc3) - desc1.putList(cID('GEfs'), list1) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Set - Blending Multiply - blend_multiply() - - # Set - set_opacity(25) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 4 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(6, 246) - - # Reset - reset_colors() - - # Filter Gallery - Glowing Edge - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('GlwE')) - desc1.putInteger(cID('EdgW'), 1) - desc1.putInteger(cID('EdgB'), 20) - desc1.putInteger(cID('Smth'), 15) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Desaturate - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) - - # Levels - Auto Tone - auto_tone() - - # Levels - Auto Contrast - auto_contrast() - - # Levels Adjustment - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindCustom")) - list1 = ps.ActionList() - desc2 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Cmps')) - desc2.putReference(cID('Chnl'), ref1) - list2 = ps.ActionList() - list2.putInteger(25) - list2.putInteger(230) - desc2.putList(cID('Inpt'), list2) - list1.putObject(cID('LvlA'), desc2) - desc1.putList(cID('Adjs'), list1) - APP.executeAction(cID('Lvls'), desc1, dialog_mode) - - # Invert - APP.executeAction(cID('Invr'), ps.ActionDescriptor(), dialog_mode) - - # Set - blend_multiply() - - # Select - select_bg() - - # Layer Via Copy - Background Copy 5 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(7, 248) - - # Layer Via Copy - Background Copy 6 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Invert - APP.executeAction(cID('Invr'), ps.ActionDescriptor(), dialog_mode) - - # Gaussian Blur - desc1 = ps.ActionDescriptor() - desc1.putUnitDouble(cID('Rds '), cID('#Pxl'), 50) - APP.executeAction(sID('gaussianBlur'), desc1, dialog_mode) - - # Set - Blending Color Dodge - blend_color_dodge() - - # Select - select_layers("Background copy 5", [248, 249]) - - # Merge Layers - APP.executeAction(sID('mergeLayersNew'), ps.ActionDescriptor(), dialog_mode) - - # Desaturate - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) - - # Layer Via Copy - Background Copy 7 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Reset - reset_colors() - - # Filter Gallery - Glowing Edge - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('GlwE')) - desc1.putInteger(cID('EdgW'), 1) - desc1.putInteger(cID('EdgB'), 20) - desc1.putInteger(cID('Smth'), 15) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Invert - APP.executeAction(cID('Invr'), ps.ActionDescriptor(), dialog_mode) - - # Set - blend_multiply() - - # Set - set_opacity(80) - - # Select - select_layers("Background copy 6", [249, 250]) - - # Merge Layers - APP.executeAction(sID('mergeLayersNew'), ps.ActionDescriptor(), dialog_mode) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 5 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(8, 252) - - # Desaturate - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) - - # High Pass Filter - desc1 = ps.ActionDescriptor() - desc1.putUnitDouble(cID('Rds '), cID('#Pxl'), 30) - APP.executeAction(sID('highPass'), desc1, dialog_mode) - - # Set - Blending Linear Light - blend_linear_light() - - # Set - Layer Style, Blending Options: Blend if Gray - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - list1 = ps.ActionList() - desc3 = ps.ActionDescriptor() - ref2 = ps.ActionReference() - ref2.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Gry ')) - desc3.putReference(cID('Chnl'), ref2) - desc3.putInteger(cID('SrcB'), 0) - desc3.putInteger(cID('Srcl'), 0) - desc3.putInteger(cID('SrcW'), 75) - desc3.putInteger(cID('Srcm'), 125) - desc3.putInteger(cID('DstB'), 55) - desc3.putInteger(cID('Dstl'), 125) - desc3.putInteger(cID('DstW'), 255) - desc3.putInteger(cID('Dstt'), 255) - list1.putObject(cID('Blnd'), desc3) - desc2.putList(cID('Blnd'), list1) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Set - set_opacity(50) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 6 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(9, 254) - - # Reset - reset_colors() - - # Filter Gallery - Cutout - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('Ct ')) - desc1.putInteger(cID('NmbL'), 8) - desc1.putInteger(cID('EdgS'), 10) - desc1.putInteger(cID('EdgF'), 1) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Desaturate - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) - - # Levels - Auto Tone - auto_tone() - - # Levels - Auto Contrast - auto_contrast() - - # Color Range - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Clrs'), cID('Clrs'), cID('Mdtn')) - desc1.putInteger(sID("midtonesFuzziness"), 40) - desc1.putInteger(sID("midtonesLowerLimit"), 105) - desc1.putInteger(sID("midtonesUpperLimit"), 150) - desc1.putInteger(sID("colorModel"), 0) - APP.executeAction(sID('colorRange'), desc1, dialog_mode) - - # Layer Via Copy - Layer 2 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Find Edges - APP.executeAction(sID('findEdges'), ps.ActionDescriptor(), dialog_mode) - - # Hide - hide_layer("Background copy 6") - - # Hide - hide_layer() - - # Select - select_bg() - - # Layer Via Copy - Background copy 8 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(9, 257) - - # Reset - reset_colors() +def blend_multiply(): + blend("multiply") - # Filter Gallery - Cutout - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('Ct ')) - desc1.putInteger(cID('NmbL'), 8) - desc1.putInteger(cID('EdgS'), 8) - desc1.putInteger(cID('EdgF'), 1) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Desaturate - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) - - # Levels - auto_tone() - - # Levels - auto_contrast() - - # Color Range - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Clrs'), cID('Clrs'), cID('Mdtn')) - desc1.putInteger(sID("midtonesFuzziness"), 40) - desc1.putInteger(sID("midtonesLowerLimit"), 105) - desc1.putInteger(sID("midtonesUpperLimit"), 150) - desc1.putInteger(sID("colorModel"), 0) - APP.executeAction(sID('colorRange'), desc1, dialog_mode) - - # Layer Via Copy - Layer 3 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Find Edges - APP.executeAction(sID('findEdges'), ps.ActionDescriptor(), dialog_mode) - - # Select - select_layer("Background copy 8", 257) - - # Select - select_layers("Background copy 6", [257, 254]) - - # Delete - delete_layers([257, 254]) - - # Select - select_layer("Layer 2", 255) - - # Show - show_layer() - - # Select - select_layers("Layer 3", [258, 255]) - - # Set - blend_multiply() - - # Set - set_opacity(30) - - # Select - select_layer("Background copy 3", 244) - - # Set - set_opacity(30) - - # Select - select_layer("Background copy 7", 250) - - # Move - move_layer(2, 250) - - # Select - select_layer("Background copy 5", 252) - - # Move - move_layer(10, 252) - - # Select - select_layer("Background copy 7", 250) - - # Set - set_opacity(50) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 6 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(11, 261) - - # Filter - Distort Wave - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Wvtp'), cID('Wvtp'), cID('WvSn')) - desc1.putInteger(cID('NmbG'), 5) - desc1.putInteger(cID('WLMn'), 10) - desc1.putInteger(cID('WLMx'), 500) - desc1.putInteger(cID('AmMn'), 5) - desc1.putInteger(cID('AmMx'), 35) - desc1.putInteger(cID('SclH'), 100) - desc1.putInteger(cID('SclV'), 100) - desc1.putEnumerated(cID('UndA'), cID('UndA'), cID('RptE')) - desc1.putInteger(cID('RndS'), 1260853) - APP.executeAction(cID('Wave'), desc1, dialog_mode) - - # Reset - reset_colors() - - # Filter Gallery - Photocopy, Accented Edges - desc1 = ps.ActionDescriptor() - list1 = ps.ActionList() - desc2 = ps.ActionDescriptor() - desc2.putEnumerated(cID('GEfk'), cID('GEft'), cID('AccE')) - desc2.putInteger(cID('EdgW'), 3) - desc2.putInteger(cID('EdgB'), 20) - desc2.putInteger(cID('Smth'), 15) - list1.putObject(cID('GEfc'), desc2) - desc3 = ps.ActionDescriptor() - desc3.putEnumerated(cID('GEfk'), cID('GEft'), cID('Phtc')) - desc3.putInteger(cID('Dtl '), 1) - desc3.putInteger(cID('Drkn'), 49) - list1.putObject(cID('GEfc'), desc3) - desc1.putList(cID('GEfs'), list1) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Levels - Auto Tone - auto_tone() - - # Levels - Auto Contrast - auto_contrast() - - # Set - Blending Multiply - blend_multiply() - - # Set - set_opacity(50) - - # Move - move_layer(5, 261) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 8 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(12, 263) - - # Reset - reset_colors() - - # Filter Gallery - Photocopy - filter_photocopy(detail=2, darken=5) - - # Set - blend_multiply() - - # Layer Via Copy - Background Copy 9 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Transform - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc1.putEnumerated(cID('FTcs'), cID('QCSt'), sID("QCSAverage")) - desc2 = ps.ActionDescriptor() - desc2.putUnitDouble(cID('Hrzn'), cID('#Pxl'), 0) - desc2.putUnitDouble(cID('Vrtc'), cID('#Pxl'), 0) - desc1.putObject(cID('Ofst'), cID('Ofst'), desc2) - desc1.putUnitDouble(cID('Wdth'), cID('#Prc'), 110) - desc1.putUnitDouble(cID('Hght'), cID('#Prc'), 110) - desc1.putBoolean(cID('Lnkd'), True) - desc1.putEnumerated(cID('Intr'), cID('Intp'), cID('Bcbc')) - APP.executeAction(cID('Trnf'), desc1, dialog_mode) - - # Select - select_layer("Background copy 8", 263) - - # Transform - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc1.putEnumerated(cID('FTcs'), cID('QCSt'), sID("QCSAverage")) - desc2 = ps.ActionDescriptor() - desc2.putUnitDouble(cID('Hrzn'), cID('#Pxl'), -2.27373675443232e-13) - desc2.putUnitDouble(cID('Vrtc'), cID('#Pxl'), 0) - desc1.putObject(cID('Ofst'), cID('Ofst'), desc2) - desc1.putUnitDouble(cID('Wdth'), cID('#Prc'), 90) - desc1.putUnitDouble(cID('Hght'), cID('#Prc'), 90) - desc1.putBoolean(cID('Lnkd'), True) - desc1.putEnumerated(cID('Intr'), cID('Intp'), cID('Bcbc')) - APP.executeAction(cID('Trnf'), desc1, dialog_mode) - - # Select - select_layers("Background copy 9", [263, 264]) - - # Set - set_opacity(10) - # Move - move_layer(8, [263, 264]) +def blend_color_dodge(): + blend("colorDodge") - # Select - select_layer("Background copy 7", 250) - # Select - select_layers("Layer 2", [ - 250, 240, 242, 261, 244, 246, 263, 264, 258, 255 - ]) +def blend_linear_light(): + blend("linearLight") - # Set - blend_linear_burn() - # Select - select_layer("Color Fill 1", 238) +def blend_linear_burn(): + blend("linearBurn") - # Make - new_layer(268) - # Fill - 50% Gray - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Usng'), cID('FlCn'), cID('Gry ')) - desc1.putUnitDouble(cID('Opct'), cID('#Prc'), 100) - desc1.putEnumerated(cID('Md '), cID('BlnM'), cID('Nrml')) - APP.executeAction(cID('Fl '), desc1, dialog_mode) +def blend_soft_light(): + blend("softLight") - # Filter Gallery - Texturizer - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('Txtz')) - desc1.putEnumerated(cID('TxtT'), cID('TxtT'), cID('TxSt')) - desc1.putInteger(cID('Scln'), 200) - desc1.putInteger(cID('Rlf '), 4) - desc1.putEnumerated(cID('LghD'), cID('LghD'), cID('LDTp')) - desc1.putBoolean(cID('InvT'), False) - APP.executeAction(1195730531, desc1, dialog_mode) - # Set - Blending Soft Light - blend_soft_light() +def blend_screen(): + blend("screen") - # Select - select_layer("Layer 4", 268) - # Make - new_layer(269) +def blend_overlay(): + blend("overlay") - # Fill - Foreground Color - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Usng'), cID('FlCn'), cID('FrgC')) - desc1.putUnitDouble(cID('Opct'), cID('#Prc'), 100) - desc1.putEnumerated(cID('Md '), cID('BlnM'), cID('Nrml')) - APP.executeAction(cID('Fl '), desc1, dialog_mode) - # Add Noise - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('Dstr'), cID('Dstr'), cID('Gsn ')) - desc1.putUnitDouble(cID('Nose'), cID('#Prc'), 25) - desc1.putBoolean(cID('Mnch'), True) - desc1.putInteger(cID('FlRs'), 1315132) - APP.executeAction(sID('addNoise'), desc1, dialog_mode) - APP.activeDocument.activeLayer.opacity = 40 - - # Set - blend_screen() - - # Levels Adjustment - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindCustom")) - list1 = ps.ActionList() - desc2 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Cmps')) - desc2.putReference(cID('Chnl'), ref1) - list2 = ps.ActionList() - list2.putInteger(0) - list2.putInteger(90) - desc2.putList(cID('Inpt'), list2) - list1.putObject(cID('LvlA'), desc2) - desc1.putList(cID('Adjs'), list1) - APP.executeAction(cID('Lvls'), desc1, dialog_mode) +def blend_color(): + blend("color") - # Select - select_layer("Background copy 4", 246) - - # Move - move_layer(6, 246) - - # Select - select_layer("Background copy 3", 244) - - # Set - set_opacity(20) - - # Select - select_layer("Layer 3", 258) - - # Select - select_layers("Layer 2", [258, 255]) - - # Set - set_opacity(40) - - # Select - select_layer("Background copy 5", 252) - - # Reset - reset_colors() - - # Make - Gradient Map - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc3 = ps.ActionDescriptor() - desc4 = ps.ActionDescriptor() - desc4.putString(cID('Nm '), "Foreground to Background") - desc4.putEnumerated(cID('GrdF'), cID('GrdF'), cID('CstS')) - desc4.putDouble(cID('Intr'), 4096) - list1 = ps.ActionList() - desc5 = ps.ActionDescriptor() - desc6 = ps.ActionDescriptor() - desc6.putDouble(cID('Rd '), 0) - desc6.putDouble(cID('Grn '), 0) - desc6.putDouble(cID('Bl '), 0) - desc5.putObject(cID('Clr '), sID("RGBColor"), desc6) - desc5.putEnumerated(cID('Type'), cID('Clry'), cID('UsrS')) - desc5.putInteger(cID('Lctn'), 0) - desc5.putInteger(cID('Mdpn'), 50) - list1.putObject(cID('Clrt'), desc5) - desc7 = ps.ActionDescriptor() - desc8 = ps.ActionDescriptor() - desc8.putDouble(cID('Rd '), 255) - desc8.putDouble(cID('Grn '), 255) - desc8.putDouble(cID('Bl '), 255) - desc7.putObject(cID('Clr '), sID("RGBColor"), desc8) - desc7.putEnumerated(cID('Type'), cID('Clry'), cID('UsrS')) - desc7.putInteger(cID('Lctn'), 4096) - desc7.putInteger(cID('Mdpn'), 50) - list1.putObject(cID('Clrt'), desc7) - desc4.putList(cID('Clrs'), list1) - list2 = ps.ActionList() - desc9 = ps.ActionDescriptor() - desc9.putUnitDouble(cID('Opct'), cID('#Prc'), 100) - desc9.putInteger(cID('Lctn'), 0) - desc9.putInteger(cID('Mdpn'), 50) - list2.putObject(cID('TrnS'), desc9) - desc10 = ps.ActionDescriptor() - desc10.putUnitDouble(cID('Opct'), cID('#Prc'), 100) - desc10.putInteger(cID('Lctn'), 4096) - desc10.putInteger(cID('Mdpn'), 50) - list2.putObject(cID('TrnS'), desc10) - desc4.putList(cID('Trns'), list2) - desc3.putObject(cID('Grad'), cID('Grdn'), desc4) - desc2.putObject(cID('Type'), cID('GdMp'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Set - blend_soft_light() - # Set - set_opacity(20) - - # Make - Levels Adjustment Layer - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc3 = ps.ActionDescriptor() - desc3.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindDefault")) - desc2.putObject(cID('Type'), cID('Lvls'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Set - Levels Adjustment Layer Settings - desc1 = ps.ActionDescriptor() - ref1 = ps.ActionReference() - ref1.putEnumerated(cID('AdjL'), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ps.ActionDescriptor() - desc2.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindCustom")) - list1 = ps.ActionList() - desc3 = ps.ActionDescriptor() - ref2 = ps.ActionReference() - ref2.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Cmps')) - desc3.putReference(cID('Chnl'), ref2) - desc3.putDouble(cID('Gmm '), 0.8) - list2 = ps.ActionList() - list2.putInteger(30) - list2.putInteger(250) - desc3.putList(cID('Inpt'), list2) - list1.putObject(cID('LvlA'), desc3) - desc2.putList(cID('Adjs'), list1) - desc1.putObject(cID('T '), cID('Lvls'), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Select - select_layer("Background copy 6", 112) - - # Set - set_opacity(40) - - # Select - select_bg() - - # Layer Via Copy - Background Copy 10 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(18, 121) - - # Reset - reset_colors() - - # Filter Gallery - Graphic Pen - desc1 = ps.ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('GraP')) - desc1.putInteger(cID('StrL'), 15) - desc1.putInteger(cID('LgDr'), 50) - desc1.putEnumerated(cID('SDir'), cID('StrD'), cID('SDRD')) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Set - blend_overlay() - - # Set - set_opacity(30) - - # Move - move_layer(5, 121) - - # Select - select_bg() - - # Layer Via Copy - Background copy 11 - APP.executeAction(sID('copyToLayer'), ps.ActionDescriptor(), dialog_mode) - - # Move - move_layer(16, 127) - - # Set - blend_color() - - """ +def run( + thr: Event, + draft_sketch: bool = False, + rough_sketch: bool = False, + black_and_white: bool = True, + manual_editing: bool = False, +): + """ + Pencil Sketchify Steps + """ + + # Is the main layer "Layer 1" + APP.instance.activeDocument.activeLayer.name = "Background" + + # Make - New Layer 1 + new_layer(139) + + # Select + select_bg() + + # Make - Solid Color Layer + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putClass(APP.instance.sID("contentLayer")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc3 = ps.ActionDescriptor() + desc4 = ps.ActionDescriptor() + desc4.putInteger(APP.instance.cID("Rd "), 166) + desc4.putInteger(APP.instance.cID("Grn "), 166) + desc4.putInteger(APP.instance.cID("Bl "), 166) + desc3.putObject(APP.instance.cID("Clr "), APP.instance.sID("RGBColor"), desc4) + desc2.putObject( + APP.instance.cID("Type"), APP.instance.sID("solidColorLayer"), desc3 + ) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.sID("contentLayer"), desc2) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Select + select_bg() + + # Layer Via Copy - Background copy + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(3, 240) + + # Reset Colors + reset_colors() + + # Filter Gallery - Photocopy + filter_photocopy(detail=2, darken=5) + + # Set - Blending Multiply + blend_multiply() + + # Select + select_bg() + + # Layer Via Copy - Background copy 2 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(4, 242) + + # Reset Colors + reset_colors() + + # Filter Gallery - Photocopy, Accented Edges + desc1 = ps.ActionDescriptor() + list1 = ps.ActionList() + desc2 = ps.ActionDescriptor() + desc3 = ps.ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("AccE") + ) + desc2.putInteger(APP.instance.cID("EdgW"), 3) + desc2.putInteger(APP.instance.cID("EdgB"), 20) + desc2.putInteger(APP.instance.cID("Smth"), 15) + list1.putObject(APP.instance.cID("GEfc"), desc2) + desc3.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Phtc") + ) + desc3.putInteger(APP.instance.cID("Dtl "), 1) + desc3.putInteger(APP.instance.cID("Drkn"), 49) + list1.putObject(APP.instance.cID("GEfc"), desc3) + desc1.putList(APP.instance.cID("GEfs"), list1) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Set - Blending Multiply + blend_multiply() + + # Select + select_bg() + + # Layer Via Copy - Background Copy 3 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(5, 244) + + # Reset Colors + reset_colors() + + # Filter Gallery - Photocopy, Stamp + desc1 = ps.ActionDescriptor() + list1 = ps.ActionList() + desc2 = ps.ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Stmp") + ) + desc2.putInteger(APP.instance.cID("LgDr"), 25) + desc2.putInteger(APP.instance.cID("Smth"), 40) + list1.putObject(APP.instance.cID("GEfc"), desc2) + desc3 = ps.ActionDescriptor() + desc3.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Phtc") + ) + desc3.putInteger(APP.instance.cID("Dtl "), 1) + desc3.putInteger(APP.instance.cID("Drkn"), 49) + list1.putObject(APP.instance.cID("GEfc"), desc3) + desc1.putList(APP.instance.cID("GEfs"), list1) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Set - Blending Multiply + blend_multiply() + + # Set + set_opacity(25) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 4 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(6, 246) + + # Reset + reset_colors() + + # Filter Gallery - Glowing Edge + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("GlwE") + ) + desc1.putInteger(APP.instance.cID("EdgW"), 1) + desc1.putInteger(APP.instance.cID("EdgB"), 20) + desc1.putInteger(APP.instance.cID("Smth"), 15) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Desaturate + APP.instance.executeAction( + APP.instance.cID("Dstt"), ps.ActionDescriptor(), dialog_mode + ) + + # Levels - Auto Tone + auto_tone() + + # Levels - Auto Contrast + auto_contrast() + + # Levels Adjustment + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindCustom"), + ) + list1 = ps.ActionList() + desc2 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Chnl"), APP.instance.cID("Chnl"), APP.instance.cID("Cmps") + ) + desc2.putReference(APP.instance.cID("Chnl"), ref1) + list2 = ps.ActionList() + list2.putInteger(25) + list2.putInteger(230) + desc2.putList(APP.instance.cID("Inpt"), list2) + list1.putObject(APP.instance.cID("LvlA"), desc2) + desc1.putList(APP.instance.cID("Adjs"), list1) + APP.instance.executeAction(APP.instance.cID("Lvls"), desc1, dialog_mode) + + # Invert + APP.instance.executeAction( + APP.instance.cID("Invr"), ps.ActionDescriptor(), dialog_mode + ) + + # Set + blend_multiply() + + # Select + select_bg() + + # Layer Via Copy - Background Copy 5 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(7, 248) + + # Layer Via Copy - Background Copy 6 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Invert + APP.instance.executeAction( + APP.instance.cID("Invr"), ps.ActionDescriptor(), dialog_mode + ) + + # Gaussian Blur + desc1 = ps.ActionDescriptor() + desc1.putUnitDouble(APP.instance.cID("Rds "), APP.instance.cID("#Pxl"), 50) + APP.instance.executeAction(APP.instance.sID("gaussianBlur"), desc1, dialog_mode) + + # Set - Blending Color Dodge + blend_color_dodge() + + # Select + select_layers("Background copy 5", [248, 249]) + + # Merge Layers + APP.instance.executeAction( + APP.instance.sID("mergeLayersNew"), ps.ActionDescriptor(), dialog_mode + ) + + # Desaturate + APP.instance.executeAction( + APP.instance.cID("Dstt"), ps.ActionDescriptor(), dialog_mode + ) + + # Layer Via Copy - Background Copy 7 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Reset + reset_colors() + + # Filter Gallery - Glowing Edge + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("GlwE") + ) + desc1.putInteger(APP.instance.cID("EdgW"), 1) + desc1.putInteger(APP.instance.cID("EdgB"), 20) + desc1.putInteger(APP.instance.cID("Smth"), 15) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Invert + APP.instance.executeAction( + APP.instance.cID("Invr"), ps.ActionDescriptor(), dialog_mode + ) + + # Set + blend_multiply() + + # Set + set_opacity(80) + + # Select + select_layers("Background copy 6", [249, 250]) + + # Merge Layers + APP.instance.executeAction( + APP.instance.sID("mergeLayersNew"), ps.ActionDescriptor(), dialog_mode + ) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 5 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(8, 252) + + # Desaturate + APP.instance.executeAction( + APP.instance.cID("Dstt"), ps.ActionDescriptor(), dialog_mode + ) + + # High Pass Filter + desc1 = ps.ActionDescriptor() + desc1.putUnitDouble(APP.instance.cID("Rds "), APP.instance.cID("#Pxl"), 30) + APP.instance.executeAction(APP.instance.sID("highPass"), desc1, dialog_mode) + + # Set - Blending Linear Light + blend_linear_light() + + # Set - Layer Style, Blending Options: Blend if Gray + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + list1 = ps.ActionList() + desc3 = ps.ActionDescriptor() + ref2 = ps.ActionReference() + ref2.putEnumerated( + APP.instance.cID("Chnl"), APP.instance.cID("Chnl"), APP.instance.cID("Gry ") + ) + desc3.putReference(APP.instance.cID("Chnl"), ref2) + desc3.putInteger(APP.instance.cID("SrcB"), 0) + desc3.putInteger(APP.instance.cID("Srcl"), 0) + desc3.putInteger(APP.instance.cID("SrcW"), 75) + desc3.putInteger(APP.instance.cID("Srcm"), 125) + desc3.putInteger(APP.instance.cID("DstB"), 55) + desc3.putInteger(APP.instance.cID("Dstl"), 125) + desc3.putInteger(APP.instance.cID("DstW"), 255) + desc3.putInteger(APP.instance.cID("Dstt"), 255) + list1.putObject(APP.instance.cID("Blnd"), desc3) + desc2.putList(APP.instance.cID("Blnd"), list1) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Set + set_opacity(50) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 6 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(9, 254) + + # Reset + reset_colors() + + # Filter Gallery - Cutout + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Ct ") + ) + desc1.putInteger(APP.instance.cID("NmbL"), 8) + desc1.putInteger(APP.instance.cID("EdgS"), 10) + desc1.putInteger(APP.instance.cID("EdgF"), 1) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Desaturate + APP.instance.executeAction( + APP.instance.cID("Dstt"), ps.ActionDescriptor(), dialog_mode + ) + + # Levels - Auto Tone + auto_tone() + + # Levels - Auto Contrast + auto_contrast() + + # Color Range + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Clrs"), APP.instance.cID("Clrs"), APP.instance.cID("Mdtn") + ) + desc1.putInteger(APP.instance.sID("midtonesFuzziness"), 40) + desc1.putInteger(APP.instance.sID("midtonesLowerLimit"), 105) + desc1.putInteger(APP.instance.sID("midtonesUpperLimit"), 150) + desc1.putInteger(APP.instance.sID("colorModel"), 0) + APP.instance.executeAction(APP.instance.sID("colorRange"), desc1, dialog_mode) + + # Layer Via Copy - Layer 2 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Find Edges + APP.instance.executeAction( + APP.instance.sID("findEdges"), ps.ActionDescriptor(), dialog_mode + ) + + # Hide + hide_layer("Background copy 6") + + # Hide + hide_layer() + + # Select + select_bg() + + # Layer Via Copy - Background copy 8 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(9, 257) + + # Reset + reset_colors() + + # Filter Gallery - Cutout + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Ct ") + ) + desc1.putInteger(APP.instance.cID("NmbL"), 8) + desc1.putInteger(APP.instance.cID("EdgS"), 8) + desc1.putInteger(APP.instance.cID("EdgF"), 1) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Desaturate + APP.instance.executeAction( + APP.instance.cID("Dstt"), ps.ActionDescriptor(), dialog_mode + ) + + # Levels + auto_tone() + + # Levels + auto_contrast() + + # Color Range + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Clrs"), APP.instance.cID("Clrs"), APP.instance.cID("Mdtn") + ) + desc1.putInteger(APP.instance.sID("midtonesFuzziness"), 40) + desc1.putInteger(APP.instance.sID("midtonesLowerLimit"), 105) + desc1.putInteger(APP.instance.sID("midtonesUpperLimit"), 150) + desc1.putInteger(APP.instance.sID("colorModel"), 0) + APP.instance.executeAction(APP.instance.sID("colorRange"), desc1, dialog_mode) + + # Layer Via Copy - Layer 3 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Find Edges + APP.instance.executeAction( + APP.instance.sID("findEdges"), ps.ActionDescriptor(), dialog_mode + ) + + # Select + select_layer("Background copy 8", 257) + + # Select + select_layers("Background copy 6", [257, 254]) + + # Delete + delete_layers([257, 254]) + + # Select + select_layer("Layer 2", 255) + + # Show + show_layer() + + # Select + select_layers("Layer 3", [258, 255]) + + # Set + blend_multiply() + + # Set + set_opacity(30) + + # Select + select_layer("Background copy 3", 244) + + # Set + set_opacity(30) + + # Select + select_layer("Background copy 7", 250) + + # Move + move_layer(2, 250) + + # Select + select_layer("Background copy 5", 252) + + # Move + move_layer(10, 252) + + # Select + select_layer("Background copy 7", 250) + + # Set + set_opacity(50) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 6 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(11, 261) + + # Filter - Distort Wave + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Wvtp"), APP.instance.cID("Wvtp"), APP.instance.cID("WvSn") + ) + desc1.putInteger(APP.instance.cID("NmbG"), 5) + desc1.putInteger(APP.instance.cID("WLMn"), 10) + desc1.putInteger(APP.instance.cID("WLMx"), 500) + desc1.putInteger(APP.instance.cID("AmMn"), 5) + desc1.putInteger(APP.instance.cID("AmMx"), 35) + desc1.putInteger(APP.instance.cID("SclH"), 100) + desc1.putInteger(APP.instance.cID("SclV"), 100) + desc1.putEnumerated( + APP.instance.cID("UndA"), APP.instance.cID("UndA"), APP.instance.cID("RptE") + ) + desc1.putInteger(APP.instance.cID("RndS"), 1260853) + APP.instance.executeAction(APP.instance.cID("Wave"), desc1, dialog_mode) + + # Reset + reset_colors() + + # Filter Gallery - Photocopy, Accented Edges + desc1 = ps.ActionDescriptor() + list1 = ps.ActionList() + desc2 = ps.ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("AccE") + ) + desc2.putInteger(APP.instance.cID("EdgW"), 3) + desc2.putInteger(APP.instance.cID("EdgB"), 20) + desc2.putInteger(APP.instance.cID("Smth"), 15) + list1.putObject(APP.instance.cID("GEfc"), desc2) + desc3 = ps.ActionDescriptor() + desc3.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Phtc") + ) + desc3.putInteger(APP.instance.cID("Dtl "), 1) + desc3.putInteger(APP.instance.cID("Drkn"), 49) + list1.putObject(APP.instance.cID("GEfc"), desc3) + desc1.putList(APP.instance.cID("GEfs"), list1) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Levels - Auto Tone + auto_tone() + + # Levels - Auto Contrast + auto_contrast() + + # Set - Blending Multiply + blend_multiply() + + # Set + set_opacity(50) + + # Move + move_layer(5, 261) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 8 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(12, 263) + + # Reset + reset_colors() + + # Filter Gallery - Photocopy + filter_photocopy(detail=2, darken=5) + + # Set + blend_multiply() + + # Layer Via Copy - Background Copy 9 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Transform + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putEnumerated( + APP.instance.cID("FTcs"), + APP.instance.cID("QCSt"), + APP.instance.sID("QCSAverage"), + ) + desc2 = ps.ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Hrzn"), APP.instance.cID("#Pxl"), 0) + desc2.putUnitDouble(APP.instance.cID("Vrtc"), APP.instance.cID("#Pxl"), 0) + desc1.putObject(APP.instance.cID("Ofst"), APP.instance.cID("Ofst"), desc2) + desc1.putUnitDouble(APP.instance.cID("Wdth"), APP.instance.cID("#Prc"), 110) + desc1.putUnitDouble(APP.instance.cID("Hght"), APP.instance.cID("#Prc"), 110) + desc1.putBoolean(APP.instance.cID("Lnkd"), True) + desc1.putEnumerated( + APP.instance.cID("Intr"), APP.instance.cID("Intp"), APP.instance.cID("Bcbc") + ) + APP.instance.executeAction(APP.instance.cID("Trnf"), desc1, dialog_mode) + + # Select + select_layer("Background copy 8", 263) + + # Transform + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putEnumerated( + APP.instance.cID("FTcs"), + APP.instance.cID("QCSt"), + APP.instance.sID("QCSAverage"), + ) + desc2 = ps.ActionDescriptor() + desc2.putUnitDouble( + APP.instance.cID("Hrzn"), APP.instance.cID("#Pxl"), -2.27373675443232e-13 + ) + desc2.putUnitDouble(APP.instance.cID("Vrtc"), APP.instance.cID("#Pxl"), 0) + desc1.putObject(APP.instance.cID("Ofst"), APP.instance.cID("Ofst"), desc2) + desc1.putUnitDouble(APP.instance.cID("Wdth"), APP.instance.cID("#Prc"), 90) + desc1.putUnitDouble(APP.instance.cID("Hght"), APP.instance.cID("#Prc"), 90) + desc1.putBoolean(APP.instance.cID("Lnkd"), True) + desc1.putEnumerated( + APP.instance.cID("Intr"), APP.instance.cID("Intp"), APP.instance.cID("Bcbc") + ) + APP.instance.executeAction(APP.instance.cID("Trnf"), desc1, dialog_mode) + + # Select + select_layers("Background copy 9", [263, 264]) + + # Set + set_opacity(10) + + # Move + move_layer(8, [263, 264]) + + # Select + select_layer("Background copy 7", 250) + + # Select + select_layers("Layer 2", [250, 240, 242, 261, 244, 246, 263, 264, 258, 255]) + + # Set + blend_linear_burn() + + # Select + select_layer("Color Fill 1", 238) + + # Make + new_layer(268) + + # Fill - 50% Gray + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Usng"), APP.instance.cID("FlCn"), APP.instance.cID("Gry ") + ) + desc1.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 100) + desc1.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.cID("Nrml") + ) + APP.instance.executeAction(APP.instance.cID("Fl "), desc1, dialog_mode) + + # Filter Gallery - Texturizer + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Txtz") + ) + desc1.putEnumerated( + APP.instance.cID("TxtT"), APP.instance.cID("TxtT"), APP.instance.cID("TxSt") + ) + desc1.putInteger(APP.instance.cID("Scln"), 200) + desc1.putInteger(APP.instance.cID("Rlf "), 4) + desc1.putEnumerated( + APP.instance.cID("LghD"), APP.instance.cID("LghD"), APP.instance.cID("LDTp") + ) + desc1.putBoolean(APP.instance.cID("InvT"), False) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Set - Blending Soft Light + blend_soft_light() + + # Select + select_layer("Layer 4", 268) + + # Make + new_layer(269) + + # Fill - Foreground Color + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Usng"), APP.instance.cID("FlCn"), APP.instance.cID("FrgC") + ) + desc1.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 100) + desc1.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.cID("Nrml") + ) + APP.instance.executeAction(APP.instance.cID("Fl "), desc1, dialog_mode) + + # Add Noise + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Dstr"), APP.instance.cID("Dstr"), APP.instance.cID("Gsn ") + ) + desc1.putUnitDouble(APP.instance.cID("Nose"), APP.instance.cID("#Prc"), 25) + desc1.putBoolean(APP.instance.cID("Mnch"), True) + desc1.putInteger(APP.instance.cID("FlRs"), 1315132) + APP.instance.executeAction(APP.instance.sID("addNoise"), desc1, dialog_mode) + APP.instance.activeDocument.activeLayer.opacity = 40 + + # Set + blend_screen() + + # Levels Adjustment + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindCustom"), + ) + list1 = ps.ActionList() + desc2 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("Chnl"), APP.instance.cID("Chnl"), APP.instance.cID("Cmps") + ) + desc2.putReference(APP.instance.cID("Chnl"), ref1) + list2 = ps.ActionList() + list2.putInteger(0) + list2.putInteger(90) + desc2.putList(APP.instance.cID("Inpt"), list2) + list1.putObject(APP.instance.cID("LvlA"), desc2) + desc1.putList(APP.instance.cID("Adjs"), list1) + APP.instance.executeAction(APP.instance.cID("Lvls"), desc1, dialog_mode) + + # Select + select_layer("Background copy 4", 246) + + # Move + move_layer(6, 246) + + # Select + select_layer("Background copy 3", 244) + + # Set + set_opacity(20) + + # Select + select_layer("Layer 3", 258) + + # Select + select_layers("Layer 2", [258, 255]) + + # Set + set_opacity(40) + + # Select + select_layer("Background copy 5", 252) + + # Reset + reset_colors() + + # Make - Gradient Map + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putClass(APP.instance.cID("AdjL")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc3 = ps.ActionDescriptor() + desc4 = ps.ActionDescriptor() + desc4.putString(APP.instance.cID("Nm "), "Foreground to Background") + desc4.putEnumerated( + APP.instance.cID("GrdF"), APP.instance.cID("GrdF"), APP.instance.cID("CstS") + ) + desc4.putDouble(APP.instance.cID("Intr"), 4096) + list1 = ps.ActionList() + desc5 = ps.ActionDescriptor() + desc6 = ps.ActionDescriptor() + desc6.putDouble(APP.instance.cID("Rd "), 0) + desc6.putDouble(APP.instance.cID("Grn "), 0) + desc6.putDouble(APP.instance.cID("Bl "), 0) + desc5.putObject(APP.instance.cID("Clr "), APP.instance.sID("RGBColor"), desc6) + desc5.putEnumerated( + APP.instance.cID("Type"), APP.instance.cID("Clry"), APP.instance.cID("UsrS") + ) + desc5.putInteger(APP.instance.cID("Lctn"), 0) + desc5.putInteger(APP.instance.cID("Mdpn"), 50) + list1.putObject(APP.instance.cID("Clrt"), desc5) + desc7 = ps.ActionDescriptor() + desc8 = ps.ActionDescriptor() + desc8.putDouble(APP.instance.cID("Rd "), 255) + desc8.putDouble(APP.instance.cID("Grn "), 255) + desc8.putDouble(APP.instance.cID("Bl "), 255) + desc7.putObject(APP.instance.cID("Clr "), APP.instance.sID("RGBColor"), desc8) + desc7.putEnumerated( + APP.instance.cID("Type"), APP.instance.cID("Clry"), APP.instance.cID("UsrS") + ) + desc7.putInteger(APP.instance.cID("Lctn"), 4096) + desc7.putInteger(APP.instance.cID("Mdpn"), 50) + list1.putObject(APP.instance.cID("Clrt"), desc7) + desc4.putList(APP.instance.cID("Clrs"), list1) + list2 = ps.ActionList() + desc9 = ps.ActionDescriptor() + desc9.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 100) + desc9.putInteger(APP.instance.cID("Lctn"), 0) + desc9.putInteger(APP.instance.cID("Mdpn"), 50) + list2.putObject(APP.instance.cID("TrnS"), desc9) + desc10 = ps.ActionDescriptor() + desc10.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 100) + desc10.putInteger(APP.instance.cID("Lctn"), 4096) + desc10.putInteger(APP.instance.cID("Mdpn"), 50) + list2.putObject(APP.instance.cID("TrnS"), desc10) + desc4.putList(APP.instance.cID("Trns"), list2) + desc3.putObject(APP.instance.cID("Grad"), APP.instance.cID("Grdn"), desc4) + desc2.putObject(APP.instance.cID("Type"), APP.instance.cID("GdMp"), desc3) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.cID("AdjL"), desc2) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Set + blend_soft_light() + + # Set + set_opacity(20) + + # Make - Levels Adjustment Layer + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putClass(APP.instance.cID("AdjL")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc3 = ps.ActionDescriptor() + desc3.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindDefault"), + ) + desc2.putObject(APP.instance.cID("Type"), APP.instance.cID("Lvls"), desc3) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.cID("AdjL"), desc2) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Set - Levels Adjustment Layer Settings + desc1 = ps.ActionDescriptor() + ref1 = ps.ActionReference() + ref1.putEnumerated( + APP.instance.cID("AdjL"), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ps.ActionDescriptor() + desc2.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindCustom"), + ) + list1 = ps.ActionList() + desc3 = ps.ActionDescriptor() + ref2 = ps.ActionReference() + ref2.putEnumerated( + APP.instance.cID("Chnl"), APP.instance.cID("Chnl"), APP.instance.cID("Cmps") + ) + desc3.putReference(APP.instance.cID("Chnl"), ref2) + desc3.putDouble(APP.instance.cID("Gmm "), 0.8) + list2 = ps.ActionList() + list2.putInteger(30) + list2.putInteger(250) + desc3.putList(APP.instance.cID("Inpt"), list2) + list1.putObject(APP.instance.cID("LvlA"), desc3) + desc2.putList(APP.instance.cID("Adjs"), list1) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lvls"), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Select + select_layer("Background copy 6", 112) + + # Set + set_opacity(40) + + # Select + select_bg() + + # Layer Via Copy - Background Copy 10 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(18, 121) + + # Reset + reset_colors() + + # Filter Gallery - Graphic Pen + desc1 = ps.ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("GraP") + ) + desc1.putInteger(APP.instance.cID("StrL"), 15) + desc1.putInteger(APP.instance.cID("LgDr"), 50) + desc1.putEnumerated( + APP.instance.cID("SDir"), APP.instance.cID("StrD"), APP.instance.cID("SDRD") + ) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Set + blend_overlay() + + # Set + set_opacity(30) + + # Move + move_layer(5, 121) + + # Select + select_bg() + + # Layer Via Copy - Background copy 11 + APP.instance.executeAction( + APP.instance.sID("copyToLayer"), ps.ActionDescriptor(), dialog_mode + ) + + # Move + move_layer(16, 127) + + # Set + blend_color() + + """ # Make - Hue/Saturation Adjustment desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) + ref1.putClass(APP.instance.cID('AdjL')) + desc1.putReference(APP.instance.cID('null'), ref1) desc2 = ps.ActionDescriptor() desc3 = ps.ActionDescriptor() - desc3.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindDefault")) - desc3.putBoolean(cID('Clrz'), False) - desc2.putObject(cID('Type'), cID('HStr'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) + desc3.putEnumerated(APP.instance.sID("presetKind"), APP.instance.sID("presetKindType"), APP.instance.sID("presetKindDefault")) + desc3.putBoolean(APP.instance.cID('Clrz'), False) + desc2.putObject(APP.instance.cID('Type'), APP.instance.cID('HStr'), desc3) + desc1.putObject(APP.instance.cID('Usng'), APP.instance.cID('AdjL'), desc2) + APP.instance.executeAction(APP.instance.cID('Mk '), desc1, dialog_mode) # Create Clipping Mask desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - APP.executeAction(sID('groupEvent'), desc1, dialog_mode) + ref1.putEnumerated(APP.instance.cID('Lyr '), APP.instance.cID('Ordn'), APP.instance.cID('Trgt')) + desc1.putReference(APP.instance.cID('null'), ref1) + APP.instance.executeAction(APP.instance.sID('groupEvent'), desc1, dialog_mode) # Select select_layers("Background copy 11", [127, 128]) @@ -1021,67 +1220,67 @@ def run( # Make - Hue/Saturation Adjustment Layer desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) + ref1.putClass(APP.instance.cID('AdjL')) + desc1.putReference(APP.instance.cID('null'), ref1) desc2 = ps.ActionDescriptor() desc3 = ps.ActionDescriptor() - desc3.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindDefault")) - desc3.putBoolean(cID('Clrz'), False) - desc2.putObject(cID('Type'), cID('HStr'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) + desc3.putEnumerated(APP.instance.sID("presetKind"), APP.instance.sID("presetKindType"), APP.instance.sID("presetKindDefault")) + desc3.putBoolean(APP.instance.cID('Clrz'), False) + desc2.putObject(APP.instance.cID('Type'), APP.instance.cID('HStr'), desc3) + desc1.putObject(APP.instance.cID('Usng'), APP.instance.cID('AdjL'), desc2) + APP.instance.executeAction(APP.instance.cID('Mk '), desc1, dialog_mode) # Merge Visible desc1 = ps.ActionDescriptor() - desc1.putBoolean(cID('Dplc'), True) - APP.executeAction(sID('mergeVisible'), desc1, dialog_mode) + desc1.putBoolean(APP.instance.cID('Dplc'), True) + APP.instance.executeAction(APP.instance.sID('mergeVisible'), desc1, dialog_mode) # Desaturate def step182(): - APP.executeAction(cID('Dstt'), ps.ActionDescriptor(), dialog_mode) + APP.instance.executeAction(APP.instance.cID('Dstt'), ps.ActionDescriptor(), dialog_mode) # High Pass def step183(): desc1 = ps.ActionDescriptor() - desc1.putUnitDouble(cID('Rds '), cID('#Pxl'), 1) - APP.executeAction(sID('highPass'), desc1, dialog_mode) + desc1.putUnitDouble(APP.instance.cID('Rds '), APP.instance.cID('#Pxl'), 1) + APP.instance.executeAction(APP.instance.sID('highPass'), desc1, dialog_mode) # Set def step184(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) + ref1.putEnumerated(APP.instance.cID('Lyr '), APP.instance.cID('Ordn'), APP.instance.cID('Trgt')) + desc1.putReference(APP.instance.cID('null'), ref1) desc2 = ps.ActionDescriptor() - desc2.putEnumerated(cID('Md '), cID('BlnM'), sID("vividLight")) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) + desc2.putEnumerated(APP.instance.cID('Md '), APP.instance.cID('BlnM'), APP.instance.sID("vividLight")) + desc1.putObject(APP.instance.cID('T '), APP.instance.cID('Lyr '), desc2) + APP.instance.executeAction(APP.instance.cID('setd'), desc1, dialog_mode) # Select def step185(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), "Color Fill 1") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) + ref1.putName(APP.instance.cID('Lyr '), "Color Fill 1") + desc1.putReference(APP.instance.cID('null'), ref1) + desc1.putBoolean(APP.instance.cID('MkVs'), False) list1 = ps.ActionList() list1.putInteger(90) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1.putList(APP.instance.cID('LyrI'), list1) + APP.instance.executeAction(APP.instance.cID('slct'), desc1, dialog_mode) # Select def step186(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), "Layer 6") - desc1.putReference(cID('null'), ref1) - desc1.putEnumerated(sID("selectionModifier"), sID("selectionModifierType"), sID("addToSelectionContinuous")) - desc1.putBoolean(cID('MkVs'), False) + ref1.putName(APP.instance.cID('Lyr '), "Layer 6") + desc1.putReference(APP.instance.cID('null'), ref1) + desc1.putEnumerated(APP.instance.sID("selectionModifier"), APP.instance.sID("selectionModifierType"), APP.instance.sID("addToSelectionContinuous")) + desc1.putBoolean(APP.instance.cID('MkVs'), False) list1 = ps.ActionList() list1.putInteger(90) list1.putInteger(116) @@ -1104,94 +1303,94 @@ def step186(): list1.putInteger(119) list1.putInteger(130) list1.putInteger(131) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1.putList(APP.instance.cID('LyrI'), list1) + APP.instance.executeAction(APP.instance.cID('slct'), desc1, dialog_mode) # Make def step187(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putClass(sID("layerSection")) - desc1.putReference(cID('null'), ref1) + ref1.putClass(APP.instance.sID("layerSection")) + desc1.putReference(APP.instance.cID('null'), ref1) ref2 = ps.ActionReference() - ref2.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('From'), ref2) + ref2.putEnumerated(APP.instance.cID('Lyr '), APP.instance.cID('Ordn'), APP.instance.cID('Trgt')) + desc1.putReference(APP.instance.cID('From'), ref2) desc2 = ps.ActionDescriptor() - desc2.putString(cID('Nm '), "Pencil Sketch") - desc1.putObject(cID('Usng'), sID("layerSection"), desc2) - desc1.putInteger(sID("layerSectionStart"), 136) - desc1.putInteger(sID("layerSectionEnd"), 137) - desc1.putString(cID('Nm '), "Pencil Sketch") - APP.executeAction(cID('Mk '), desc1, dialog_mode) + desc2.putString(APP.instance.cID('Nm '), "Pencil Sketch") + desc1.putObject(APP.instance.cID('Usng'), APP.instance.sID("layerSection"), desc2) + desc1.putInteger(APP.instance.sID("layerSectionStart"), 136) + desc1.putInteger(APP.instance.sID("layerSectionEnd"), 137) + desc1.putString(APP.instance.cID('Nm '), "Pencil Sketch") + APP.instance.executeAction(APP.instance.cID('Mk '), desc1, dialog_mode) # Make def step188(): desc1 = ps.ActionDescriptor() - desc1.putClass(cID('Nw '), cID('Chnl')) + desc1.putClass(APP.instance.cID('Nw '), APP.instance.cID('Chnl')) ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Msk ')) - desc1.putReference(cID('At '), ref1) - desc1.putEnumerated(cID('Usng'), cID('UsrM'), cID('RvlA')) - APP.executeAction(cID('Mk '), desc1, dialog_mode) + ref1.putEnumerated(APP.instance.cID('Chnl'), APP.instance.cID('Chnl'), APP.instance.cID('Msk ')) + desc1.putReference(APP.instance.cID('At '), ref1) + desc1.putEnumerated(APP.instance.cID('Usng'), APP.instance.cID('UsrM'), APP.instance.cID('RvlA')) + APP.instance.executeAction(APP.instance.cID('Mk '), desc1, dialog_mode) # Set def step189(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) + ref1.putEnumerated(APP.instance.cID('Lyr '), APP.instance.cID('Ordn'), APP.instance.cID('Trgt')) + desc1.putReference(APP.instance.cID('null'), ref1) desc2 = ps.ActionDescriptor() - desc2.putBoolean(cID('Usrs'), False) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) + desc2.putBoolean(APP.instance.cID('Usrs'), False) + desc1.putObject(APP.instance.cID('T '), APP.instance.cID('Lyr '), desc2) + APP.instance.executeAction(APP.instance.cID('setd'), desc1, dialog_mode) # Select def step190(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putName(cID('Lyr '), "Layer 1") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) + ref1.putName(APP.instance.cID('Lyr '), "Layer 1") + desc1.putReference(APP.instance.cID('null'), ref1) + desc1.putBoolean(APP.instance.cID('MkVs'), False) list1 = ps.ActionList() list1.putInteger(139) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) + desc1.putList(APP.instance.cID('LyrI'), list1) + APP.instance.executeAction(APP.instance.cID('slct'), desc1, dialog_mode) # Delete def step191(): desc1 = ps.ActionDescriptor() ref1 = ps.ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) + ref1.putEnumerated(APP.instance.cID('Lyr '), APP.instance.cID('Ordn'), APP.instance.cID('Trgt')) + desc1.putReference(APP.instance.cID('null'), ref1) list1 = ps.ActionList() list1.putInteger(139) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('Dlt '), desc1, dialog_mode) + desc1.putList(APP.instance.cID('LyrI'), list1) + APP.instance.executeAction(APP.instance.cID('Dlt '), desc1, dialog_mode) # Select Background def step343(): select_bg() """ - if not draft_sketch: - hide_layer("Layer 2") - hide_layer("Layer 3") + if not draft_sketch: + hide_layer("Layer 2") + hide_layer("Layer 3") - if not rough_sketch: - hide_layer("Background copy 3") - hide_layer("Background copy 6") - hide_layer("Background copy 8") - hide_layer("Background copy 9") + if not rough_sketch: + hide_layer("Background copy 3") + hide_layer("Background copy 6") + hide_layer("Background copy 8") + hide_layer("Background copy 9") - if black_and_white: - hide_layer("Background copy 11") + if black_and_white: + hide_layer("Background copy 11") - # Flatten - if manual_editing: - CONSOLE.await_choice(thr, "Sketch Action complete, hit continue when ready!") - APP.executeAction(cID("FltI"), None, dialog_mode) + # Flatten + if manual_editing: + CONSOLE.await_choice(thr, "Sketch Action complete, hit continue when ready!") + APP.instance.executeAction(APP.instance.cID("FltI"), None, dialog_mode) diff --git a/plugins/Investigamer/py/actions/sketch.py b/plugins/Investigamer/py/actions/sketch.py index 69e64065..9616e06d 100644 --- a/plugins/Investigamer/py/actions/sketch.py +++ b/plugins/Investigamer/py/actions/sketch.py @@ -1,429 +1,547 @@ """ * Sketchify Action Module """ + # Third Party Imports from photoshop.api import ActionDescriptor, ActionList, ActionReference, DialogModes # Local Imports from src import APP -# QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID - def run(): - """Trix old sketchify Steps.""" - - # Duplicate - def step1(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putProperty(cID('Lyr '), cID('Bckg')) - desc1.putReference(cID('null'), ref1) - desc1.putString(cID('Nm '), "Layer 1 copy") - desc1.putInteger(cID('Vrsn'), 5) - APP.executeAction(cID('Dplc'), desc1, dialog_mode) - - # Invert - def step2(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - APP.executeAction(cID('Invr'), ActionDescriptor(), dialog_mode) - - # Gaussian Blur - def step3(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - desc1.putUnitDouble(cID('Rds '), cID('#Pxl'), 65) - APP.executeAction(sID('gaussianBlur'), desc1, dialog_mode) - - # Set - def step4(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putEnumerated(cID('Md '), cID('BlnM'), cID('CDdg')) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Make - def step5(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc3 = ActionDescriptor() - desc3.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindDefault")) - desc2.putObject(cID('Type'), cID('Lvls'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Set - def step6(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('AdjL'), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindCustom")) - list1 = ActionList() - desc3 = ActionDescriptor() - ref2 = ActionReference() - ref2.putEnumerated(cID('Chnl'), cID('Chnl'), cID('Cmps')) - desc3.putReference(cID('Chnl'), ref2) - list2 = ActionList() - list2.putInteger(91) - list2.putInteger(255) - desc3.putList(cID('Inpt'), list2) - desc3.putDouble(cID('Gmm '), 0.66) - list1.putObject(cID('LvlA'), desc3) - desc2.putList(cID('Adjs'), list1) - desc1.putObject(cID('T '), cID('Lvls'), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Make - def step7(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putClass(cID('AdjL')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc3 = ActionDescriptor() - desc3.putEnumerated(sID("presetKind"), sID("presetKindType"), sID("presetKindDefault")) - desc3.putInteger(cID('Rd '), 40) - desc3.putInteger(cID('Yllw'), 60) - desc3.putInteger(cID('Grn '), 40) - desc3.putInteger(cID('Cyn '), 60) - desc3.putInteger(cID('Bl '), 20) - desc3.putInteger(cID('Mgnt'), 80) - desc3.putBoolean(sID("useTint"), False) - desc4 = ActionDescriptor() - desc4.putDouble(cID('Rd '), 225) - desc4.putDouble(cID('Grn '), 211) - desc4.putDouble(cID('Bl '), 179) - desc3.putObject(sID("tintColor"), sID("RGBColor"), desc4) - desc2.putObject(cID('Type'), cID('BanW'), desc3) - desc1.putObject(cID('Usng'), cID('AdjL'), desc2) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Set - def step8(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putProperty(cID('Chnl'), sID("selection")) - desc1.putReference(cID('null'), ref1) - desc1.putEnumerated(cID('T '), cID('Ordn'), cID('Al ')) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Copy Merged - def step9(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - APP.executeAction(sID('copyMerged'), ActionDescriptor(), dialog_mode) - - # Paste - def step10(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - desc1.putEnumerated(cID('AntA'), cID('Annt'), cID('Anno')) - desc1.putClass(cID('As '), cID('Pxel')) - APP.executeAction(cID('past'), desc1, dialog_mode) - - # Filter Gallery - def step11(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('GlwE')) - desc1.putInteger(cID('EdgW'), 1) - desc1.putInteger(cID('EdgB'), 20) - desc1.putInteger(cID('Smth'), 15) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Invert - def step12(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - APP.executeAction(cID('Invr'), ActionDescriptor(), dialog_mode) - - # Blend mode "Multiply" - # Step 13 and Step 20 - def blend_mode_multiply(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putEnumerated(cID('Md '), cID('BlnM'), cID('Mltp')) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Set - def step14(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putUnitDouble(cID('Opct'), cID('#Prc'), 60) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Select - def step15(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putName(cID('Lyr '), "Black & White 1") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ActionList() - list1.putInteger(6) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) - - # Make - def step16(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putClass(cID('Lyr ')) - desc1.putReference(cID('null'), ref1) - desc1.putInteger(cID('LyrI'), 8) - APP.executeAction(cID('Mk '), desc1, dialog_mode) - - # Select - def step17(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putName(cID('Lyr '), "Layer 2") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ActionList() - list1.putInteger(7) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) - - # Fill - def step18(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - desc1.putEnumerated(cID('Usng'), cID('FlCn'), cID('BckC')) - APP.executeAction(cID('Fl '), desc1, dialog_mode) - - # Filter Gallery - def step19(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - desc1.putEnumerated(cID('GEfk'), cID('GEft'), cID('Txtz')) - desc1.putEnumerated(cID('TxtT'), cID('TxtT'), cID('TxSt')) - desc1.putInteger(cID('Scln'), 100) - desc1.putInteger(cID('Rlf '), 4) - desc1.putEnumerated(cID('LghD'), cID('LghD'), cID('LDTp')) - desc1.putBoolean(cID('InvT'), False) - APP.executeAction(1195730531, desc1, dialog_mode) - - # Set - def step21(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putUnitDouble(cID('Opct'), cID('#Prc'), 70) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Select - def step22(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putName(cID('Lyr '), "Black & White 1") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ActionList() - list1.putInteger(6) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) - - # Set - def step23(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putUnitDouble(cID('Opct'), cID('#Prc'), 50) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Select - def step24(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putName(cID('Lyr '), "Background") - desc1.putReference(cID('null'), ref1) - desc1.putBoolean(cID('MkVs'), False) - list1 = ActionList() - list1.putInteger(1) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('slct'), desc1, dialog_mode) - - # Duplicate - def step25(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc1.putString(cID('Nm '), "top adjustment") - desc1.putInteger(cID('Vrsn'), 5) - list1 = ActionList() - list1.putInteger(35) - desc1.putList(cID('Idnt'), list1) - APP.executeAction(cID('Dplc'), desc1, dialog_mode) - - # Move - def step26(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - ref2 = ActionReference() - ref2.putIndex(cID('Lyr '), 7) - desc1.putReference(cID('T '), ref2) - desc1.putBoolean(cID('Adjs'), False) - desc1.putInteger(cID('Vrsn'), 5) - list1 = ActionList() - list1.putInteger(35) - desc1.putList(cID('LyrI'), list1) - APP.executeAction(cID('move'), desc1, dialog_mode) - - # Set - def step27(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putUnitDouble(cID('Opct'), cID('#Prc'), 40) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Set - def step28(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - desc1 = ActionDescriptor() - ref1 = ActionReference() - ref1.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('null'), ref1) - desc2 = ActionDescriptor() - desc2.putEnumerated(cID('Md '), cID('BlnM'), cID('HrdL')) - desc1.putObject(cID('T '), cID('Lyr '), desc2) - APP.executeAction(cID('setd'), desc1, dialog_mode) - - # Merge Visible - def step29(enabled: bool = True, dialog: bool = False): - if not enabled: - return - dialog_mode = DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs - APP.executeAction(sID('mergeVisible'), ActionDescriptor(), dialog_mode) - - # Run each step - step1() # Duplicate - step2() # Invert - step3() # Gaussian Blur - step4() # Set - step5() # Make - step6() # Set - step7() # Make - step8() # Set - step9() # Copy Merged - step10() # Paste - step11() # Filter Gallery - step12() # Invert - blend_mode_multiply() # Set - step14() # Set - step15() # Select - step16() # Make - step17() # Select - step18() # Fill - step19() # Filter Gallery - blend_mode_multiply() # Set - step21() # Set - step22() # Select - step23() # Set - step24() # Select - step25() # Duplicate - step26() # Move - step27() # Set - step28() # Set - step29() # Merge + """Trix old sketchify Steps.""" + + # Duplicate + def step1(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putProperty(APP.instance.cID("Lyr "), APP.instance.cID("Bckg")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putString(APP.instance.cID("Nm "), "Layer 1 copy") + desc1.putInteger(APP.instance.cID("Vrsn"), 5) + APP.instance.executeAction(APP.instance.cID("Dplc"), desc1, dialog_mode) + + # Invert + def step2(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + APP.instance.executeAction( + APP.instance.cID("Invr"), ActionDescriptor(), dialog_mode + ) + + # Gaussian Blur + def step3(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + desc1.putUnitDouble(APP.instance.cID("Rds "), APP.instance.cID("#Pxl"), 65) + APP.instance.executeAction(APP.instance.sID("gaussianBlur"), desc1, dialog_mode) + + # Set + def step4(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.cID("CDdg") + ) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Make + def step5(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putClass(APP.instance.cID("AdjL")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc3 = ActionDescriptor() + desc3.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindDefault"), + ) + desc2.putObject(APP.instance.cID("Type"), APP.instance.cID("Lvls"), desc3) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.cID("AdjL"), desc2) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Set + def step6(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("AdjL"), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindCustom"), + ) + list1 = ActionList() + desc3 = ActionDescriptor() + ref2 = ActionReference() + ref2.putEnumerated( + APP.instance.cID("Chnl"), APP.instance.cID("Chnl"), APP.instance.cID("Cmps") + ) + desc3.putReference(APP.instance.cID("Chnl"), ref2) + list2 = ActionList() + list2.putInteger(91) + list2.putInteger(255) + desc3.putList(APP.instance.cID("Inpt"), list2) + desc3.putDouble(APP.instance.cID("Gmm "), 0.66) + list1.putObject(APP.instance.cID("LvlA"), desc3) + desc2.putList(APP.instance.cID("Adjs"), list1) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lvls"), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Make + def step7(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putClass(APP.instance.cID("AdjL")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc3 = ActionDescriptor() + desc3.putEnumerated( + APP.instance.sID("presetKind"), + APP.instance.sID("presetKindType"), + APP.instance.sID("presetKindDefault"), + ) + desc3.putInteger(APP.instance.cID("Rd "), 40) + desc3.putInteger(APP.instance.cID("Yllw"), 60) + desc3.putInteger(APP.instance.cID("Grn "), 40) + desc3.putInteger(APP.instance.cID("Cyn "), 60) + desc3.putInteger(APP.instance.cID("Bl "), 20) + desc3.putInteger(APP.instance.cID("Mgnt"), 80) + desc3.putBoolean(APP.instance.sID("useTint"), False) + desc4 = ActionDescriptor() + desc4.putDouble(APP.instance.cID("Rd "), 225) + desc4.putDouble(APP.instance.cID("Grn "), 211) + desc4.putDouble(APP.instance.cID("Bl "), 179) + desc3.putObject( + APP.instance.sID("tintColor"), APP.instance.sID("RGBColor"), desc4 + ) + desc2.putObject(APP.instance.cID("Type"), APP.instance.cID("BanW"), desc3) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.cID("AdjL"), desc2) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Set + def step8(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putProperty(APP.instance.cID("Chnl"), APP.instance.sID("selection")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putEnumerated( + APP.instance.cID("T "), APP.instance.cID("Ordn"), APP.instance.cID("Al ") + ) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Copy Merged + def step9(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + APP.instance.executeAction( + APP.instance.sID("copyMerged"), ActionDescriptor(), dialog_mode + ) + + # Paste + def step10(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("AntA"), APP.instance.cID("Annt"), APP.instance.cID("Anno") + ) + desc1.putClass(APP.instance.cID("As "), APP.instance.cID("Pxel")) + APP.instance.executeAction(APP.instance.cID("past"), desc1, dialog_mode) + + # Filter Gallery + def step11(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("GlwE") + ) + desc1.putInteger(APP.instance.cID("EdgW"), 1) + desc1.putInteger(APP.instance.cID("EdgB"), 20) + desc1.putInteger(APP.instance.cID("Smth"), 15) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Invert + def step12(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + APP.instance.executeAction( + APP.instance.cID("Invr"), ActionDescriptor(), dialog_mode + ) + + # Blend mode "Multiply" + # Step 13 and Step 20 + def blend_mode_multiply(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.cID("Mltp") + ) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Set + def step14(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 60) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Select + def step15(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putName(APP.instance.cID("Lyr "), "Black & White 1") + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ActionList() + list1.putInteger(6) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) + + # Make + def step16(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putClass(APP.instance.cID("Lyr ")) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putInteger(APP.instance.cID("LyrI"), 8) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, dialog_mode) + + # Select + def step17(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putName(APP.instance.cID("Lyr "), "Layer 2") + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ActionList() + list1.putInteger(7) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) + + # Fill + def step18(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("Usng"), APP.instance.cID("FlCn"), APP.instance.cID("BckC") + ) + APP.instance.executeAction(APP.instance.cID("Fl "), desc1, dialog_mode) + + # Filter Gallery + def step19(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + desc1.putEnumerated( + APP.instance.cID("GEfk"), APP.instance.cID("GEft"), APP.instance.cID("Txtz") + ) + desc1.putEnumerated( + APP.instance.cID("TxtT"), APP.instance.cID("TxtT"), APP.instance.cID("TxSt") + ) + desc1.putInteger(APP.instance.cID("Scln"), 100) + desc1.putInteger(APP.instance.cID("Rlf "), 4) + desc1.putEnumerated( + APP.instance.cID("LghD"), APP.instance.cID("LghD"), APP.instance.cID("LDTp") + ) + desc1.putBoolean(APP.instance.cID("InvT"), False) + APP.instance.executeAction(1195730531, desc1, dialog_mode) + + # Set + def step21(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 70) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Select + def step22(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putName(APP.instance.cID("Lyr "), "Black & White 1") + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ActionList() + list1.putInteger(6) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) + + # Set + def step23(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 50) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Select + def step24(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putName(APP.instance.cID("Lyr "), "Background") + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putBoolean(APP.instance.cID("MkVs"), False) + list1 = ActionList() + list1.putInteger(1) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("slct"), desc1, dialog_mode) + + # Duplicate + def step25(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc1.putString(APP.instance.cID("Nm "), "top adjustment") + desc1.putInteger(APP.instance.cID("Vrsn"), 5) + list1 = ActionList() + list1.putInteger(35) + desc1.putList(APP.instance.cID("Idnt"), list1) + APP.instance.executeAction(APP.instance.cID("Dplc"), desc1, dialog_mode) + + # Move + def step26(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + ref2 = ActionReference() + ref2.putIndex(APP.instance.cID("Lyr "), 7) + desc1.putReference(APP.instance.cID("T "), ref2) + desc1.putBoolean(APP.instance.cID("Adjs"), False) + desc1.putInteger(APP.instance.cID("Vrsn"), 5) + list1 = ActionList() + list1.putInteger(35) + desc1.putList(APP.instance.cID("LyrI"), list1) + APP.instance.executeAction(APP.instance.cID("move"), desc1, dialog_mode) + + # Set + def step27(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putUnitDouble(APP.instance.cID("Opct"), APP.instance.cID("#Prc"), 40) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Set + def step28(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + desc1 = ActionDescriptor() + ref1 = ActionReference() + ref1.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("null"), ref1) + desc2 = ActionDescriptor() + desc2.putEnumerated( + APP.instance.cID("Md "), APP.instance.cID("BlnM"), APP.instance.cID("HrdL") + ) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, dialog_mode) + + # Merge Visible + def step29(enabled: bool = True, dialog: bool = False): + if not enabled: + return + dialog_mode = ( + DialogModes.DisplayAllDialogs if dialog else DialogModes.DisplayNoDialogs + ) + APP.instance.executeAction( + APP.instance.sID("mergeVisible"), ActionDescriptor(), dialog_mode + ) + + # Run each step + step1() # Duplicate + step2() # Invert + step3() # Gaussian Blur + step4() # Set + step5() # Make + step6() # Set + step7() # Make + step8() # Set + step9() # Copy Merged + step10() # Paste + step11() # Filter Gallery + step12() # Invert + blend_mode_multiply() # Set + step14() # Set + step15() # Select + step16() # Make + step17() # Select + step18() # Fill + step19() # Filter Gallery + blend_mode_multiply() # Set + step21() # Set + step22() # Select + step23() # Set + step24() # Select + step25() # Duplicate + step26() # Move + step27() # Set + step28() # Set + step29() # Merge diff --git a/src/__init__.py b/src/__init__.py index eb2d714d..e5120bb0 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -9,6 +9,8 @@ from dynaconf import Validator from omnitils.files import get_project_version +from src.utils.threading import ThreadInitializedInstance + # Local Imports from ._config import AppConfig from ._loader import get_all_plugins, get_all_templates, get_template_map, get_template_map_defaults @@ -50,7 +52,7 @@ def _get_proj_version(path: Path) -> str: CFG = AppConfig(env=ENV) # Global Photoshop handler -APP = PhotoshopHandler(env=ENV) +APP = ThreadInitializedInstance(lambda: PhotoshopHandler(env=ENV)) # Conditionally import the GUI console if not ENV.HEADLESS: diff --git a/src/commands/test/utility.py b/src/commands/test/utility.py index ca8a818b..574a7ff9 100644 --- a/src/commands/test/utility.py +++ b/src/commands/test/utility.py @@ -2,6 +2,7 @@ * General Testing Utility * For contributors and plugin development. """ + # Standard Library Imports from contextlib import suppress from _ctypes import COMError @@ -19,7 +20,8 @@ ActionReference, ElementPlacement, DialogModes, - LayerKind) + LayerKind, +) from psd_tools.constants import Resource from psd_tools import PSDImage from psd_tools.psd.image_resources import ImageResource @@ -32,7 +34,6 @@ from src.utils.adobe import LayerContainer # Photoshop infrastructure -cID, sID = APP.charIDToTypeID, APP.stringIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs # Reference Box colors @@ -47,7 +48,9 @@ """ -def test_new_color(new: str, old: str | None = None, ignore: list[str] | None = None) -> None: +def test_new_color( + new: str, old: str | None = None, ignore: list[str] | None = None +) -> None: """Enables given color in all necessary groups. Optionally disable a color in those groups. Args: @@ -57,7 +60,13 @@ def test_new_color(new: str, old: str | None = None, ignore: list[str] | None = """ if ignore is None: ignore = ["Pinlines & Textbox"] - groups = ["Name & Title Boxes", "Legendary Crown", "Pinlines & Textbox", "Background", "PT Box"] + groups = [ + "Name & Title Boxes", + "Legendary Crown", + "Pinlines & Textbox", + "Background", + "PT Box", + ] for r in ignore: groups.remove(r) for g in groups: @@ -71,7 +80,7 @@ def test_new_color(new: str, old: str | None = None, ignore: list[str] | None = def make_duals( name: str = "Pinlines & Textbox", mask_top: ArtLayer | None = None, - mask_bottom: ArtLayer | None = None + mask_bottom: ArtLayer | None = None, ): """Creates dual color layers for a given group. @@ -86,10 +95,11 @@ def make_duals( # Loop through each dual for dual in duals: - # Change layer visibility top = psd.getLayer(dual[0], group).duplicate(ref, ElementPlacement.PlaceBefore) - bottom = psd.getLayer(dual[1], group).duplicate(top, ElementPlacement.PlaceAfter) + bottom = psd.getLayer(dual[1], group).duplicate( + top, ElementPlacement.PlaceAfter + ) top.visible = True bottom.visible = True @@ -107,7 +117,7 @@ def make_duals( def create_blended_layer( colors: str | list[str], group: LayerSet, - masks: None | ArtLayer | list[ArtLayer] = None + masks: None | ArtLayer | list[ArtLayer] = None, ): """Create a multicolor layer using a gradient mask. @@ -153,8 +163,8 @@ def check_if_needed(key, keys_stored): Returns: True if needed, False if skipped. """ - if 'double' in keys_stored: - if key in ['largeInt', 'int']: + if "double" in keys_stored: + if key in ["largeInt", "int"]: return False return True @@ -171,20 +181,20 @@ def try_all_getters(desc: ActionDescriptor, type_id) -> dict: """ values = {} getters = { - 'bool': 'getBoolean', - 'class': 'getClass', - 'enumType': 'getEnumerationType', - 'enumVal': 'getEnumerationValue', - 'list': 'getList', - 'objType': 'getObjectType', - 'objValue': 'getObjectValue', - 'path': 'getPath', - 'ref': 'getReference', - 'str': 'getString', - 'type': 'getType', - 'double': 'getUnitDoubleValue', - 'int': 'getInteger', - 'largeInt': 'getLargeInteger', + "bool": "getBoolean", + "class": "getClass", + "enumType": "getEnumerationType", + "enumVal": "getEnumerationValue", + "list": "getList", + "objType": "getObjectType", + "objValue": "getObjectValue", + "path": "getPath", + "ref": "getReference", + "str": "getString", + "type": "getType", + "double": "getUnitDoubleValue", + "int": "getInteger", + "largeInt": "getLargeInteger", } for k, func in getters.items(): if not check_if_needed(k, values.keys()): @@ -193,7 +203,7 @@ def try_all_getters(desc: ActionDescriptor, type_id) -> dict: try: # Send the getter result = getattr(desc, func)(type_id) - if k == 'list': + if k == "list": # Getter may have returned an ActionList, grab the first object result = get_action_items(result.getObjectValue(0)) values[k] = result @@ -219,7 +229,7 @@ def get_action_items(desc) -> dict: count = 0 for i in range(count): type_id: int = desc.getKey(i) - string_id: str = APP.typeIDToStringID(type_id) + string_id: str = APP.instance.typeIDToStringID(type_id) if desc.hasKey(type_id): try: result = get_action_items(desc.getObjectValue(type_id)) @@ -247,8 +257,8 @@ def dump_layer_action_descriptors(layer: ArtLayer, path: str) -> dict: """ # Get the layer descriptor reference = ActionReference() - reference.putIdentifier(sID('layer'), layer.id) - descriptor = APP.executeActionGet(reference) + reference.putIdentifier(APP.instance.sID("layer"), layer.id) + descriptor = APP.instance.executeActionGet(reference) # Generate a dict of all descriptors actions = get_action_items(descriptor) @@ -297,13 +307,13 @@ def apply_single_line_composer(layer: ArtLayer) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - ref1.putProperty(sID("property"), sID("paragraphStyle")) - ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putInteger(sID("textOverrideFeatureName"), 808464691) - desc2.putBoolean(sID("textEveryLineComposer"), False) - desc1.putObject(sID("to"), sID("paragraphStyle"), desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + ref1.putProperty(APP.instance.sID("property"), APP.instance.sID("paragraphStyle")) + ref1.putIdentifier(APP.instance.sID("textLayer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putInteger(APP.instance.sID("textOverrideFeatureName"), 808464691) + desc2.putBoolean(APP.instance.sID("textEveryLineComposer"), False) + desc1.putObject(APP.instance.sID("to"), APP.instance.sID("paragraphStyle"), desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str | None = " "): @@ -320,23 +330,35 @@ def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str | None to_tk = psd.get_text_key(to_layer) # Grab the style range of each layer and establish our offset - from_range = from_tk.getList(sID("textStyleRange")) - to_range = to_tk.getList(sID("textStyleRange")) + from_range = from_tk.getList(APP.instance.sID("textStyleRange")) + to_range = to_tk.getList(APP.instance.sID("textStyleRange")) offset = len(sep) if sep else 0 # For each item in the "from" style range, update the position and apply style to target for i in range(from_range.count): from_style = from_range.getObjectValue(i) - id_from = from_style.getInteger(sID("from")) - id_to = from_style.getInteger(sID("to")) - from_style.putInteger(sID("from"), id_from + len(to_tk.getString(sID("textKey"))) + (offset if i != 0 else 0)) - from_style.putInteger(sID("to"), id_to + len(to_tk.getString(sID("textKey"))) + offset) - to_range.putObject(sID("textStyleRange"), from_style) + id_from = from_style.getInteger(APP.instance.sID("from")) + id_to = from_style.getInteger(APP.instance.sID("to")) + from_style.putInteger( + APP.instance.sID("from"), + id_from + + len(to_tk.getString(APP.instance.sID("textKey"))) + + (offset if i != 0 else 0), + ) + from_style.putInteger( + APP.instance.sID("to"), + id_to + len(to_tk.getString(APP.instance.sID("textKey"))) + offset, + ) + to_range.putObject(APP.instance.sID("textStyleRange"), from_style) # Combine the contents and apply the updated style range - contents = str(to_tk.getString(sID("textKey")) + sep + from_tk.getString(sID("textKey"))) - to_tk.putString(sID("textKey"), contents) - to_tk.putList(sID("textStyleRange"), to_range) + contents = str( + to_tk.getString(APP.instance.sID("textKey")) + + sep + + from_tk.getString(APP.instance.sID("textKey")) + ) + to_tk.putString(APP.instance.sID("textKey"), contents) + to_tk.putList(APP.instance.sID("textStyleRange"), to_range) # Apply the updated text key to the target layer psd.apply_text_key(to_layer, to_tk) @@ -349,23 +371,32 @@ def reset_transform_factor(layer: ArtLayer) -> None: layer: TextLayer to reset transform factors for. """ key = psd.get_text_key(layer) - if key.hasKey(sID('transform')): - + if key.hasKey(APP.instance.sID("transform")): # Check the scale factor - desc = key.getObjectValue(sID("transform")) - xx = desc.getUnitDoubleValue(sID("xx")) if desc.hasKey(sID("xx")) else 1 - yy = desc.getUnitDoubleValue(sID("yy")) if desc.hasKey(sID("xx")) else 1 + desc = key.getObjectValue(APP.instance.sID("transform")) + xx = ( + desc.getUnitDoubleValue(APP.instance.sID("xx")) + if desc.hasKey(APP.instance.sID("xx")) + else 1 + ) + yy = ( + desc.getUnitDoubleValue(APP.instance.sID("yy")) + if desc.hasKey(APP.instance.sID("xx")) + else 1 + ) # Fix the scale factor if xx == 1 and yy == 1: return if xx != 1: - desc.putDouble(sID("xx"), 1) + desc.putDouble(APP.instance.sID("xx"), 1) if yy != 1: - desc.putDouble(sID("yy"), 1) + desc.putDouble(APP.instance.sID("yy"), 1) # Update the scale factor - key.putObject(sID("transform"), sID("transform"), desc) + key.putObject( + APP.instance.sID("transform"), APP.instance.sID("transform"), desc + ) psd.apply_text_key(layer, key) @@ -378,19 +409,19 @@ def xmp_remove_ancestors() -> None: """Remove DocumentAncestors property from XMP data.""" # Check that a document is open - if not APP.documents: - print('No documents open!') + if not APP.instance.documents: + print("No documents open!") return # XMP data - APP.eval_javascript(''' + APP.instance.eval_javascript(""" if (ExternalObject.AdobeXMPScript == undefined) { ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript"); } var xmp = new XMPMeta( activeDocument.xmpMetadata.rawData); xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors"); - APP.activeDocument.xmpMetadata.rawData = xmp.serialize(); - ''') + APP.instance.activeDocument.xmpMetadata.rawData = xmp.serialize(); + """) """ @@ -401,7 +432,7 @@ def xmp_remove_ancestors() -> None: def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: layer_name = layer.name color = psd.get_color(color) - docref = APP.activeDocument + docref = APP.instance.activeDocument docsel = docref.selection docref.activeLayer = layer psd.select_layer_bounds(layer, docsel) @@ -409,34 +440,38 @@ def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: desc1 = ActionDescriptor() ref1 = ActionReference() ref2 = ActionReference() - ref1.putClass(sID("path")) - desc1.putReference(sID("target"), ref1) - ref2.putProperty(sID("selectionClass"), sID("selection")) - desc1.putReference(sID("from"), ref2) - desc1.putUnitDouble(sID("tolerance"), sID("pixelsUnit"), 2.000000) - APP.executeAction(sID("make"), desc1, NO_DIALOG) + ref1.putClass(APP.instance.sID("path")) + desc1.putReference(APP.instance.sID("target"), ref1) + ref2.putProperty(APP.instance.sID("selectionClass"), APP.instance.sID("selection")) + desc1.putReference(APP.instance.sID("from"), ref2) + desc1.putUnitDouble( + APP.instance.sID("tolerance"), APP.instance.sID("pixelsUnit"), 2.000000 + ) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) ref1 = ActionReference() desc1 = ActionDescriptor() desc2 = ActionDescriptor() desc3 = ActionDescriptor() desc4 = ActionDescriptor() - ref1.putClass(sID("contentLayer")) - desc1.putReference(sID("target"), ref1) - desc4.putDouble(sID("red"), color.rgb.red) - desc4.putDouble(sID("green"), color.rgb.green) - desc4.putDouble(sID("blue"), color.rgb.blue) - desc3.putObject(sID("color"), sID("RGBColor"), desc4) - desc2.putObject(sID("type"), sID("solidColorLayer"), desc3) - desc1.putObject(sID("using"), sID("contentLayer"), desc2) - APP.executeAction(sID("make"), desc1, NO_DIALOG) - APP.activeDocument.activeLayer.name = layer_name + ref1.putClass(APP.instance.sID("contentLayer")) + desc1.putReference(APP.instance.sID("target"), ref1) + desc4.putDouble(APP.instance.sID("red"), color.rgb.red) + desc4.putDouble(APP.instance.sID("green"), color.rgb.green) + desc4.putDouble(APP.instance.sID("blue"), color.rgb.blue) + desc3.putObject(APP.instance.sID("color"), APP.instance.sID("RGBColor"), desc4) + desc2.putObject( + APP.instance.sID("type"), APP.instance.sID("solidColorLayer"), desc3 + ) + desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.activeDocument.activeLayer.name = layer_name # Check dims dims = psd.get_layer_dimensions(layer) - dims = (dims['width'], dims['height']) + dims = (dims["width"], dims["height"]) new_dims = psd.get_layer_dimensions(docref.activeLayer) - new_dims = (new_dims['width'], new_dims['height']) + new_dims = (new_dims["width"], new_dims["height"]) if not dims == new_dims: print("DIMS CHANGED:", layer_name) print("Before:", dims) @@ -456,8 +491,8 @@ def log_all_template_fonts() -> dict: """Create a log of every font found for each PSD template.""" # Ignore warnings from the psd_tools module - logging.getLogger('psd_tools').setLevel(logging.FATAL) - warnings.filterwarnings("ignore", module='psd_tools') + logging.getLogger("psd_tools").setLevel(logging.FATAL) + warnings.filterwarnings("ignore", module="psd_tools") def _get_fonts_from_psd(doc_path: str) -> set[str]: """ @@ -466,10 +501,10 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: @return: Set of font names found in the document. """ file, fonts = PSDImage.open(doc_path), set() - for layer in [n for n in file.descendants() if n.kind == 'type']: - for style in layer.engine_dict['StyleRun']['RunArray']: - font_key = style['StyleSheet']['StyleSheetData']['Font'] - fonts.add(layer.resource_dict['FontSet'][font_key]['Name']) + for layer in [n for n in file.descendants() if n.kind == "type"]: + for style in layer.engine_dict["StyleRun"]["RunArray"]: + font_key = style["StyleSheet"]["StyleSheetData"]["Font"] + fonts.add(layer.resource_dict["FontSet"][font_key]["Name"]) return fonts # PSD documents to test @@ -484,7 +519,6 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: # Check each document logging.basicConfig(level=logging.INFO) for f, temp_name in docs.items(): - # Alert the user logging.info(f"READING FONTS — {temp_name} [{f.name}] [{current}/{total}]") current += 1 @@ -498,8 +532,10 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: master.setdefault(str(f), []).append(doc) # Log a sorted master list - master: dict[str, list] = {k: v for k, v in sorted(master.items(), key=lambda item: len(item[1]))} - with open('logs/FONTS.json', 'w', encoding='utf-8') as f: + master: dict[str, list] = { + k: v for k, v in sorted(master.items(), key=lambda item: len(item[1])) + } + with open("logs/FONTS.json", "w", encoding="utf-8") as f: json.dump(master, f, indent=2) return master @@ -511,7 +547,9 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: """ -def insert_data_set_variables(xml_data: str, from_path: str, to_path: str | None = None) -> None: +def insert_data_set_variables( + xml_data: str, from_path: str, to_path: str | None = None +) -> None: """Inserts data set variable XML into a PSD document. Args: @@ -523,15 +561,19 @@ def insert_data_set_variables(xml_data: str, from_path: str, to_path: str | None to_path = to_path or from_path # Ignore warnings from the psd_tools module - logging.getLogger('psd_tools').setLevel(logging.FATAL) - warnings.filterwarnings("ignore", module='psd_tools') + logging.getLogger("psd_tools").setLevel(logging.FATAL) + warnings.filterwarnings("ignore", module="psd_tools") # Open the PSD file f = PSDImage.open(from_path) # Replace the image variables data - new_resource = ImageResource(b'MeSa', key=Resource.IMAGE_READY_VARIABLES, name='', data=xml_data.encode( - encoding='UTF-8', errors='strict')) + new_resource = ImageResource( + b"MeSa", + key=Resource.IMAGE_READY_VARIABLES, + name="", + data=xml_data.encode(encoding="UTF-8", errors="strict"), + ) f.image_resources[Resource.IMAGE_READY_VARIABLES] = new_resource # Save the PSD file @@ -544,12 +586,11 @@ def print_data_set_variables(path: str) -> None: Args: path: Path to a PSD document. """ - data = PSDImage.open(path).image_resources.get_data( - Resource.IMAGE_READY_DATA_SETS) + data = PSDImage.open(path).image_resources.get_data(Resource.IMAGE_READY_DATA_SETS) pretty_xml = minidom.parseString(data).toprettyxml() # Print without excess newlines - print('\n'.join([line for line in pretty_xml.split('\n') if line.strip()])) + print("\n".join([line for line in pretty_xml.split("\n") if line.strip()])) def format_data_set_variable_name(text: str) -> str: @@ -561,15 +602,11 @@ def format_data_set_variable_name(text: str) -> str: Returns: Properly formatted data set variable name. """ - return text.title().replace( - ' ', '').replace( - '-', '').replace( - '&', '') + return text.title().replace(" ", "").replace("-", "").replace("&", "") def get_data_set_variables( - group: LayerContainer | None = None, - tree: str | None = None + group: LayerContainer | None = None, tree: str | None = None ) -> list[dict[str, str]]: """Get data set variables for all ArtLayer and LayerSet objects in document or LayerSet. @@ -582,46 +619,57 @@ def get_data_set_variables( """ # Establish current tree - tree = f'{tree}{format_data_set_variable_name(group.name)}.' if tree and group else ( - f'{format_data_set_variable_name(group.name)}.' if group else '') + tree = ( + f"{tree}{format_data_set_variable_name(group.name)}." + if tree and group + else (f"{format_data_set_variable_name(group.name)}." if group else "") + ) # Establish group or top level document container - group = group or APP.activeDocument + group = group or APP.instance.activeDocument layer_vars: list[dict[str, str]] = [] # Add layer variables for n in group.artLayers: with suppress(Exception): if n.kind == LayerKind.TextLayer: - layer_vars.append({ - 'varName': f'{tree}{format_data_set_variable_name(n.name)}.Text', - 'trait': 'textcontent', - 'docRef': f"id('{n.id}')" - }) - elif 'Frame' in n.name: - layer_vars.append({ - 'varName': f'{tree}{format_data_set_variable_name(n.name)}.Image', - 'trait': 'fileref', - 'placementMethod': 'fill', - 'align': 'center', - 'valign': 'middle', - 'clip': 'false', - 'docRef': f"id('{n.id}')" - }) - layer_vars.append({ - 'varName': f'{tree}{format_data_set_variable_name(n.name)}.Visible', - 'trait': 'visibility', - 'docRef': f"id('{n.id}')" - }) + layer_vars.append( + { + "varName": f"{tree}{format_data_set_variable_name(n.name)}.Text", + "trait": "textcontent", + "docRef": f"id('{n.id}')", + } + ) + elif "Frame" in n.name: + layer_vars.append( + { + "varName": f"{tree}{format_data_set_variable_name(n.name)}.Image", + "trait": "fileref", + "placementMethod": "fill", + "align": "center", + "valign": "middle", + "clip": "false", + "docRef": f"id('{n.id}')", + } + ) + layer_vars.append( + { + "varName": f"{tree}{format_data_set_variable_name(n.name)}.Visible", + "trait": "visibility", + "docRef": f"id('{n.id}')", + } + ) # Add layer group variables for n in group.layerSets: with suppress(Exception): - layer_vars.append({ - 'varName': f'{tree}{format_data_set_variable_name(n.name)}.Visible', - 'trait': 'visibility', - 'docRef': f"id('{n.id}')" - }) + layer_vars.append( + { + "varName": f"{tree}{format_data_set_variable_name(n.name)}.Visible", + "trait": "visibility", + "docRef": f"id('{n.id}')", + } + ) layer_vars.extend(get_data_set_variables(n, tree)) return layer_vars @@ -630,23 +678,27 @@ def get_data_set_variable_xml(): """Generate data set variable XML for all layers in document.""" # Create the root element - root = ET.Element('variableSets', xmlns="https://ns.adobe.com/Variables/1.0/") + root = ET.Element("variableSets", xmlns="https://ns.adobe.com/Variables/1.0/") # Create variableSet -> variables - variableSet = ET.SubElement(root, 'variableSet') - variableSet.set('locked', 'none') - variableSet.set('varSetName', 'binding1') - variables = ET.SubElement(variableSet, 'variables') + variableSet = ET.SubElement(root, "variableSet") + variableSet.set("locked", "none") + variableSet.set("varSetName", "binding1") + variables = ET.SubElement(variableSet, "variables") # Add your variables, could do this programmatically for each layer for var in get_data_set_variables(): - variable = ET.SubElement(variables, 'variable') + variable = ET.SubElement(variables, "variable") for k, v in var.items(): variable.set(k, v) # Convert the XML to a string - return (ET.tostring(root, encoding='utf8', method='xml', short_empty_elements=False) - .decode().replace('\n', '', 1).replace('><', '>\n<')) + '\n' + return ( + ET.tostring(root, encoding="utf8", method="xml", short_empty_elements=False) + .decode() + .replace("\n", "", 1) + .replace("><", ">\n<") + ) + "\n" def create_data_set_csv(data_set: dict[str, str], path: str) -> None: @@ -656,7 +708,7 @@ def create_data_set_csv(data_set: dict[str, str], path: str) -> None: data_set: A dictionary where variable names are the keys and variable values are the values. path: Path to save the CSV to. """ - with open(path, 'w', newline='') as file: + with open(path, "w", newline="") as file: writer = csv.writer(file) writer.writerow(data_set.keys()) writer.writerow(data_set.values()) @@ -670,13 +722,17 @@ def import_data_set(path: str) -> None: """ desc = ActionDescriptor() ref = ActionReference() - ref.putClass(sID("dataSetClass")) - desc.putReference(sID("null"), ref) - desc.putPath(sID("using"), path) - desc.putEnumerated(sID("encoding"), sID("dataSetEncoding"), sID("dataSetEncodingAuto")) - desc.putBoolean(sID("eraseAll"), True) - desc.putBoolean(sID("useFirstColumn"), True) - APP.executeAction(sID("importDataSets"), desc, NO_DIALOG) + ref.putClass(APP.instance.sID("dataSetClass")) + desc.putReference(APP.instance.sID("null"), ref) + desc.putPath(APP.instance.sID("using"), path) + desc.putEnumerated( + APP.instance.sID("encoding"), + APP.instance.sID("dataSetEncoding"), + APP.instance.sID("dataSetEncodingAuto"), + ) + desc.putBoolean(APP.instance.sID("eraseAll"), True) + desc.putBoolean(APP.instance.sID("useFirstColumn"), True) + APP.instance.executeAction(APP.instance.sID("importDataSets"), desc, NO_DIALOG) def apply_data_set(data_set_name: str) -> None: @@ -687,15 +743,15 @@ def apply_data_set(data_set_name: str) -> None: """ desc = ActionDescriptor() setRef = ActionReference() - setRef.putName(sID("dataSetClass"), data_set_name) - desc.putReference(sID("null"), setRef) - APP.executeAction(sID("apply"), desc, NO_DIALOG) + setRef.putName(APP.instance.sID("dataSetClass"), data_set_name) + desc.putReference(APP.instance.sID("null"), setRef) + APP.instance.executeAction(APP.instance.sID("apply"), desc, NO_DIALOG) """ * Testing Stuff """ -if __name__ == '__main__': +if __name__ == "__main__": """Insert any test actions here.""" pass diff --git a/src/console.py b/src/console.py index 3e17e506..79da00fc 100644 --- a/src/console.py +++ b/src/console.py @@ -20,6 +20,7 @@ # Local Imports from src._config import AppConfig from src._state import AppEnvironment, PATH +from src.utils.threading import ThreadInitializedInstance from src.utils.windows import WindowState if TYPE_CHECKING: @@ -165,7 +166,7 @@ def __init__( self, cfg: AppConfig, env: AppEnvironment, - app: PhotoshopHandler, + app: ThreadInitializedInstance[PhotoshopHandler], ): # Establish global objects @@ -354,7 +355,7 @@ def await_choice(self, thr: Event |None, msg: str | None = None, end: str = "\n" self.update(msg=msg or self.message_waiting, end=end) if self.cfg.minimize_photoshop and show_photoshop: # Show Photoshop in case it is minimized - self.app.set_window_state(WindowState.SHOWDEFAULT) + self.app.instance.set_window_state(WindowState.SHOWDEFAULT) response = input("[Y / Enter] Continue — [N] Cancel") # Signal the choice @@ -366,7 +367,7 @@ def await_choice(self, thr: Event |None, msg: str | None = None, end: str = "\n" self.cancel_thread(thr) if not choice else self.start_await_cancel(thr) if choice and self.cfg.minimize_photoshop: - self.app.set_window_state(WindowState.MINIMIZE) + self.app.instance.set_window_state(WindowState.MINIMIZE) return choice diff --git a/src/enums/adobe.py b/src/enums/adobe.py index 6242bf5b..10d63e38 100644 --- a/src/enums/adobe.py +++ b/src/enums/adobe.py @@ -23,7 +23,7 @@ def __call__(self) -> int: @property def value(self) -> int: - return int(APP.stringIDToTypeID(self._value_)) + return int(APP.instance.sID(self._value_)) class Dimensions(StrConstant): diff --git a/src/gui/app.py b/src/gui/app.py index 573c35a6..d99afa81 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -64,6 +64,7 @@ from src.utils.adobe import get_photoshop_error_message, PhotoshopHandler, PS_EXCEPTIONS from src.utils.hexapi import update_hexproof_cache, get_api_key from src.utils.fonts import check_app_fonts +from src.utils.threading import ThreadInitializedInstance """ @@ -74,9 +75,13 @@ class AppContainer(BoxLayout, GlobalAccess): """Container for overall app.""" + @cached_property + def app_tabs(self) -> "AppTabs": + return AppTabs() + def on_load(self, *args) -> None: """Add tab panel.""" - self.add_widget(AppTabs()) + self.add_widget(self.app_tabs) class AppTabs(TabbedPanel, GlobalAccess): @@ -86,10 +91,14 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self._tab_layout.padding = ( '0dp', '0dp', '0dp', '0dp') + + @cached_property + def main_tab(self) -> "MainTab": + return MainTab() def on_load(self, *args) -> None: """Add tabs.""" - self.add_widget(MainTab()) + self.add_widget(self.main_tab) self.add_widget(CreatorTab()) self.add_widget(ToolsTab()) @@ -97,9 +106,13 @@ def on_load(self, *args) -> None: class MainTab(TabbedPanelItem, GlobalAccess): """Main rendering tab.""" + @cached_property + def main_panel(self) -> MainPanel: + return MainPanel() + def on_load(self, *args) -> None: """Add content.""" - self.content = MainPanel() + self.content = self.main_panel class CreatorTab(TabbedPanelItem, GlobalAccess): @@ -129,7 +142,7 @@ class ProxyshopGUIApp(App): def __init__( self, - app: PhotoshopHandler, + app: ThreadInitializedInstance[PhotoshopHandler], con: AppConstants, cfg: AppConfig, env: AppEnvironment, @@ -183,7 +196,7 @@ def cont_padding(self) -> float: @property def app(self) -> PhotoshopHandler: """PhotoshopHandler: Global Photoshop application object.""" - return self._app + return self._app.instance @property def cfg(self) -> AppConfig: @@ -278,6 +291,18 @@ def thread_cancelled(self) -> bool: * UI Properties """ + @cached_property + def render_target_button(self) -> Button: + return self._app_layout.app_tabs.main_tab.main_panel.render_target_button + + @cached_property + def render_all_button(self) -> Button: + return self._app_layout.app_tabs.main_tab.main_panel.render_all_button + + @cached_property + def app_settings_button(self) -> Button: + return self._app_layout.app_tabs.main_tab.main_panel.app_settings_button + @cached_property def toggle_buttons(self) -> list[Button]: """list[Button]: UI buttons to toggle when disable_buttons or enable_buttons is called.""" @@ -927,66 +952,120 @@ def render_dropped_files(self, _window, _x, _y) -> None: def on_start(self) -> None: """Fired after build is fired. Run a diagnostic check to see what works.""" - self.console.update(msg_success("--- STATUS ---")) - - # Check if using latest version - self.console.update( - f"Proxyshop Version ... {msg_success('Using latest version!')}" if ( - self.check_app_version() - ) else f"Proxyshop Version ... {msg_info('New release available!')}" - ) - - # Update set data if needed - check, error = update_hexproof_cache() - if check: - self.con.reload() - message = msg_error(error) if error else msg_success( - 'Update was applied!' if check else 'Using latest data!') - self.console.update(f"Hexproof API Data ... {message}") - - # Check if API keys are valid - if not self.env.API_GOOGLE: - self.env.API_GOOGLE = get_api_key('proxyshop.google.drive') - if not self.env.API_AMAZON: - self.env.API_AMAZON = get_api_key('proxyshop.amazon.s3') - keys_missing = [k for k, v in [ - ('Google Drive', self.env.API_GOOGLE), - ('Amazon S3', self.env.API_AMAZON) - ] if not v] - message = msg_warn( - f"Keys disabled: {', '.join(keys_missing)}" - ) if keys_missing else msg_success('Keys retrieved!') - self.console.update(f"Updater API Keys ... {message}") - - # Check Photoshop status - result = self.app.refresh_app() - if isinstance(result, OSError): - # Photoshop test failed - self.console.log_exception(result) - self.console.update(f"Photoshop ... {msg_error('Cannot make connection with Photoshop!')}\n" - f"Check [b]logs/error.txt[/b] for more details.") - self.console.update(f"Fonts ... {msg_warn('Cannot test fonts without Photoshop.')}") - return - # Photoshop test passed - self.console.update(f"Photoshop ... {msg_success('Connection established!')}") - - # Check for missing or outdated fonts - missing, outdated = check_app_fonts(self.app, [PATH.FONTS]) + self.disable_buttons() + self.app_settings_button.disabled = False - # Font test passed - if not missing and not outdated: - self.console.update(f"Fonts ... {msg_success('All essential fonts installed!')}") - return + self.console.update("Running startup checks...") - # Missing fonts - self.console.update(f"Fonts ... {msg_warn('Missing or outdated fonts:')}", end='') - if missing: - self.console.update( - get_bullet_points([f"{f['name']} — {msg_warn('Not Installed')}" for f in missing.values()]), end="") - if outdated: + def photoshop_checks(app: PhotoshopHandler) -> None: + try: + # Check Photoshop connection + result = app.refresh_app() + if isinstance(result, OSError): + # Photoshop test failed + self.console.log_exception(result) + self.console.update( + f"Photoshop ... {msg_error('Cannot make connection with Photoshop!')}\n" + f"Check [b]logs/error.txt[/b] for more details." + ) + self.console.update( + f"Fonts ... {msg_warn('Cannot test fonts without Photoshop.')}" + ) + return + # Photoshop test passed + self.console.update( + f"Photoshop ... {msg_success('Connection established!')}" + ) + + # Check for missing or outdated fonts + missing, outdated = check_app_fonts(app, [PATH.FONTS]) + + # Font test passed + if not missing and not outdated: + self.console.update( + f"Fonts ... {msg_success('All essential fonts installed!')}" + ) + return + + # Missing fonts + self.console.update( + f"Fonts ... {msg_warn('Missing or outdated fonts:')}", end="" + ) + if missing: + self.console.update( + get_bullet_points( + [ + f"{f['name']} — {msg_warn('Not Installed')}" + for f in missing.values() + ] + ), + end="", + ) + if outdated: + self.console.update( + get_bullet_points( + [ + f"{f['name']} — {msg_info('New Version')}" + for f in outdated.values() + ] + ), + end="", + ) + finally: + self.render_target_button.disabled = False + self.render_all_button.disabled = False + + if self._app.ready: + photoshop_checks(self.app) + else: + self._app.add_listener(photoshop_checks) + + def check_latest_version() -> None: + # Check if using latest version self.console.update( - get_bullet_points([f"{f['name']} — {msg_info('New Version')}" for f in outdated.values()]), end="") - self.console.update() + f"Proxyshop Version ... {msg_success('Using latest version!')}" + if (self.check_app_version()) + else f"Proxyshop Version ... {msg_info('New release available!')}" + ) + + def update_set_data() -> None: + # Update set data if needed + check, error = update_hexproof_cache() + if check: + self.con.reload() + message = ( + msg_error(error) + if error + else msg_success( + "Update was applied!" if check else "Using latest data!" + ) + ) + self.console.update(f"Hexproof API Data ... {message}") + + def check_api_keys() -> None: + # Check if API keys are valid + if not self.env.API_GOOGLE: + self.env.API_GOOGLE = get_api_key("proxyshop.google.drive") + if not self.env.API_AMAZON: + self.env.API_AMAZON = get_api_key("proxyshop.amazon.s3") + keys_missing = [ + k + for k, v in [ + ("Google Drive", self.env.API_GOOGLE), + ("Amazon S3", self.env.API_AMAZON), + ] + if not v + ] + message = ( + msg_warn(f"Keys disabled: {', '.join(keys_missing)}") + if keys_missing + else msg_success("Keys retrieved!") + ) + self.console.update(f"Updater API Keys ... {message}") + self.console.update_btn.disabled = False + + for check in (check_latest_version, update_set_data, check_api_keys): + Thread(target=check).start() def add_console(self) -> None: """Adds the console to the app window. Label gets frozen if loaded beforehand.""" diff --git a/src/gui/console.py b/src/gui/console.py index 9743990c..ee6d145f 100644 --- a/src/gui/console.py +++ b/src/gui/console.py @@ -23,6 +23,7 @@ from src._state import AppEnvironment, PATH from src.gui._state import get_root_app from src.gui.utils import HoverButton +from src.utils.threading import ThreadInitializedInstance from src.utils.windows import WindowState if TYPE_CHECKING: @@ -41,7 +42,7 @@ def __init__( self, cfg: AppConfig, env: AppEnvironment, - app: PhotoshopHandler, + app: ThreadInitializedInstance[PhotoshopHandler], **kwargs ): # Establish global objects @@ -273,7 +274,7 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show self.enable_buttons() if self.cfg.minimize_photoshop and show_photoshop: # Show Photoshop in case it is minimized - self.app.set_window_state(WindowState.SHOWDEFAULT) + self.app.instance.set_window_state(WindowState.SHOWDEFAULT) self.start_await() # Cancel the current thread or continue based on user signal @@ -282,7 +283,7 @@ def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show # Minimize Photoshop if the setting for that is active if self.running and self.cfg.minimize_photoshop: - self.app.set_window_state(WindowState.MINIMIZE) + self.app.instance.set_window_state(WindowState.MINIMIZE) return self.running diff --git a/src/gui/tabs/main.py b/src/gui/tabs/main.py index fc438f36..5cc18170 100644 --- a/src/gui/tabs/main.py +++ b/src/gui/tabs/main.py @@ -33,13 +33,25 @@ class MainPanel(BoxLayout, GlobalAccess): """Main panel to the 'Render Cards' tab.""" Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "main.kv")) + @cached_property + def render_target_button(self) -> Button: + return self.ids.rend_targ_btn + + @cached_property + def render_all_button(self) -> Button: + return self.ids.rend_all_btn + + @cached_property + def app_settings_button(self) -> Button: + return self.ids.app_settings_btn + @cached_property def toggle_buttons(self) -> list[Button]: """Add render and settings buttons.""" return [ - self.ids.rend_targ_btn, - self.ids.rend_all_btn, - self.ids.app_settings_btn + self.render_target_button, + self.render_all_button, + self.app_settings_button, ] diff --git a/src/helpers/actions.py b/src/helpers/actions.py index 3ce63137..127f4adc 100644 --- a/src/helpers/actions.py +++ b/src/helpers/actions.py @@ -1,19 +1,14 @@ """ * Helpers: Actions """ + # Third Party Imports -from photoshop.api import ( - DialogModes, - ActionDescriptor, - ActionReference -) +from photoshop.api import DialogModes, ActionDescriptor, ActionReference # Local Imports from src import APP # QOL Definitions -sID = APP.stringIDToTypeID -cID = APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -30,9 +25,9 @@ def run_action(action_set: str, action: str) -> None: """ desc310 = ActionDescriptor() ref7 = ActionReference() - desc310.putBoolean(sID("dontRecord"), False) - desc310.putBoolean(sID("forceNotify"), True) - ref7.putName(sID("action"), action) - ref7.putName(sID("actionSet"), action_set) - desc310.putReference(sID("target"), ref7) - APP.executeAction(sID("play"), desc310, NO_DIALOG) + desc310.putBoolean(APP.instance.sID("dontRecord"), False) + desc310.putBoolean(APP.instance.sID("forceNotify"), True) + ref7.putName(APP.instance.sID("action"), action) + ref7.putName(APP.instance.sID("actionSet"), action_set) + desc310.putReference(APP.instance.sID("target"), ref7) + APP.instance.executeAction(APP.instance.sID("play"), desc310, NO_DIALOG) diff --git a/src/helpers/adjustments.py b/src/helpers/adjustments.py index b9d997e8..2993435f 100644 --- a/src/helpers/adjustments.py +++ b/src/helpers/adjustments.py @@ -1,11 +1,18 @@ """ * Helpers: Adjustment Layers """ + # Standard Library from typing import NotRequired, TypedDict, Unpack # Third Party -from photoshop.api import BlendMode, DialogModes, ActionList, ActionDescriptor, ActionReference +from photoshop.api import ( + BlendMode, + DialogModes, + ActionList, + ActionDescriptor, + ActionReference, +) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -16,7 +23,6 @@ from src.schema.colors import ColorObject, GradientConfig # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -33,9 +39,9 @@ def create_vibrant_saturation(vibrancy: int, saturation: int) -> None: """ # dialogMode (Have dialog popup?) desc232 = ActionDescriptor() - desc232.putInteger(sID("vibrance"), vibrancy) - desc232.putInteger(sID("saturation"), saturation) - APP.executeAction(sID("vibrance"), desc232, NO_DIALOG) + desc232.putInteger(APP.instance.sID("vibrance"), vibrancy) + desc232.putInteger(APP.instance.sID("saturation"), saturation) + APP.instance.executeAction(APP.instance.sID("vibrance"), desc232, NO_DIALOG) class CreateColorLayerKwargs(TypedDict): @@ -47,7 +53,7 @@ def create_color_layer( color: ColorObject, layer: ArtLayer | LayerSet | None, docref: Document | None = None, - **kwargs: Unpack[CreateColorLayerKwargs] + **kwargs: Unpack[CreateColorLayerKwargs], ) -> ArtLayer: """Create a solid color adjustment layer. @@ -63,30 +69,35 @@ def create_color_layer( Returns: The new solid color adjustment layer. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() desc3 = ActionDescriptor() - ref1.putClass(sID("contentLayer")) - desc1.putReference(sID("target"), ref1) - desc2.putBoolean(sID("group"), kwargs.get('clipped', True)) - desc2.putEnumerated(sID("color"), sID("color"), sID("blue")) + ref1.putClass(APP.instance.sID("contentLayer")) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putBoolean(APP.instance.sID("group"), kwargs.get("clipped", True)) + desc2.putEnumerated( + APP.instance.sID("color"), APP.instance.sID("color"), APP.instance.sID("blue") + ) apply_color(desc3, color) - desc2.putObject(sID("type"), sID("solidColorLayer"), desc3) - desc1.putObject(sID("using"), sID("contentLayer"), desc2) - APP.executeAction(sID("make"), desc1, NO_DIALOG) + desc2.putObject( + APP.instance.sID("type"), APP.instance.sID("solidColorLayer"), desc3 + ) + desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) layer = docref.activeLayer if not isinstance(layer, ArtLayer): raise ValueError( "Failed to create a color layer. Active layer is unexpectedly not an ArtLayer." ) - if 'blend_mode' in kwargs: - layer.blendMode = kwargs['blend_mode'] + if "blend_mode" in kwargs: + layer.blendMode = kwargs["blend_mode"] return layer + class CreateGradientLayerKwargs(TypedDict): blend_mode: NotRequired[BlendMode] clipped: NotRequired[bool] @@ -99,7 +110,7 @@ def create_gradient_layer( colors: list[GradientConfig], layer: ArtLayer | LayerSet | None, docref: Document | None = None, - **kwargs: Unpack[CreateGradientLayerKwargs] + **kwargs: Unpack[CreateGradientLayerKwargs], ) -> ArtLayer: """Create a gradient adjustment layer. @@ -118,7 +129,7 @@ def create_gradient_layer( Returns: The new gradient adjustment layer. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer desc1 = ActionDescriptor() @@ -130,44 +141,66 @@ def create_gradient_layer( list2 = ActionList() desc9 = ActionDescriptor() desc10 = ActionDescriptor() - ref1.putClass(sID("contentLayer")) - desc1.putReference(sID("target"), ref1) - desc2.putBoolean(sID("group"), kwargs.get('clipped', True)) + ref1.putClass(APP.instance.sID("contentLayer")) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putBoolean(APP.instance.sID("group"), kwargs.get("clipped", True)) + desc3.putEnumerated( + APP.instance.sID("gradientsInterpolationMethod"), + APP.instance.sID("gradientInterpolationMethodType"), + APP.instance.sID("perceptual"), + ) + desc3.putBoolean(APP.instance.sID("dither"), kwargs.get("dither", True)) + desc3.putUnitDouble( + APP.instance.sID("angle"), + APP.instance.sID("angleUnit"), + kwargs.get("rotation", 0), + ) desc3.putEnumerated( - sID("gradientsInterpolationMethod"), - sID("gradientInterpolationMethodType"), - sID("perceptual") + APP.instance.sID("type"), + APP.instance.sID("gradientType"), + APP.instance.sID("linear"), + ) + desc3.putUnitDouble( + APP.instance.sID("scale"), + APP.instance.sID("percentUnit"), + kwargs.get("scale", 100), + ) + desc4.putEnumerated( + APP.instance.sID("gradientForm"), + APP.instance.sID("gradientForm"), + APP.instance.sID("customStops"), ) - desc3.putBoolean(sID("dither"), kwargs.get("dither", True)) - desc3.putUnitDouble(sID("angle"), sID("angleUnit"), kwargs.get('rotation', 0)) - desc3.putEnumerated(sID("type"), sID("gradientType"), sID("linear")) - desc3.putUnitDouble(sID("scale"), sID("percentUnit"), kwargs.get('scale', 100)) - desc4.putEnumerated(sID("gradientForm"), sID("gradientForm"), sID("customStops")) - desc4.putDouble(sID("interfaceIconFrameDimmed"), 4096) + desc4.putDouble(APP.instance.sID("interfaceIconFrameDimmed"), 4096) for c in colors: add_color_to_gradient( color_list, - get_color(c.get('color', rgb_black())), - int(c.get('location', 0)), - int(c.get('midpoint', 50)) + get_color(c.get("color", rgb_black())), + int(c.get("location", 0)), + int(c.get("midpoint", 50)), ) - desc4.putList(sID("colors"), color_list) - desc9.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - desc9.putInteger(sID("location"), 0) - desc9.putInteger(sID("midpoint"), 50) - list2.putObject(sID("transferSpec"), desc9) - desc10.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - desc10.putInteger(sID("location"), 4096) - desc10.putInteger(sID("midpoint"), 50) - list2.putObject(sID("transferSpec"), desc10) - desc4.putList(sID("transparency"), list2) - desc3.putObject(sID("gradient"), sID("gradientClassEvent"), desc4) - desc2.putObject(sID("type"), sID("gradientLayer"), desc3) - desc1.putObject(sID("using"), sID("contentLayer"), desc2) - APP.executeAction(sID("make"), desc1, NO_DIALOG) + desc4.putList(APP.instance.sID("colors"), color_list) + desc9.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), 100 + ) + desc9.putInteger(APP.instance.sID("location"), 0) + desc9.putInteger(APP.instance.sID("midpoint"), 50) + list2.putObject(APP.instance.sID("transferSpec"), desc9) + desc10.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), 100 + ) + desc10.putInteger(APP.instance.sID("location"), 4096) + desc10.putInteger(APP.instance.sID("midpoint"), 50) + list2.putObject(APP.instance.sID("transferSpec"), desc10) + desc4.putList(APP.instance.sID("transparency"), list2) + desc3.putObject( + APP.instance.sID("gradient"), APP.instance.sID("gradientClassEvent"), desc4 + ) + desc2.putObject(APP.instance.sID("type"), APP.instance.sID("gradientLayer"), desc3) + desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) layer = docref.activeLayer if not isinstance(layer, ArtLayer): raise ValueError("Failed to create a gradient color layer") - if 'blend_mode' in kwargs: - layer.blendMode = kwargs['blend_mode'] + if "blend_mode" in kwargs: + layer.blendMode = kwargs["blend_mode"] return layer diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index 2f783961..5beb97be 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -1,6 +1,7 @@ """ * Helpers: Bounds and Dimensions """ + # Standard Library Imports from contextlib import suppress from typing import TypedDict @@ -18,7 +19,6 @@ from src.utils.adobe import PS_EXCEPTIONS, LayerBounds # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -28,6 +28,7 @@ class LayerDimensions(TypedDict): """Calculated layer dimension info for a layer.""" + width: int | float height: int | float center_x: int | float @@ -40,6 +41,7 @@ class LayerDimensions(TypedDict): class TextboxDimensions(TypedDict): """Calculated width and height of paragraph text layer bounding box.""" + width: int height: int @@ -58,15 +60,18 @@ def get_dimensions_from_bounds(bounds: LayerBounds) -> LayerDimensions: Returns: Dict containing height, width, and positioning locations. """ - width = int(bounds[2]-bounds[0]) - height = int(bounds[3]-bounds[1]) + width = int(bounds[2] - bounds[0]) + height = int(bounds[3] - bounds[1]) return LayerDimensions( width=width, height=height, center_x=round((width / 2) + bounds[0]), center_y=round((height / 2) + bounds[1]), - left=int(bounds[0]), right=int(bounds[2]), - top=int(bounds[1]), bottom=int(bounds[3])) + left=int(bounds[0]), + right=int(bounds[2]), + top=int(bounds[1]), + bottom=int(bounds[3]), + ) def get_layer_dimensions(layer: ArtLayer | LayerSet) -> LayerDimensions: @@ -84,7 +89,7 @@ def get_layer_dimensions(layer: ArtLayer | LayerSet) -> LayerDimensions: def get_group_dimensions(group: LayerSet) -> LayerDimensions: """ Compute the dimensions of a group. - + Uses a workaround to avoid erroneous dimensions, which might occur when the group contains shapes. """ @@ -106,7 +111,7 @@ def get_layer_width(layer: ArtLayer | LayerSet) -> float | int: int: Width of the layer in pixels. """ bounds = layer.bounds - return int(bounds[2]-bounds[0]) + return int(bounds[2] - bounds[0]) def get_layer_height(layer: ArtLayer | LayerSet) -> float | int: @@ -119,7 +124,7 @@ def get_layer_height(layer: ArtLayer | LayerSet) -> float | int: int: Height of the layer in pixels. """ bounds = layer.bounds - return int(bounds[3]-bounds[1]) + return int(bounds[3] - bounds[1]) """ @@ -140,15 +145,16 @@ def get_bounds_no_effects(layer: ArtLayer | LayerSet) -> LayerBounds: d = get_layer_action_descriptor(layer) try: # Try getting bounds no effects - bounds = d.getObjectValue(sID('boundsNoEffects')) + bounds = d.getObjectValue(APP.instance.sID("boundsNoEffects")) except PS_EXCEPTIONS: # Try getting bounds - bounds = d.getObjectValue(sID('bounds')) + bounds = d.getObjectValue(APP.instance.sID("bounds")) return ( - bounds.getInteger(sID('left')), - bounds.getInteger(sID('top')), - bounds.getInteger(sID('right')), - bounds.getInteger(sID('bottom'))) + bounds.getInteger(APP.instance.sID("left")), + bounds.getInteger(APP.instance.sID("top")), + bounds.getInteger(APP.instance.sID("right")), + bounds.getInteger(APP.instance.sID("bottom")), + ) # Fallback to layer object bounds property return layer.bounds @@ -178,8 +184,10 @@ def get_width_no_effects(layer: ArtLayer | LayerSet) -> float | int: with suppress(Exception): # Try getting bounds no effects d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundsNoEffects')) - return bounds.getInteger(sID('right')) - bounds.getInteger(sID('left')) + bounds = d.getObjectValue(APP.instance.sID("boundsNoEffects")) + return bounds.getInteger(APP.instance.sID("right")) - bounds.getInteger( + APP.instance.sID("left") + ) return get_layer_width(layer) @@ -195,8 +203,10 @@ def get_height_no_effects(layer: ArtLayer | LayerSet) -> float | int: with suppress(Exception): # Try getting bounds no effects d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundsNoEffects')) - return bounds.getInteger(sID('bottom')) - bounds.getInteger(sID('top')) + bounds = d.getObjectValue(APP.instance.sID("boundsNoEffects")) + return bounds.getInteger(APP.instance.sID("bottom")) - bounds.getInteger( + APP.instance.sID("top") + ) return get_layer_height(layer) @@ -215,9 +225,9 @@ def check_textbox_overflow(layer: ArtLayer) -> bool: True if text overflowing, else False. """ # Create a test layer to check the difference - height = get_layer_dimensions(layer)['height'] + height = get_layer_dimensions(layer)["height"] layer.textItem.height = 1000 - dif = get_layer_dimensions(layer)['height'] - height + dif = get_layer_dimensions(layer)["height"] - height undo_action() if dif > 0: return True @@ -234,12 +244,12 @@ def get_textbox_bounds(layer: ArtLayer) -> LayerBounds: List of bounds integers. """ d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundingBox')) + bounds = d.getObjectValue(APP.instance.sID("boundingBox")) return ( - bounds.getInteger(sID('left')), - bounds.getInteger(sID('top')), - bounds.getInteger(sID('right')), - bounds.getInteger(sID('bottom')) + bounds.getInteger(APP.instance.sID("left")), + bounds.getInteger(APP.instance.sID("top")), + bounds.getInteger(APP.instance.sID("right")), + bounds.getInteger(APP.instance.sID("bottom")), ) @@ -253,10 +263,10 @@ def get_textbox_dimensions(layer: ArtLayer) -> TextboxDimensions: Dict containing width and height. """ d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundingBox')) + bounds = d.getObjectValue(APP.instance.sID("boundingBox")) return { - 'width': bounds.getInteger(sID('width')), - 'height': bounds.getInteger(sID('height')) + "width": bounds.getInteger(APP.instance.sID("width")), + "height": bounds.getInteger(APP.instance.sID("height")), } @@ -270,8 +280,8 @@ def get_textbox_width(layer: ArtLayer) -> int: Width of the textbox. """ d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundingBox')) - return bounds.getInteger(sID('width')) + bounds = d.getObjectValue(APP.instance.sID("boundingBox")) + return bounds.getInteger(APP.instance.sID("width")) def get_textbox_height(layer: ArtLayer) -> int: @@ -284,5 +294,5 @@ def get_textbox_height(layer: ArtLayer) -> int: Height of the textbox. """ d = get_layer_action_descriptor(layer) - bounds = d.getObjectValue(sID('boundingBox')) - return bounds.getInteger(sID('height')) + bounds = d.getObjectValue(APP.instance.sID("boundingBox")) + return bounds.getInteger(APP.instance.sID("height")) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index dd9ab92f..bbde3c1e 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -23,7 +23,6 @@ from src.schema.colors import GradientConfig # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -253,10 +252,10 @@ def apply_rgb_from_list( color_type: Color action descriptor type, defaults to 'color'. """ ad = ActionDescriptor() - ad.putDouble(sID("red"), color[0]) - ad.putDouble(sID("green"), color[1]) - ad.putDouble(sID("blue"), color[2]) - action.putObject(sID(color_type), sID("RGBColor"), ad) + ad.putDouble(APP.instance.sID("red"), color[0]) + ad.putDouble(APP.instance.sID("green"), color[1]) + ad.putDouble(APP.instance.sID("blue"), color[2]) + action.putObject(APP.instance.sID(color_type), APP.instance.sID("RGBColor"), ad) def apply_cmyk_from_list( @@ -272,11 +271,13 @@ def apply_cmyk_from_list( color_type: Color action descriptor type, defaults to 'color'. """ ad = ActionDescriptor() - ad.putDouble(sID("cyan"), color[0]) - ad.putDouble(sID("magenta"), color[1]) - ad.putDouble(sID("yellowColor"), color[2]) - ad.putDouble(sID("black"), color[3]) - action.putObject(sID(color_type), sID("CMYKColorClass"), ad) + ad.putDouble(APP.instance.sID("cyan"), color[0]) + ad.putDouble(APP.instance.sID("magenta"), color[1]) + ad.putDouble(APP.instance.sID("yellowColor"), color[2]) + ad.putDouble(APP.instance.sID("black"), color[3]) + action.putObject( + APP.instance.sID(color_type), APP.instance.sID("CMYKColorClass"), ad + ) def apply_rgb( @@ -348,14 +349,22 @@ def add_color_to_gradient( """ action = ActionDescriptor() apply_color(action, color) - action.putEnumerated(sID("type"), sID("colorStopType"), sID("userStop")) - action.putInteger(sID("location"), location) - action.putInteger(sID("midpoint"), midpoint) - action_list.putObject(sID("colorStop"), action) + action.putEnumerated( + APP.instance.sID("type"), + APP.instance.sID("colorStopType"), + APP.instance.sID("userStop"), + ) + action.putInteger(APP.instance.sID("location"), location) + action.putInteger(APP.instance.sID("midpoint"), midpoint) + action_list.putObject(APP.instance.sID("colorStop"), action) def fill_layer_primary(): """Fill active layer using foreground color.""" desc1 = ActionDescriptor() - desc1.putEnumerated(sID("using"), sID("fillContents"), sID("foregroundColor")) - APP.executeAction(sID("fill"), desc1, NO_DIALOG) + desc1.putEnumerated( + APP.instance.sID("using"), + APP.instance.sID("fillContents"), + APP.instance.sID("foregroundColor"), + ) + APP.instance.executeAction(APP.instance.sID("fill"), desc1, NO_DIALOG) diff --git a/src/helpers/descriptors.py b/src/helpers/descriptors.py index 13cd234f..4744e16d 100644 --- a/src/helpers/descriptors.py +++ b/src/helpers/descriptors.py @@ -12,7 +12,6 @@ from src import APP # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -30,5 +29,5 @@ def get_layer_action_descriptor(layer: ArtLayer | LayerSet) -> ActionDescriptor: Action descriptor info object about the layer. """ ref = ActionReference() - ref.putIdentifier(sID('layer'), layer.id) - return APP.executeActionGet(ref) + ref.putIdentifier(APP.instance.sID("layer"), layer.id) + return APP.instance.executeActionGet(ref) diff --git a/src/helpers/design.py b/src/helpers/design.py index 84ee4b14..e0fe66a3 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -27,7 +27,6 @@ from src.utils.adobe import PS_EXCEPTIONS # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -35,9 +34,7 @@ """ -def fill_empty_area( - reference: ArtLayer, color: SolidColor | None = None -) -> ArtLayer: +def fill_empty_area(reference: ArtLayer, color: SolidColor | None = None) -> ArtLayer: """Fills empty gaps on an art layer, such as a symbol, with a solid color. Args: @@ -45,22 +42,22 @@ def fill_empty_area( color: Color of the background fill """ # Magic Wand contiguous outside symbol - docref = APP.activeDocument + docref = APP.instance.activeDocument docsel = docref.selection coords = ActionDescriptor() click1 = ActionDescriptor() ref1 = ActionReference() - idPaint = sID("paint") - idPixel = sID("pixelsUnit") - idTolerance = sID("tolerance") - coords.putUnitDouble(sID("horizontal"), idPixel, 5) - coords.putUnitDouble(sID("vertical"), idPixel, 5) - ref1.putProperty(sID("channel"), sID("selection")) - click1.putReference(sID("target"), ref1) - click1.putObject(sID("to"), idPaint, coords) + idPaint = APP.instance.sID("paint") + idPixel = APP.instance.sID("pixelsUnit") + idTolerance = APP.instance.sID("tolerance") + coords.putUnitDouble(APP.instance.sID("horizontal"), idPixel, 5) + coords.putUnitDouble(APP.instance.sID("vertical"), idPixel, 5) + ref1.putProperty(APP.instance.sID("channel"), APP.instance.sID("selection")) + click1.putReference(APP.instance.sID("target"), ref1) + click1.putObject(APP.instance.sID("to"), idPaint, coords) click1.putInteger(idTolerance, 12) - click1.putBoolean(sID("antiAlias"), True) - APP.executeAction(sID("set"), click1) + click1.putBoolean(APP.instance.sID("antiAlias"), True) + APP.instance.executeAction(APP.instance.sID("set"), click1) # Invert selection docsel.invert() @@ -73,13 +70,17 @@ def fill_empty_area( layer.move(reference, ElementPlacement.PlaceAfter) # Fill selection with stroke color - APP.foregroundColor = color or rgb_black() + APP.instance.foregroundColor = color or rgb_black() click3 = ActionDescriptor() - click3.putObject(sID("from"), idPaint, coords) + click3.putObject(APP.instance.sID("from"), idPaint, coords) click3.putInteger(idTolerance, 0) - click3.putEnumerated(sID("using"), sID("fillContents"), sID("foregroundColor")) - click3.putBoolean(sID("contiguous"), False) - APP.executeAction(sID("fill"), click3) + click3.putEnumerated( + APP.instance.sID("using"), + APP.instance.sID("fillContents"), + APP.instance.sID("foregroundColor"), + ) + click3.putBoolean(APP.instance.sID("contiguous"), False) + APP.instance.executeAction(APP.instance.sID("fill"), click3) # Clear Selection docsel.deselect() @@ -96,7 +97,7 @@ def content_aware_fill_edges( feather: Whether to feather the selection before performing the fill operation. """ # Set active layer if needed, then rasterize - docref = APP.activeDocument + docref = APP.instance.activeDocument if layer: docref.activeLayer = layer active_layer = layer @@ -150,7 +151,7 @@ def generative_fill_edges( Smart layer document if Generative Fill operation succeeded, otherwise None. """ # Set docref and use active layer if not provided - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer active_layer = layer @@ -171,7 +172,7 @@ def generative_fill_edges( edit_smart_layer() # Select pixels of active layer and invert - docref = APP.activeDocument + docref = APP.instance.activeDocument select_layer_pixels(active_layer) selection = docref.selection @@ -214,10 +215,20 @@ def generative_fill_edges( def content_aware_fill() -> None: """Fills the current selection using content aware fill.""" desc = ActionDescriptor() - desc.putEnumerated(sID("using"), sID("fillContents"), sID("contentAware")) - desc.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - desc.putEnumerated(sID("mode"), sID("blendMode"), sID("normal")) - APP.executeAction(sID("fill"), desc, NO_DIALOG) + desc.putEnumerated( + APP.instance.sID("using"), + APP.instance.sID("fillContents"), + APP.instance.sID("contentAware"), + ) + desc.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), 100 + ) + desc.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("blendMode"), + APP.instance.sID("normal"), + ) + APP.instance.executeAction(APP.instance.sID("fill"), desc, NO_DIALOG) def generative_fill() -> None: @@ -226,26 +237,36 @@ def generative_fill() -> None: ref1 = ActionReference() desc2 = ActionDescriptor() desc3 = ActionDescriptor() - ref1.putEnumerated(sID("document"), sID("ordinal"), sID("targetEnum")) - desc1.putReference(sID("target"), ref1) - desc1.putString(sID("prompt"), """""") - desc1.putString(sID("serviceID"), """clio""") - desc1.putEnumerated(sID("mode"), sID("syntheticFillMode"), sID("inpaint")) - desc3.putString(sID("gi_PROMPT"), """""") - desc3.putString(sID("gi_MODE"), """ginp""") - desc3.putInteger(sID("gi_SEED"), -1) - desc3.putInteger(sID("gi_NUM_STEPS"), -1) - desc3.putInteger(sID("gi_GUIDANCE"), 6) - desc3.putInteger(sID("gi_SIMILARITY"), 0) - desc3.putBoolean(sID("gi_CROP"), False) - desc3.putBoolean(sID("gi_DILATE"), False) - desc3.putInteger(sID("gi_CONTENT_PRESERVE"), 0) - desc3.putBoolean(sID("gi_ENABLE_PROMPT_FILTER"), True) - desc3.putBoolean(sID("dualCrop"), True) - desc3.putString(sID("gi_ADVANCED"), """{"enable_mts":true}""") - desc2.putObject(sID("clio"), sID("clio"), desc3) - desc1.putObject(sID("serviceOptionsList"), sID("target"), desc2) - APP.executeAction(sID("syntheticFill"), desc1, NO_DIALOG) + ref1.putEnumerated( + APP.instance.sID("document"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + desc1.putString(APP.instance.sID("prompt"), """""") + desc1.putString(APP.instance.sID("serviceID"), """clio""") + desc1.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("syntheticFillMode"), + APP.instance.sID("inpaint"), + ) + desc3.putString(APP.instance.sID("gi_PROMPT"), """""") + desc3.putString(APP.instance.sID("gi_MODE"), """ginp""") + desc3.putInteger(APP.instance.sID("gi_SEED"), -1) + desc3.putInteger(APP.instance.sID("gi_NUM_STEPS"), -1) + desc3.putInteger(APP.instance.sID("gi_GUIDANCE"), 6) + desc3.putInteger(APP.instance.sID("gi_SIMILARITY"), 0) + desc3.putBoolean(APP.instance.sID("gi_CROP"), False) + desc3.putBoolean(APP.instance.sID("gi_DILATE"), False) + desc3.putInteger(APP.instance.sID("gi_CONTENT_PRESERVE"), 0) + desc3.putBoolean(APP.instance.sID("gi_ENABLE_PROMPT_FILTER"), True) + desc3.putBoolean(APP.instance.sID("dualCrop"), True) + desc3.putString(APP.instance.sID("gi_ADVANCED"), """{"enable_mts":true}""") + desc2.putObject(APP.instance.sID("clio"), APP.instance.sID("clio"), desc3) + desc1.putObject( + APP.instance.sID("serviceOptionsList"), APP.instance.sID("target"), desc2 + ) + APP.instance.executeAction(APP.instance.sID("syntheticFill"), desc1, NO_DIALOG) def repair_edges(edge: int = 6) -> None: @@ -257,42 +278,48 @@ def repair_edges(edge: int = 6) -> None: # Select all desc632724 = ActionDescriptor() ref489 = ActionReference() - ref489.putProperty(sID("channel"), sID("selection")) - desc632724.putReference(sID("target"), ref489) - desc632724.putEnumerated(sID("to"), sID("ordinal"), sID("allEnum")) - APP.executeAction(sID("set"), desc632724, NO_DIALOG) + ref489.putProperty(APP.instance.sID("channel"), APP.instance.sID("selection")) + desc632724.putReference(APP.instance.sID("target"), ref489) + desc632724.putEnumerated( + APP.instance.sID("to"), APP.instance.sID("ordinal"), APP.instance.sID("allEnum") + ) + APP.instance.executeAction(APP.instance.sID("set"), desc632724, NO_DIALOG) # Contract selection contract = ActionDescriptor() - contract.putUnitDouble(sID("by"), sID("pixelsUnit"), edge) - contract.putBoolean(sID("selectionModifyEffectAtCanvasBounds"), True) - APP.executeAction(sID("contract"), contract, NO_DIALOG) + contract.putUnitDouble(APP.instance.sID("by"), APP.instance.sID("pixelsUnit"), edge) + contract.putBoolean(APP.instance.sID("selectionModifyEffectAtCanvasBounds"), True) + APP.instance.executeAction(APP.instance.sID("contract"), contract, NO_DIALOG) # Inverse the selection - APP.executeAction(sID("inverse"), None, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("inverse"), None, NO_DIALOG) # Content aware fill desc_caf = ActionDescriptor() desc_caf.putEnumerated( - sID("cafSamplingRegion"), - sID("cafSamplingRegion"), - sID("cafSamplingRegionRectangular"), + APP.instance.sID("cafSamplingRegion"), + APP.instance.sID("cafSamplingRegion"), + APP.instance.sID("cafSamplingRegionRectangular"), ) - desc_caf.putBoolean(sID("cafSampleAllLayers"), False) + desc_caf.putBoolean(APP.instance.sID("cafSampleAllLayers"), False) desc_caf.putEnumerated( - sID("cafColorAdaptationLevel"), - sID("cafColorAdaptationLevel"), - sID("cafColorAdaptationDefault"), + APP.instance.sID("cafColorAdaptationLevel"), + APP.instance.sID("cafColorAdaptationLevel"), + APP.instance.sID("cafColorAdaptationDefault"), ) desc_caf.putEnumerated( - sID("cafRotationAmount"), sID("cafRotationAmount"), sID("cafRotationAmountNone") + APP.instance.sID("cafRotationAmount"), + APP.instance.sID("cafRotationAmount"), + APP.instance.sID("cafRotationAmountNone"), ) - desc_caf.putBoolean(sID("cafScale"), False) - desc_caf.putBoolean(sID("cafMirror"), False) + desc_caf.putBoolean(APP.instance.sID("cafScale"), False) + desc_caf.putBoolean(APP.instance.sID("cafMirror"), False) desc_caf.putEnumerated( - sID("cafOutput"), sID("cafOutput"), sID("cafOutputToNewLayer") + APP.instance.sID("cafOutput"), + APP.instance.sID("cafOutput"), + APP.instance.sID("cafOutputToNewLayer"), ) - APP.executeAction(sID("cafWorkspace"), desc_caf, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("cafWorkspace"), desc_caf, NO_DIALOG) # Deselect - APP.activeDocument.selection.deselect() + APP.instance.activeDocument.selection.deselect() diff --git a/src/helpers/document.py b/src/helpers/document.py index b02aeea7..676ff652 100644 --- a/src/helpers/document.py +++ b/src/helpers/document.py @@ -1,6 +1,7 @@ """ * Helpers: Documents """ + # Standard Library Imports from pathlib import Path from typing import Any @@ -17,7 +18,7 @@ JPEGSaveOptions, PhotoshopSaveOptions, ElementPlacement, - FormatOptionsType + FormatOptionsType, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -29,7 +30,6 @@ from src.utils.adobe import PS_EXCEPTIONS # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -47,14 +47,16 @@ def get_leaf_layers(group: Document | LayerSet | None = None) -> list[ArtLayer]: A list of leaf layers in a LayerSet or document. """ if not group: - group = APP.activeDocument + group = APP.instance.activeDocument layers = [node for node in group.artLayers] for g in group.layerSets: layers.extend(get_leaf_layers(g)) return layers + ArtLayerTree = dict[str, "ArtLayer | ArtLayerTree"] + def get_layer_tree(group: Document | LayerSet | None = None) -> ArtLayerTree: """Composes a dictionary tree of layers in the active document or a specific LayerSet. @@ -65,7 +67,7 @@ def get_layer_tree(group: Document | LayerSet | None = None) -> ArtLayerTree: A dictionary tree comprised of all the layers in a document or group. """ if not group: - group = APP.activeDocument + group = APP.instance.activeDocument layers: ArtLayerTree = {layer.name: layer for layer in group.artLayers} for g in group.layerSets: layers[g.name] = get_layer_tree(g) @@ -80,8 +82,8 @@ def get_layer_tree(group: Document | LayerSet | None = None) -> ArtLayerTree: def import_art( layer: ArtLayer, path: str | Path, - name: str = 'Layer 1', - docref: Document | None = None + name: str = "Layer 1", + docref: Document | None = None, ) -> ArtLayer: """Imports an art file into the active layer. @@ -95,14 +97,16 @@ def import_art( Imported art layer. """ desc = ActionDescriptor() - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.activeLayer = layer - desc.putPath(sID('target'), str(path)) - APP.executeAction(sID('placeEvent'), desc) + desc.putPath(APP.instance.sID("target"), str(path)) + APP.instance.executeAction(APP.instance.sID("placeEvent"), desc) active_layer = docref.activeLayer if not isinstance(active_layer, ArtLayer): - raise ValueError("Failed to import art. Active layer is unexpectedly not an ArtLayer.") + raise ValueError( + "Failed to import art. Active layer is unexpectedly not an ArtLayer." + ) active_layer.name = name return active_layer @@ -112,7 +116,7 @@ def import_svg( path: str | Path, ref: ArtLayer | LayerSet | None = None, placement: ElementPlacement | None = None, - docref: Document | None = None + docref: Document | None = None, ) -> ArtLayer: """Imports an SVG image, then moves it if needed. @@ -127,13 +131,15 @@ def import_svg( """ # Import the art desc = ActionDescriptor() - docref = docref or APP.activeDocument - desc.putPath(sID('target'), str(path)) - APP.executeAction(sID('placeEvent'), desc) + docref = docref or APP.instance.activeDocument + desc.putPath(APP.instance.sID("target"), str(path)) + APP.instance.executeAction(APP.instance.sID("placeEvent"), desc) active_layer = docref.activeLayer if not isinstance(active_layer, ArtLayer): - raise ValueError("Failed to import SVG. Active layer is unexpectedly a LayerSet.") + raise ValueError( + "Failed to import SVG. Active layer is unexpectedly a LayerSet." + ) # Position the layer if needed if ref and placement: @@ -145,7 +151,7 @@ def paste_file( layer: ArtLayer, path: str | Path, action: Callable[[], Any] | None = None, - docref: Document | None = None + docref: Document | None = None, ) -> ArtLayer: """Pastes the given file into the specified layer. @@ -160,36 +166,35 @@ def paste_file( Active layer where art was pasted. """ # Select the correct layer, then load the file - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.activeLayer = layer - APP.load(str(path)) + APP.instance.load(str(path)) # Optionally run action on art before importing it if action: action() # Select the entire image, copy it, and close the file - newdoc = APP.activeDocument + newdoc = APP.instance.activeDocument docsel = newdoc.selection docsel.selectAll() docsel.copy() - newdoc.close( - SaveOptions.DoNotSaveChanges) + newdoc.close(SaveOptions.DoNotSaveChanges) # Paste the image into the specific layer docref.paste() active_layer = docref.activeLayer if not isinstance(active_layer, ArtLayer): - raise ValueError("Failed to paste file. Active layer is unexpectedly not an ArtLayer.") + raise ValueError( + "Failed to paste file. Active layer is unexpectedly not an ArtLayer." + ) return active_layer def import_art_into_new_layer( - path: str | Path, - name: str = "New Layer", - docref: Document | None = None + path: str | Path, name: str = "New Layer", docref: Document | None = None ) -> ArtLayer: """Creates a new layer and imports a given art into that layer. @@ -201,11 +206,7 @@ def import_art_into_new_layer( Returns: New ArtLayer with imported art. """ - return import_art( - layer=create_new_layer(name), - path=path, - name=name, - docref=docref) + return import_art(layer=create_new_layer(name), path=path, name=name, docref=docref) """ @@ -222,12 +223,12 @@ def jump_to_history_state(position: int): """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putOffset(sID('historyState'), position) - desc1.putReference(sID('target'), ref1) - APP.executeAction(sID('select'), desc1, NO_DIALOG) + ref1.putOffset(APP.instance.sID("historyState"), position) + desc1.putReference(APP.instance.sID("target"), ref1) + APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) -def toggle_history_state(direction: str = 'previous') -> None: +def toggle_history_state(direction: str = "previous") -> None: """Alter the history state. Args: @@ -235,19 +236,23 @@ def toggle_history_state(direction: str = 'previous') -> None: """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putEnumerated(sID('historyState'), sID('ordinal'), sID(direction)) - desc1.putReference(sID('target'), ref1) - APP.executeAction(sID('select'), desc1, NO_DIALOG) + ref1.putEnumerated( + APP.instance.sID("historyState"), + APP.instance.sID("ordinal"), + APP.instance.sID(direction), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) def undo_action() -> None: """Undo the last action in the history state.""" - toggle_history_state('previous') + toggle_history_state("previous") def redo_action() -> None: """Redo the last action undone in the history state.""" - toggle_history_state('next') + toggle_history_state("next") def reset_document(docref: Document | None = None) -> None: @@ -256,11 +261,11 @@ def reset_document(docref: Document | None = None) -> None: Args: docref: Reference document to reset state in, use active if not provided. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument d1, r1 = ActionDescriptor(), ActionReference() - r1.putName(sID('snapshotClass'), docref.name) - d1.putReference(sID('target'), r1) - APP.executeAction(sID('select'), d1, NO_DIALOG) + r1.putName(APP.instance.sID("snapshotClass"), docref.name) + d1.putReference(APP.instance.sID("target"), r1) + APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) """ @@ -278,7 +283,7 @@ def points_to_pixels(number: int | float, docref: Document | None = None) -> flo Returns: Float representing the given value in pixel units. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument return (docref.resolution / 72) * number @@ -292,7 +297,7 @@ def pixels_to_points(number: int | float, docref: Document | None = None) -> flo Returns: Float representing the given value in point units. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument return number / (docref.resolution / 72) @@ -308,7 +313,7 @@ def check_active_document() -> bool: True if exists, otherwise False. """ try: - if APP.documents.length > 0: + if APP.instance.documents.length > 0: return True except PS_EXCEPTIONS: pass @@ -325,8 +330,8 @@ def get_document(name: str) -> Document | None: The Document if located, None if missing. """ try: - doc = APP.documents[name] - APP.activeDocument = doc + doc = APP.instance.documents[name] + APP.instance.activeDocument = doc return doc except PS_EXCEPTIONS: return @@ -340,12 +345,16 @@ def get_document(name: str) -> Document | None: def trim_transparent_pixels() -> None: """Trim transparent pixels from Photoshop document.""" desc258 = ActionDescriptor() - desc258.putEnumerated(sID('trimBasedOn'), sID('trimBasedOn'), sID('transparency')) - desc258.putBoolean(sID('top'), True) - desc258.putBoolean(sID('bottom'), True) - desc258.putBoolean(sID('left'), True) - desc258.putBoolean(sID('right'), True) - APP.executeAction(sID('trim'), desc258, NO_DIALOG) + desc258.putEnumerated( + APP.instance.sID("trimBasedOn"), + APP.instance.sID("trimBasedOn"), + APP.instance.sID("transparency"), + ) + desc258.putBoolean(APP.instance.sID("top"), True) + desc258.putBoolean(APP.instance.sID("bottom"), True) + desc258.putBoolean(APP.instance.sID("left"), True) + desc258.putBoolean(APP.instance.sID("right"), True) + APP.instance.executeAction(APP.instance.sID("trim"), desc258, NO_DIALOG) """ @@ -360,17 +369,18 @@ def save_document_png(path: Path, docref: Document | None = None) -> None: path: Path to save the PNG file. docref: Current active document. Use active if not provided. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument png_options = PNGSaveOptions() png_options.compression = 3 png_options.interlaced = False docref.saveAs( - file_path=str(path.with_suffix('.png')), - options=png_options, - asCopy=True) + file_path=str(path.with_suffix(".png")), options=png_options, asCopy=True + ) -def save_document_jpeg(path: Path, docref: Document | None = None, optimize: bool = True) -> None: +def save_document_jpeg( + path: Path, docref: Document | None = None, optimize: bool = True +) -> None: """Save the current document as a JPEG. Args: @@ -381,27 +391,22 @@ def save_document_jpeg(path: Path, docref: Document | None = None, optimize: boo """ # Set up the save options - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument options = JPEGSaveOptions(quality=12) try: - # Reduces filesize, unsupported by older Photoshop versions if optimize: options.formatOptions = FormatOptionsType.OptimizedBaseline # Save the document docref.saveAs( - file_path=str(path.with_suffix('.jpg')), - options=options, - asCopy=True) + file_path=str(path.with_suffix(".jpg")), options=options, asCopy=True + ) # Retry without Optimize Baseline except PS_EXCEPTIONS as e: if optimize: - return save_document_jpeg( - path=path, - optimize=False, - docref=docref) + return save_document_jpeg(path=path, optimize=False, docref=docref) raise OSError from e @@ -412,11 +417,12 @@ def save_document_psd(path: Path, docref: Document | None = None) -> None: path: Path to save the PSD file. docref: Open Photoshop document. Use active if not provided. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.saveAs( - file_path=str(path.with_suffix('.psd')), + file_path=str(path.with_suffix(".psd")), options=PhotoshopSaveOptions(), - asCopy=True) + asCopy=True, + ) def save_document_psb(path: Path) -> None: @@ -429,14 +435,16 @@ def save_document_psb(path: Path) -> None: """ d1 = ActionDescriptor() d2 = ActionDescriptor() - d2.putBoolean(sID('maximizeCompatibility'), True) - d1.putObject(sID('as'), sID('largeDocumentFormat'), d2) - d1.putPath(sID('in'), str(path.with_suffix('.psb'))) - d1.putBoolean(sID('lowerCase'), True) - APP.executeAction(sID('save'), d1, NO_DIALOG) + d2.putBoolean(APP.instance.sID("maximizeCompatibility"), True) + d1.putObject(APP.instance.sID("as"), APP.instance.sID("largeDocumentFormat"), d2) + d1.putPath(APP.instance.sID("in"), str(path.with_suffix(".psb"))) + d1.putBoolean(APP.instance.sID("lowerCase"), True) + APP.instance.executeAction(APP.instance.sID("save"), d1, NO_DIALOG) -def close_document(save: bool = False, docref: Document | None = None, purge: bool = True) -> None: +def close_document( + save: bool = False, docref: Document | None = None, purge: bool = True +) -> None: """Close the active document. Args: @@ -444,11 +452,11 @@ def close_document(save: bool = False, docref: Document | None = None, purge: bo docref: Open Photoshop document. Use active if not provided. purge: Whether to purge all caches. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument save_options = SaveOptions.SaveChanges if save else SaveOptions.DoNotSaveChanges docref.close(saving=save_options) if purge: - APP.purge(PurgeTarget.AllCaches) + APP.instance.purge(PurgeTarget.AllCaches) """ @@ -464,10 +472,14 @@ def rotate_document(angle: int) -> None: """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putEnumerated(sID('document'), sID('ordinal'), sID('first')) - desc1.putReference(sID('target'), ref1) - desc1.putUnitDouble(sID('angle'), sID('angleUnit'), angle) - APP.executeAction(sID('rotateEventEnum'), desc1, NO_DIALOG) + ref1.putEnumerated( + APP.instance.sID("document"), + APP.instance.sID("ordinal"), + APP.instance.sID("first"), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + desc1.putUnitDouble(APP.instance.sID("angle"), APP.instance.sID("angleUnit"), angle) + APP.instance.executeAction(APP.instance.sID("rotateEventEnum"), desc1, NO_DIALOG) def rotate_counter_clockwise() -> None: @@ -497,8 +509,12 @@ def paste_to_document(layer: ArtLayer | LayerSet | None = None): layer: Layer to make active, if provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer desc1 = ActionDescriptor() - desc1.putEnumerated(sID("antiAlias"), sID("antiAliasType"), sID("antiAliasNone")) - desc1.putClass(sID("as"), sID("pixel")) - APP.executeAction(sID("paste"), desc1, NO_DIALOG) + desc1.putEnumerated( + APP.instance.sID("antiAlias"), + APP.instance.sID("antiAliasType"), + APP.instance.sID("antiAliasNone"), + ) + desc1.putClass(APP.instance.sID("as"), APP.instance.sID("pixel")) + APP.instance.executeAction(APP.instance.sID("paste"), desc1, NO_DIALOG) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index 7aef4c6d..53a72ea6 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -4,19 +4,24 @@ # Standard Library Imports # Third Party Imports -from photoshop.api import DialogModes, ActionDescriptor, ActionReference, ActionList +from photoshop.api import ActionDescriptor, ActionList, ActionReference, DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet # Local Imports from src import APP -from src.helpers.colors import apply_color, get_color, add_color_to_gradient from src.enums.adobe import Stroke -from src.schema.adobe import EffectBevel, EffectColorOverlay, EffectDropShadow, EffectGradientOverlay, EffectStroke, \ - LayerEffects +from src.helpers.colors import add_color_to_gradient, apply_color, get_color +from src.schema.adobe import ( + EffectBevel, + EffectColorOverlay, + EffectDropShadow, + EffectGradientOverlay, + EffectStroke, + LayerEffects, +) # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -33,17 +38,23 @@ def set_fill_opacity(opacity: float, layer: ArtLayer | LayerSet | None) -> None: """ # Set the active layer if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer # Set the layer's fill opacity d = ActionDescriptor() ref = ActionReference() d1 = ActionDescriptor() - ref.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - d.putReference(sID("target"), ref) - d1.putUnitDouble(sID("fillOpacity"), sID("percentUnit"), opacity) - d.putObject(sID("to"), sID("layer"), d1) - APP.executeAction(sID("set"), d, NO_DIALOG) + ref.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + d.putReference(APP.instance.sID("target"), ref) + d1.putUnitDouble( + APP.instance.sID("fillOpacity"), APP.instance.sID("percentUnit"), opacity + ) + d.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), d1) + APP.instance.executeAction(APP.instance.sID("set"), d, NO_DIALOG) """ @@ -51,7 +62,9 @@ def set_fill_opacity(opacity: float, layer: ArtLayer | LayerSet | None) -> None: """ -def set_layer_fx_visibility(layer: ArtLayer | LayerSet | None = None, visible: bool = True) -> None: +def set_layer_fx_visibility( + layer: ArtLayer | LayerSet | None = None, visible: bool = True +) -> None: """Shows or hides the layer effects on a given layer. Args: @@ -60,17 +73,23 @@ def set_layer_fx_visibility(layer: ArtLayer | LayerSet | None = None, visible: b """ # Set the active layer if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer # Set the layer's FX visibility ref = ActionReference() desc = ActionDescriptor() action_list = ActionList() - ref.putClass(sID("layerEffects")) - ref.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) + ref.putClass(APP.instance.sID("layerEffects")) + ref.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) action_list.putReference(ref) - desc.putList(sID("target"), action_list) - APP.executeAction(sID("show" if visible else "hide"), desc, NO_DIALOG) + desc.putList(APP.instance.sID("target"), action_list) + APP.instance.executeAction( + APP.instance.sID("show" if visible else "hide"), desc, NO_DIALOG + ) def enable_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: @@ -98,15 +117,24 @@ def clear_layer_fx(layer: ArtLayer | LayerSet | None) -> None: layer: Layer object """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer try: desc1600 = ActionDescriptor() ref126 = ActionReference() - ref126.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - desc1600.putReference(sID("target"), ref126) - APP.executeAction(sID("disableLayerStyle"), desc1600, NO_DIALOG) + ref126.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc1600.putReference(APP.instance.sID("target"), ref126) + APP.instance.executeAction( + APP.instance.sID("disableLayerStyle"), desc1600, NO_DIALOG + ) except Exception as e: - print(e, f"""\nLayer "{layer.name if layer else ''}" has no effects!""") + print( + e, + f"""\nLayer "{layer.name if layer else ""}" has no effects!""", + ) def rasterize_layer_fx(layer: ArtLayer) -> None: @@ -117,13 +145,19 @@ def rasterize_layer_fx(layer: ArtLayer) -> None: """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putIdentifier(sID("layer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc1.putEnumerated(sID("what"), sID("rasterizeItem"), sID("layerStyle")) - APP.executeAction(sID("rasterizeLayer"), desc1, NO_DIALOG) - - -def copy_layer_fx(from_layer: ArtLayer | LayerSet, to_layer: ArtLayer | LayerSet) -> None: + ref1.putIdentifier(APP.instance.sID("layer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc1.putEnumerated( + APP.instance.sID("what"), + APP.instance.sID("rasterizeItem"), + APP.instance.sID("layerStyle"), + ) + APP.instance.executeAction(APP.instance.sID("rasterizeLayer"), desc1, NO_DIALOG) + + +def copy_layer_fx( + from_layer: ArtLayer | LayerSet, to_layer: ArtLayer | LayerSet +) -> None: """Copies the layer effects from one layer to another layer. Args: @@ -133,18 +167,28 @@ def copy_layer_fx(from_layer: ArtLayer | LayerSet, to_layer: ArtLayer | LayerSet # Get layer effects from source layer desc_get = ActionDescriptor() ref_get = ActionReference() - ref_get.putIdentifier(sID("layer"), from_layer.id) - desc_get.putReference(sID("null"), ref_get) - desc_get.putEnumerated(sID("class"), sID("class"), sID("layerEffects")) - result_desc = APP.executeAction(sID("get"), desc_get, NO_DIALOG) + ref_get.putIdentifier(APP.instance.sID("layer"), from_layer.id) + desc_get.putReference(APP.instance.sID("null"), ref_get) + desc_get.putEnumerated( + APP.instance.sID("class"), + APP.instance.sID("class"), + APP.instance.sID("layerEffects"), + ) + result_desc = APP.instance.executeAction( + APP.instance.sID("get"), desc_get, NO_DIALOG + ) # Apply layer effects to target layer desc_set = ActionDescriptor() ref_set = ActionReference() - ref_set.putIdentifier(sID("layer"), to_layer.id) - desc_set.putReference(sID("null"), ref_set) - desc_set.putObject(sID("to"), sID("layerEffects"), result_desc.getObjectValue(sID("layerEffects"))) - APP.executeAction(sID("set"), desc_set, NO_DIALOG) + ref_set.putIdentifier(APP.instance.sID("layer"), to_layer.id) + desc_set.putReference(APP.instance.sID("null"), ref_set) + desc_set.putObject( + APP.instance.sID("to"), + APP.instance.sID("layerEffects"), + result_desc.getObjectValue(APP.instance.sID("layerEffects")), + ) + APP.instance.executeAction(APP.instance.sID("set"), desc_set, NO_DIALOG) """ @@ -160,13 +204,17 @@ def apply_fx(layer: ArtLayer | LayerSet, effects: list[LayerEffects]) -> None: effects: List of effects to apply. """ # Set up the main action - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer main_action = ActionDescriptor() fx_action = ActionDescriptor() main_ref = ActionReference() - main_ref.putProperty(sID("property"), sID("layerEffects")) - main_ref.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - main_action.putReference(sID("target"), main_ref) + main_ref.putProperty(APP.instance.sID("property"), APP.instance.sID("layerEffects")) + main_ref.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + main_action.putReference(APP.instance.sID("target"), main_ref) # Add each action from fx dictionary for fx in effects: @@ -182,8 +230,10 @@ def apply_fx(layer: ArtLayer | LayerSet, effects: list[LayerEffects]) -> None: apply_fx_stroke(fx_action, fx) # Apply all fx actions - main_action.putObject(sID("to"), sID("layerEffects"), fx_action) - APP.executeAction(sID("set"), main_action, NO_DIALOG) + main_action.putObject( + APP.instance.sID("to"), APP.instance.sID("layerEffects"), fx_action + ) + APP.instance.executeAction(APP.instance.sID("set"), main_action, NO_DIALOG) def apply_fx_bevel(action: ActionDescriptor, fx: EffectBevel) -> None: @@ -194,26 +244,70 @@ def apply_fx_bevel(action: ActionDescriptor, fx: EffectBevel) -> None: fx: Bevel effect properties. """ d1, d2 = ActionDescriptor(), ActionDescriptor() - d1.putEnumerated(sID("highlightMode"), sID("blendMode"), sID("screen")) - apply_color(d1, fx.highlight_color, 'highlightColor') - d1.putUnitDouble(sID("highlightOpacity"), sID("percentUnit"), fx.highlight_opacity) - d1.putEnumerated(sID("shadowMode"), sID("blendMode"), sID("multiply")) - apply_color(d1, fx.shadow_color, 'shadowColor') - d1.putUnitDouble(sID("shadowOpacity"), sID("percentUnit"), fx.shadow_opacity) - d1.putEnumerated(sID("bevelTechnique"), sID("bevelTechnique"), sID("softMatte")) - d1.putEnumerated(sID("bevelStyle"), sID("bevelEmbossStyle"), sID("outerBevel")) - d1.putBoolean(sID("useGlobalAngle"), fx.global_light) - d1.putUnitDouble(sID("localLightingAngle"), sID("angleUnit"), fx.rotation) - d1.putUnitDouble(sID("localLightingAltitude"), sID("angleUnit"), fx.altitude) - d1.putUnitDouble(sID("strengthRatio"), sID("percentUnit"), fx.depth) - d1.putUnitDouble(sID("blur"), sID("pixelsUnit"), fx.size) - d1.putEnumerated(sID("bevelDirection"), sID("bevelEmbossStampStyle"), sID("in")) - d1.putObject(sID("transferSpec"), sID("shapeCurveType"), d2) - d1.putBoolean(sID("antialiasGloss"), False) - d1.putUnitDouble(sID("softness"), sID("pixelsUnit"), fx.softness) - d1.putBoolean(sID("useShape"), False) - d1.putBoolean(sID("useTexture"), False) - action.putObject(sID("bevelEmboss"), sID("bevelEmboss"), d1) + d1.putEnumerated( + APP.instance.sID("highlightMode"), + APP.instance.sID("blendMode"), + APP.instance.sID("screen"), + ) + apply_color(d1, fx.highlight_color, "highlightColor") + d1.putUnitDouble( + APP.instance.sID("highlightOpacity"), + APP.instance.sID("percentUnit"), + fx.highlight_opacity, + ) + d1.putEnumerated( + APP.instance.sID("shadowMode"), + APP.instance.sID("blendMode"), + APP.instance.sID("multiply"), + ) + apply_color(d1, fx.shadow_color, "shadowColor") + d1.putUnitDouble( + APP.instance.sID("shadowOpacity"), + APP.instance.sID("percentUnit"), + fx.shadow_opacity, + ) + d1.putEnumerated( + APP.instance.sID("bevelTechnique"), + APP.instance.sID("bevelTechnique"), + APP.instance.sID("softMatte"), + ) + d1.putEnumerated( + APP.instance.sID("bevelStyle"), + APP.instance.sID("bevelEmbossStyle"), + APP.instance.sID("outerBevel"), + ) + d1.putBoolean(APP.instance.sID("useGlobalAngle"), fx.global_light) + d1.putUnitDouble( + APP.instance.sID("localLightingAngle"), + APP.instance.sID("angleUnit"), + fx.rotation, + ) + d1.putUnitDouble( + APP.instance.sID("localLightingAltitude"), + APP.instance.sID("angleUnit"), + fx.altitude, + ) + d1.putUnitDouble( + APP.instance.sID("strengthRatio"), APP.instance.sID("percentUnit"), fx.depth + ) + d1.putUnitDouble(APP.instance.sID("blur"), APP.instance.sID("pixelsUnit"), fx.size) + d1.putEnumerated( + APP.instance.sID("bevelDirection"), + APP.instance.sID("bevelEmbossStampStyle"), + APP.instance.sID("in"), + ) + d1.putObject( + APP.instance.sID("transferSpec"), APP.instance.sID("shapeCurveType"), d2 + ) + d1.putBoolean(APP.instance.sID("antialiasGloss"), False) + d1.putUnitDouble( + APP.instance.sID("softness"), APP.instance.sID("pixelsUnit"), fx.softness + ) + d1.putBoolean(APP.instance.sID("useShape"), False) + d1.putBoolean(APP.instance.sID("useTexture"), False) + action.putObject( + APP.instance.sID("bevelEmboss"), APP.instance.sID("bevelEmboss"), d1 + ) def apply_fx_color_overlay(action: ActionDescriptor, fx: EffectColorOverlay) -> None: @@ -224,10 +318,16 @@ def apply_fx_color_overlay(action: ActionDescriptor, fx: EffectColorOverlay) -> fx: Color Overlay effect properties. """ d = ActionDescriptor() - d.putEnumerated(sID("mode"), sID("blendMode"), sID("normal")) + d.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("blendMode"), + APP.instance.sID("normal"), + ) apply_color(d, fx.color) - d.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) - action.putObject(sID("solidFill"), sID("solidFill"), d) + d.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), fx.opacity + ) + action.putObject(APP.instance.sID("solidFill"), APP.instance.sID("solidFill"), d) def apply_fx_drop_shadow(action: ActionDescriptor, fx: EffectDropShadow) -> None: @@ -239,23 +339,43 @@ def apply_fx_drop_shadow(action: ActionDescriptor, fx: EffectDropShadow) -> None """ d1 = ActionDescriptor() d2 = ActionDescriptor() - d1.putEnumerated(sID("mode"), sID("blendMode"), sID("multiply")) + d1.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("blendMode"), + APP.instance.sID("multiply"), + ) apply_color(d1, fx.color) - d1.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) - d1.putBoolean(sID("useGlobalAngle"), False) - d1.putUnitDouble(sID("localLightingAngle"), sID("angleUnit"), fx.rotation) - d1.putUnitDouble(sID("distance"), sID("pixelsUnit"), fx.distance) - d1.putUnitDouble(sID("chokeMatte"), sID("pixelsUnit"), fx.spread) - d1.putUnitDouble(sID("blur"), sID("pixelsUnit"), fx.size) - d1.putUnitDouble(sID("noise"), sID("percentUnit"), fx.noise) - d1.putBoolean(sID("antiAlias"), False) - d2.putString(sID("name"), "Linear") - d1.putObject(sID("transferSpec"), sID("shapeCurveType"), d2) - d1.putBoolean(sID("layerConceals"), True) - action.putObject(sID("dropShadow"), sID("dropShadow"), d1) - - -def apply_fx_gradient_overlay(action: ActionDescriptor, fx: EffectGradientOverlay) -> None: + d1.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), fx.opacity + ) + d1.putBoolean(APP.instance.sID("useGlobalAngle"), False) + d1.putUnitDouble( + APP.instance.sID("localLightingAngle"), + APP.instance.sID("angleUnit"), + fx.rotation, + ) + d1.putUnitDouble( + APP.instance.sID("distance"), APP.instance.sID("pixelsUnit"), fx.distance + ) + d1.putUnitDouble( + APP.instance.sID("chokeMatte"), APP.instance.sID("pixelsUnit"), fx.spread + ) + d1.putUnitDouble(APP.instance.sID("blur"), APP.instance.sID("pixelsUnit"), fx.size) + d1.putUnitDouble( + APP.instance.sID("noise"), APP.instance.sID("percentUnit"), fx.noise + ) + d1.putBoolean(APP.instance.sID("antiAlias"), False) + d2.putString(APP.instance.sID("name"), "Linear") + d1.putObject( + APP.instance.sID("transferSpec"), APP.instance.sID("shapeCurveType"), d2 + ) + d1.putBoolean(APP.instance.sID("layerConceals"), True) + action.putObject(APP.instance.sID("dropShadow"), APP.instance.sID("dropShadow"), d1) + + +def apply_fx_gradient_overlay( + action: ActionDescriptor, fx: EffectGradientOverlay +) -> None: """Adds gradient effect to layer effects action. Args: @@ -269,39 +389,65 @@ def apply_fx_gradient_overlay(action: ActionDescriptor, fx: EffectGradientOverla d5 = ActionDescriptor() color_list = ActionList() transparency_list = ActionList() - d1.putEnumerated(sID("mode"), sID("blendMode"), sID(fx.blend_mode)) - d1.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) - d2.putEnumerated(sID("gradientForm"), sID("gradientForm"), sID("customStops")) - d2.putDouble(sID("interfaceIconFrameDimmed"), fx.size) + d1.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("blendMode"), + APP.instance.sID(fx.blend_mode), + ) + d1.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), fx.opacity + ) + d2.putEnumerated( + APP.instance.sID("gradientForm"), + APP.instance.sID("gradientForm"), + APP.instance.sID("customStops"), + ) + d2.putDouble(APP.instance.sID("interfaceIconFrameDimmed"), fx.size) for c in fx.colors: add_color_to_gradient( action_list=color_list, color=get_color(c.color), location=c.location, - midpoint=c.midpoint + midpoint=c.midpoint, ) - d2.putList(sID("colors"), color_list) - d3.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - d3.putInteger(sID("location"), 0) - d3.putInteger(sID("midpoint"), 50) - transparency_list.putObject(sID("transferSpec"), d3) - d4.putUnitDouble(sID("opacity"), sID("percentUnit"), 100) - d4.putInteger(sID("location"), round(fx.size)) - d4.putInteger(sID("midpoint"), 50) - transparency_list.putObject(sID("transferSpec"), d4) - d2.putList(sID("transparency"), transparency_list) - d1.putObject(sID("gradient"), sID("gradientClassEvent"), d2) - d1.putUnitDouble(sID("angle"), sID("angleUnit"), fx.rotation) - d1.putEnumerated(sID("type"), sID("gradientType"), sID("linear")) - d1.putBoolean(sID("reverse"), False) - d1.putBoolean(sID("dither"), fx.dither) - d1.putEnumerated(cID("gs99"), sID("gradientInterpolationMethodType"), sID(fx.method)) - d1.putBoolean(sID("align"), True) - d1.putUnitDouble(sID("scale"), sID("percentUnit"), fx.scale) - d5.putUnitDouble(sID("horizontal"), sID("percentUnit"), 0) - d5.putUnitDouble(sID("vertical"), sID("percentUnit"), 0) - d1.putObject(sID("offset"), sID("paint"), d5) - action.putObject(sID("gradientFill"), sID("gradientFill"), d1) + d2.putList(APP.instance.sID("colors"), color_list) + d3.putUnitDouble(APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), 100) + d3.putInteger(APP.instance.sID("location"), 0) + d3.putInteger(APP.instance.sID("midpoint"), 50) + transparency_list.putObject(APP.instance.sID("transferSpec"), d3) + d4.putUnitDouble(APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), 100) + d4.putInteger(APP.instance.sID("location"), round(fx.size)) + d4.putInteger(APP.instance.sID("midpoint"), 50) + transparency_list.putObject(APP.instance.sID("transferSpec"), d4) + d2.putList(APP.instance.sID("transparency"), transparency_list) + d1.putObject( + APP.instance.sID("gradient"), APP.instance.sID("gradientClassEvent"), d2 + ) + d1.putUnitDouble( + APP.instance.sID("angle"), APP.instance.sID("angleUnit"), fx.rotation + ) + d1.putEnumerated( + APP.instance.sID("type"), + APP.instance.sID("gradientType"), + APP.instance.sID("linear"), + ) + d1.putBoolean(APP.instance.sID("reverse"), False) + d1.putBoolean(APP.instance.sID("dither"), fx.dither) + d1.putEnumerated( + APP.instance.cID("gs99"), + APP.instance.sID("gradientInterpolationMethodType"), + APP.instance.sID(fx.method), + ) + d1.putBoolean(APP.instance.sID("align"), True) + d1.putUnitDouble( + APP.instance.sID("scale"), APP.instance.sID("percentUnit"), fx.scale + ) + d5.putUnitDouble(APP.instance.sID("horizontal"), APP.instance.sID("percentUnit"), 0) + d5.putUnitDouble(APP.instance.sID("vertical"), APP.instance.sID("percentUnit"), 0) + d1.putObject(APP.instance.sID("offset"), APP.instance.sID("paint"), d5) + action.putObject( + APP.instance.sID("gradientFill"), APP.instance.sID("gradientFill"), d1 + ) def apply_fx_stroke(action: ActionDescriptor, fx: EffectStroke) -> None: @@ -312,11 +458,25 @@ def apply_fx_stroke(action: ActionDescriptor, fx: EffectStroke) -> None: fx: Stroke effect properties. """ d = ActionDescriptor() - d.putEnumerated(sID("style"), sID("frameStyle"), Stroke.position(fx.style)) - d.putEnumerated(sID("paintType"), sID("frameFill"), sID("solidColor")) - d.putEnumerated(sID("mode"), sID("blendMode"), sID("normal")) - d.putUnitDouble(sID("opacity"), sID("percentUnit"), fx.opacity) - d.putUnitDouble(sID("size"), sID("pixelsUnit"), fx.weight) + d.putEnumerated( + APP.instance.sID("style"), + APP.instance.sID("frameStyle"), + Stroke.position(fx.style), + ) + d.putEnumerated( + APP.instance.sID("paintType"), + APP.instance.sID("frameFill"), + APP.instance.sID("solidColor"), + ) + d.putEnumerated( + APP.instance.sID("mode"), + APP.instance.sID("blendMode"), + APP.instance.sID("normal"), + ) + d.putUnitDouble( + APP.instance.sID("opacity"), APP.instance.sID("percentUnit"), fx.opacity + ) + d.putUnitDouble(APP.instance.sID("size"), APP.instance.sID("pixelsUnit"), fx.weight) apply_color(d, get_color(fx.color)) - d.putBoolean(sID("overprint"), False) - action.putObject(sID("frameFX"), sID("frameFX"), d) + d.putBoolean(APP.instance.sID("overprint"), False) + action.putObject(APP.instance.sID("frameFX"), APP.instance.sID("frameFX"), d) diff --git a/src/helpers/layers.py b/src/helpers/layers.py index c60aa37b..67083b3a 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -1,22 +1,33 @@ """ * Helpers: Layers and Layer Groups """ + # Standard Library Imports from contextlib import suppress from collections.abc import Sequence, Iterable # Third Party Imports -from photoshop.api import DialogModes, ActionDescriptor, ActionReference, BlendMode, ElementPlacement +from photoshop.api import ( + DialogModes, + ActionDescriptor, + ActionReference, + BlendMode, + ElementPlacement, +) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet # Local Imports from src import APP, ENV -from src.utils.adobe import LayerContainer, LayerContainerTypes, ReferenceLayer, PS_EXCEPTIONS +from src.utils.adobe import ( + LayerContainer, + LayerContainerTypes, + ReferenceLayer, + PS_EXCEPTIONS, +) # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs @@ -27,7 +38,10 @@ def getLayer( name: str, - group: str | None | LayerContainerTypes | Iterable[LayerContainerTypes | str | None] = None + group: str + | None + | LayerContainerTypes + | Iterable[LayerContainerTypes | str | None] = None, ) -> ArtLayer | None: """Retrieve ArtLayer object from given name and group/group tree. @@ -42,16 +56,16 @@ def getLayer( # LayerSet provided? if not group: # LayerSet not provided - return APP.activeDocument.artLayers[name] + return APP.instance.activeDocument.artLayers[name] elif isinstance(group, str): # LayerSet name given - return APP.activeDocument.layerSets[group].artLayers[name] + return APP.instance.activeDocument.layerSets[group].artLayers[name] elif isinstance(group, LayerContainer): # LayerSet object given return group.artLayers[name] elif isinstance(group, tuple | list): # Tuple or list of LayerSets - layer_set = APP.activeDocument + layer_set = APP.instance.activeDocument for g in group: if isinstance(g, str): # LayerSet name given @@ -75,7 +89,10 @@ def getLayer( def getLayerSet( name: str, - group: str | None | LayerContainerTypes | Iterable[LayerContainerTypes | str | None] = None + group: str + | None + | LayerContainerTypes + | Iterable[LayerContainerTypes | str | None] = None, ) -> LayerSet | None: """Retrieve layer group object. @@ -90,13 +107,13 @@ def getLayerSet( # Was LayerSet provided? if not group: # No LayerSet given - return APP.activeDocument.layerSets[name] + return APP.instance.activeDocument.layerSets[name] elif isinstance(group, str): # LayerSet name given - return APP.activeDocument.layerSets[group].layerSets[name] + return APP.instance.activeDocument.layerSets[group].layerSets[name] elif isinstance(group, tuple | list): # Tuple or list of groups - layer_set = APP.activeDocument + layer_set = APP.instance.activeDocument for g in group: if isinstance(g, str): # LayerSet name given @@ -121,7 +138,9 @@ def getLayerSet( return -def get_reference_layer(name: str, group: None | str | LayerSet | Document = None) -> ReferenceLayer | None: +def get_reference_layer( + name: str, group: None | str | LayerSet | Document = None +) -> ReferenceLayer | None: """Get an ArtLayer that is a static reference layer. Args: @@ -136,18 +155,16 @@ def get_reference_layer(name: str, group: None | str | LayerSet | Document = Non # Select the proper group if str or None provided if not group: - group = APP.activeDocument + group = APP.instance.activeDocument if isinstance(group, str): try: - group = APP.activeDocument.layerSets[group] + group = APP.instance.activeDocument.layerSets[group] except PS_EXCEPTIONS: - group = APP.activeDocument + group = APP.instance.activeDocument # Select the reference layer with suppress(Exception): - return ReferenceLayer( - parent=group.artLayers.app[name], - app=APP) + return ReferenceLayer(parent=group.artLayers.app[name], app=APP.instance) return None @@ -166,8 +183,8 @@ def create_new_layer(layer_name: str | None = None) -> ArtLayer: Newly created layer object """ # Create new layer at top of layers - active_layer = APP.activeDocument.activeLayer - layer = APP.activeDocument.artLayers.add() + active_layer = APP.instance.activeDocument.activeLayer + layer = APP.instance.activeDocument.artLayers.add() layer.name = layer_name or "Layer" # Name it & set blend mode to normal @@ -178,7 +195,9 @@ def create_new_layer(layer_name: str | None = None) -> ArtLayer: return layer -def merge_layers(layers: list[ArtLayer | LayerSet], name: str | None = None) -> ArtLayer: +def merge_layers( + layers: list[ArtLayer | LayerSet], name: str | None = None +) -> ArtLayer: """Merge a set of layers together. Args: @@ -199,11 +218,13 @@ def merge_layers(layers: list[ArtLayer | LayerSet], name: str | None = None) -> select_layers(layers) # Merge layers and return result - APP.executeAction(sID("mergeLayersNew"), None, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, NO_DIALOG) - active_layer = APP.activeDocument.activeLayer + active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): - raise ValueError("Failed to merge layers. Active layer is unexpectedly not an ArtLayer.") + raise ValueError( + "Failed to merge layers. Active layer is unexpectedly not an ArtLayer." + ) if name: active_layer.name = name @@ -236,21 +257,25 @@ def group_layers( desc1 = ActionDescriptor() ref1 = ActionReference() ref2 = ActionReference() - ref1.putClass(sID("layerSection")) - desc1.putReference(sID('null'), ref1) - ref2.putEnumerated(cID('Lyr '), cID('Ordn'), cID('Trgt')) - desc1.putReference(cID('From'), ref2) + ref1.putClass(APP.instance.sID("layerSection")) + desc1.putReference(APP.instance.sID("null"), ref1) + ref2.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc1.putReference(APP.instance.cID("From"), ref2) desc2 = ActionDescriptor() - desc2.putString(cID('Nm '), name) - desc1.putObject(cID('Usng'), sID("layerSection"), desc2) - desc1.putInteger(sID("layerSectionStart"), 0) - desc1.putInteger(sID("layerSectionEnd"), 1) - desc1.putString(cID('Nm '), name) - APP.executeAction(cID('Mk '), desc1, NO_DIALOG) - - active_layer = APP.activeDocument.activeLayer + desc2.putString(APP.instance.cID("Nm "), name) + desc1.putObject(APP.instance.cID("Usng"), APP.instance.sID("layerSection"), desc2) + desc1.putInteger(APP.instance.sID("layerSectionStart"), 0) + desc1.putInteger(APP.instance.sID("layerSectionEnd"), 1) + desc1.putString(APP.instance.cID("Nm "), name) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, NO_DIALOG) + + active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): - raise ValueError("Failed to group layers. Active layer is unexpectedly not a LayerSet.") + raise ValueError( + "Failed to group layers. Active layer is unexpectedly not a LayerSet." + ) return active_layer @@ -268,15 +293,21 @@ def duplicate_group(group: LayerSet, name: str) -> LayerSet: desc241 = ActionDescriptor() ref4 = ActionReference() - ref4.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - desc241.putReference(sID("target"), ref4) - desc241.putString(sID("name"), name) - desc241.putInteger(sID("version"), 5) - APP.executeAction(sID("duplicate"), desc241, NO_DIALOG) - - active_layer = APP.activeDocument.activeLayer + ref4.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc241.putReference(APP.instance.sID("target"), ref4) + desc241.putString(APP.instance.sID("name"), name) + desc241.putInteger(APP.instance.sID("version"), 5) + APP.instance.executeAction(APP.instance.sID("duplicate"), desc241, NO_DIALOG) + + active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): - raise ValueError("Failed to duplicate group. Active layer is unexpectedly not a LayerSet.") + raise ValueError( + "Failed to duplicate group. Active layer is unexpectedly not a LayerSet." + ) return active_layer @@ -288,8 +319,8 @@ def merge_group(group: LayerSet | None = None) -> None: group: Layer set to merge. Merges active if not provided. """ if group: - APP.activeDocument.activeLayer = group - APP.executeAction(sID("mergeLayersNew"), None, NO_DIALOG) + APP.instance.activeDocument.activeLayer = group + APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, NO_DIALOG) """ @@ -297,26 +328,32 @@ def merge_group(group: LayerSet | None = None) -> None: """ -def smart_layer(layer: ArtLayer | LayerSet | None = None, docref: Document | None = None) -> ArtLayer: +def smart_layer( + layer: ArtLayer | LayerSet | None = None, docref: Document | None = None +) -> ArtLayer: """Makes a given layer, or the currently selected layer(s) into a smart layer. Args: layer: Layer to turn into smart layer, use active layer(s) if not provided. docref: Document reference, use active if not provided. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer - APP.executeAction(sID("newPlacedLayer"), None, NO_DIALOG) - - active_layer = APP.activeDocument.activeLayer + APP.instance.executeAction(APP.instance.sID("newPlacedLayer"), None, NO_DIALOG) + + active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): - raise ValueError("Failed to convert layer to smart layer. Active layer is unexpectedly not an ArtLayer.") + raise ValueError( + "Failed to convert layer to smart layer. Active layer is unexpectedly not an ArtLayer." + ) return active_layer -def edit_smart_layer(layer: ArtLayer | None = None, docref: Document | None = None) -> None: +def edit_smart_layer( + layer: ArtLayer | None = None, docref: Document | None = None +) -> None: """Opens the contents of a given smart layer (as a separate document) for editing. Args: @@ -324,12 +361,16 @@ def edit_smart_layer(layer: ArtLayer | None = None, docref: Document | None = No docref: Document reference, use active if not provided. """ if layer: - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.activeLayer = layer - APP.executeAction(sID("placedLayerEditContents"), None, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("placedLayerEditContents"), None, NO_DIALOG + ) -def unpack_smart_layer(layer: ArtLayer | None = None, docref: Document | None = None) -> None: +def unpack_smart_layer( + layer: ArtLayer | None = None, docref: Document | None = None +) -> None: """Converts a smart layer back into its separate components. Args: @@ -337,9 +378,11 @@ def unpack_smart_layer(layer: ArtLayer | None = None, docref: Document | None = docref: Document reference, use active if not provided. """ if layer: - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.activeLayer = layer - APP.executeAction(sID("placedLayerConvertToLayers"), None, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("placedLayerConvertToLayers"), None, NO_DIALOG + ) """ @@ -357,12 +400,12 @@ def lock_layer(layer: ArtLayer | LayerSet, protection: str = "protectAll") -> No d1 = ActionDescriptor() d2 = ActionDescriptor() r1 = ActionReference() - r1.putIdentifier(sID("layer"), layer.id) - d1.putReference(sID("target"), r1) - d2.putBoolean(sID(protection), True) - idlayerLocking = sID("layerLocking") + r1.putIdentifier(APP.instance.sID("layer"), layer.id) + d1.putReference(APP.instance.sID("target"), r1) + d2.putBoolean(APP.instance.sID(protection), True) + idlayerLocking = APP.instance.sID("layerLocking") d1.putObject(idlayerLocking, idlayerLocking, d2) - APP.executeAction(sID("applyLocking"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("applyLocking"), d1, NO_DIALOG) def unlock_layer(layer: ArtLayer | LayerSet) -> None: @@ -388,16 +431,13 @@ def select_layer(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None """ d1 = ActionDescriptor() r1 = ActionReference() - r1.putIdentifier(sID('layer'), layer.id) - d1.putReference(sID('target'), r1) - d1.putBoolean(sID('makeVisible'), make_visible) - APP.executeAction(sID('select'), d1, NO_DIALOG) + r1.putIdentifier(APP.instance.sID("layer"), layer.id) + d1.putReference(APP.instance.sID("target"), r1) + d1.putBoolean(APP.instance.sID("makeVisible"), make_visible) + APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) -def select_layer_add( - layer: ArtLayer | LayerSet, - make_visible: bool = False -) -> None: +def select_layer_add(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None: """Add layer to currently selected and optionally force it to be visible. Args: @@ -406,14 +446,15 @@ def select_layer_add( """ desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putIdentifier(sID("layer"), layer.id) - desc1.putReference(sID("target"), ref1) + ref1.putIdentifier(APP.instance.sID("layer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) desc1.putEnumerated( - sID('selectionModifier'), - sID('selectionModifierType'), - sID('addToSelection')) - desc1.putBoolean(sID("makeVisible"), make_visible) - APP.executeAction(sID('select'), desc1, NO_DIALOG) + APP.instance.sID("selectionModifier"), + APP.instance.sID("selectionModifierType"), + APP.instance.sID("addToSelection"), + ) + desc1.putBoolean(APP.instance.sID("makeVisible"), make_visible) + APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: @@ -426,35 +467,39 @@ def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: if not layers: return if len(layers) == 1: - APP.activeDocument.activeLayer = layers[0] + APP.instance.activeDocument.activeLayer = layers[0] select_no_layers() # ID's and descriptors - idLayer = sID('layer') - idSelect = sID('select') - idTarget = sID('target') - idAddToSel = sID('addToSelection') - idSelMod = sID('selectionModifier') - idSelModType = sID('selectionModifierType') + idLayer = APP.instance.sID("layer") + idSelect = APP.instance.sID("select") + idTarget = APP.instance.sID("target") + idAddToSel = APP.instance.sID("addToSelection") + idSelMod = APP.instance.sID("selectionModifier") + idSelModType = APP.instance.sID("selectionModifierType") d1, r1 = ActionDescriptor(), ActionReference() # Select initial layer r1.putIdentifier(idLayer, layers[0].id) d1.putReference(idTarget, r1) d1.putEnumerated(idSelMod, idSelModType, idAddToSel) - d1.putBoolean(sID('makeVisible'), False) - APP.executeAction(idSelect, d1, NO_DIALOG) + d1.putBoolean(APP.instance.sID("makeVisible"), False) + APP.instance.executeAction(idSelect, d1, NO_DIALOG) # Select each additional layer for lay in layers[1:]: r1.putIdentifier(idLayer, lay.id) d1.putReference(idTarget, r1) - APP.executeAction(idSelect, d1, NO_DIALOG) + APP.instance.executeAction(idSelect, d1, NO_DIALOG) def select_no_layers() -> None: """Deselect all layers.""" d1, r1 = ActionDescriptor(), ActionReference() - r1.putEnumerated(sID("layer"), sID("ordinal"), sID("targetEnum")) - d1.putReference(sID("target"), r1) - APP.executeAction(sID("selectNoLayers"), d1, NO_DIALOG) + r1.putEnumerated( + APP.instance.sID("layer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + d1.putReference(APP.instance.sID("target"), r1) + APP.instance.executeAction(APP.instance.sID("selectNoLayers"), d1, NO_DIALOG) diff --git a/src/helpers/masks.py b/src/helpers/masks.py index de2fc712..3a7c1572 100644 --- a/src/helpers/masks.py +++ b/src/helpers/masks.py @@ -1,11 +1,12 @@ """ * Helpers: Masks """ + # Standard Library Imports from _ctypes import COMError # Third Party Imports -from photoshop.api import DialogModes, ActionDescriptor, ActionReference +from photoshop.api import ActionDescriptor, ActionReference, DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -14,7 +15,6 @@ from src.helpers.selection import select_canvas # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -23,8 +23,7 @@ def copy_layer_mask( - layer_from: ArtLayer | LayerSet, - layer_to: ArtLayer | LayerSet + layer_from: ArtLayer | LayerSet, layer_to: ArtLayer | LayerSet ) -> None: """Copies mask from one layer to another. @@ -35,19 +34,26 @@ def copy_layer_mask( desc1 = ActionDescriptor() ref17 = ActionReference() ref18 = ActionReference() - desc1.putClass(sID("new"), sID("channel")) - ref17.putEnumerated(sID("channel"), sID("channel"), sID("mask")) - ref17.putIdentifier(sID("layer"), layer_to.id) - desc1.putReference(sID("at"), ref17) - ref18.putEnumerated(sID("channel"), sID("channel"), sID("mask")) - ref18.putIdentifier(sID("layer"), layer_from.id) - desc1.putReference(sID("using"), ref18) - APP.executeAction(sID("make"), desc1, NO_DIALOG) + desc1.putClass(APP.instance.sID("new"), APP.instance.sID("channel")) + ref17.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + ref17.putIdentifier(APP.instance.sID("layer"), layer_to.id) + desc1.putReference(APP.instance.sID("at"), ref17) + ref18.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + ref18.putIdentifier(APP.instance.sID("layer"), layer_from.id) + desc1.putReference(APP.instance.sID("using"), ref18) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) def copy_vector_mask( - layer_from: ArtLayer | LayerSet, - layer_to: ArtLayer | LayerSet + layer_from: ArtLayer | LayerSet, layer_to: ArtLayer | LayerSet ) -> None: """Copies vector mask from one layer to another. @@ -59,15 +65,23 @@ def copy_vector_mask( ref1 = ActionReference() ref2 = ActionReference() ref3 = ActionReference() - ref1.putClass(sID("path")) - desc1.putReference(sID("target"), ref1) - ref2.putEnumerated(sID("path"), sID("path"), sID("vectorMask")) - ref2.putIdentifier(sID("layer"), layer_to.id) - desc1.putReference(sID("at"), ref2) - ref3.putEnumerated(sID("path"), sID("path"), sID("vectorMask")) - ref3.putIdentifier(sID("layer"), layer_from.id) - desc1.putReference(sID("using"), ref3) - APP.executeAction(sID("make"), desc1, NO_DIALOG) + ref1.putClass(APP.instance.sID("path")) + desc1.putReference(APP.instance.sID("target"), ref1) + ref2.putEnumerated( + APP.instance.sID("path"), + APP.instance.sID("path"), + APP.instance.sID("vectorMask"), + ) + ref2.putIdentifier(APP.instance.sID("layer"), layer_to.id) + desc1.putReference(APP.instance.sID("at"), ref2) + ref3.putEnumerated( + APP.instance.sID("path"), + APP.instance.sID("path"), + APP.instance.sID("vectorMask"), + ) + ref3.putIdentifier(APP.instance.sID("layer"), layer_from.id) + desc1.putReference(APP.instance.sID("using"), ref3) + APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) """ @@ -82,21 +96,20 @@ def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: layer: ArtLayer or LayerSet object. """ if not layer: - layer = APP.activeDocument.activeLayer + layer = APP.instance.activeDocument.activeLayer ref = ActionReference() - ref.putIdentifier(sID("layer"), layer.id) - desc = APP.executeActionGet(ref) - layer_fx = desc.getObjectValue(sID('layerEffects')) - layer_fx.putBoolean(sID("layerMaskAsGlobalMask"), True) + ref.putIdentifier(APP.instance.sID("layer"), layer.id) + desc = APP.instance.executeActionGet(ref) + layer_fx = desc.getObjectValue(APP.instance.sID("layerEffects")) + layer_fx.putBoolean(APP.instance.sID("layerMaskAsGlobalMask"), True) desc = ActionDescriptor() - desc.putReference(sID("target"), ref) - desc.putObject(sID("to"), sID("layer"), layer_fx) - APP.executeAction(sID("set"), desc, NO_DIALOG) + desc.putReference(APP.instance.sID("target"), ref) + desc.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), layer_fx) + APP.instance.executeAction(APP.instance.sID("set"), desc, NO_DIALOG) def set_layer_mask( - layer: ArtLayer | LayerSet | None = None, - visible: bool = True + layer: ArtLayer | LayerSet | None = None, visible: bool = True ) -> None: """Set the visibility of a layer's mask. @@ -105,15 +118,15 @@ def set_layer_mask( visible: Whether to make the layer mask visible. """ if not layer: - layer = APP.activeDocument.activeLayer + layer = APP.instance.activeDocument.activeLayer desc1 = ActionDescriptor() desc2 = ActionDescriptor() ref1 = ActionReference() - ref1.putIdentifier(cID("Lyr "), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putBoolean(cID("UsrM"), visible) - desc1.putObject(cID("T "), cID("Lyr "), desc2) - APP.executeAction(cID("setd"), desc1, NO_DIALOG) + ref1.putIdentifier(APP.instance.cID("Lyr "), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putBoolean(APP.instance.cID("UsrM"), visible) + desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) + APP.instance.executeAction(APP.instance.cID("setd"), desc1, NO_DIALOG) def enable_mask(layer: ArtLayer | LayerSet | None = None) -> None: @@ -141,18 +154,21 @@ def apply_mask(layer: ArtLayer | LayerSet | None = None) -> None: layer: ArtLayer or LayerSet object, use active layer if not provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putEnumerated(sID("channel"), sID("channel"), sID("mask")) - desc1.putReference(sID("target"), ref1) - desc1.putBoolean(sID("apply"), True) - APP.executeAction(sID("delete"), desc1, NO_DIALOG) + ref1.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + desc1.putBoolean(APP.instance.sID("apply"), True) + APP.instance.executeAction(APP.instance.sID("delete"), desc1, NO_DIALOG) def set_layer_vector_mask( - layer: ArtLayer | LayerSet | None = None, - visible: bool = False + layer: ArtLayer | LayerSet | None = None, visible: bool = False ) -> None: """Set the visibility of a layer's vector mask. @@ -161,15 +177,15 @@ def set_layer_vector_mask( visible: Whether to make the vector mask visible. """ if not layer: - layer = APP.activeDocument.activeLayer + layer = APP.instance.activeDocument.activeLayer desc1 = ActionDescriptor() desc2 = ActionDescriptor() ref1 = ActionReference() - ref1.putIdentifier(sID("layer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putBoolean(sID("vectorMaskEnabled"), visible) - desc1.putObject(sID("to"), sID("layer"), desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + ref1.putIdentifier(APP.instance.sID("layer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putBoolean(APP.instance.sID("vectorMaskEnabled"), visible) + desc1.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def enable_vector_mask(layer: ArtLayer | LayerSet | None = None) -> None: @@ -202,13 +218,17 @@ def enter_mask_channel(layer: ArtLayer | LayerSet | None = None): layer: Layer to make active, if provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - r1.putEnumerated(sID("channel"), sID("channel"), sID("mask")) - d1.putReference(sID("target"), r1) - d1.putBoolean(sID("makeVisible"), True) - APP.executeAction(sID("select"), d1, NO_DIALOG) + r1.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + d1.putReference(APP.instance.sID("target"), r1) + d1.putBoolean(APP.instance.sID("makeVisible"), True) + APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): @@ -218,13 +238,17 @@ def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): layer: Layer to make active, if provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - r1.putEnumerated(sID("channel"), sID("channel"), sID("RGB")) - d1.putReference(sID("target"), r1) - d1.putBoolean(sID("makeVisible"), True) - APP.executeAction(sID("select"), d1, NO_DIALOG) + r1.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("RGB"), + ) + d1.putReference(APP.instance.sID("target"), r1) + d1.putBoolean(APP.instance.sID("makeVisible"), True) + APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) def create_mask(layer: ArtLayer | LayerSet | None = None): @@ -234,14 +258,22 @@ def create_mask(layer: ArtLayer | LayerSet | None = None): layer: Layer to make active, if provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer d1 = ActionDescriptor() r1 = ActionReference() - d1.putClass(sID("new"), sID("channel")) - r1.putEnumerated(sID("channel"), sID("channel"), sID("mask")) - d1.putReference(sID("at"), r1) - d1.putEnumerated(sID("using"), sID("userMaskEnabled"), sID("revealAll")) - APP.executeAction(sID("make"), d1, NO_DIALOG) + d1.putClass(APP.instance.sID("new"), APP.instance.sID("channel")) + r1.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + d1.putReference(APP.instance.sID("at"), r1) + d1.putEnumerated( + APP.instance.sID("using"), + APP.instance.sID("userMaskEnabled"), + APP.instance.sID("revealAll"), + ) + APP.instance.executeAction(APP.instance.sID("make"), d1, NO_DIALOG) def copy_to_mask( @@ -257,7 +289,7 @@ def copy_to_mask( """ # Select canvas and copy - docref = APP.activeDocument + docref = APP.instance.activeDocument if source: docref.activeLayer = source docsel = docref.selection @@ -289,9 +321,13 @@ def delete_mask(layer: ArtLayer | LayerSet | None = None) -> None: layer: ArtLayer ore LayerSet object, use active layer if not provided. """ if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer desc1 = ActionDescriptor() ref1 = ActionReference() - ref1.putEnumerated(sID("channel"), sID("ordinal"), sID("targetEnum")) - desc1.putReference(sID("target"), ref1) - APP.executeAction(sID("delete"), desc1, NO_DIALOG) + ref1.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + APP.instance.executeAction(APP.instance.sID("delete"), desc1, NO_DIALOG) diff --git a/src/helpers/position.py b/src/helpers/position.py index 089d7bf0..4ecffbeb 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -1,36 +1,38 @@ """ * Helpers: Positioning """ + # Standard Library Imports import math -from typing import Literal from collections.abc import Sequence +from typing import Literal # Third Party Imports -from photoshop.api import DialogModes, AnchorPosition +from photoshop.api import AnchorPosition, DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document -from photoshop.api._selection import Selection from photoshop.api._layerSet import LayerSet +from photoshop.api._selection import Selection # Local Imports from src import APP from src.enums.adobe import Dimensions from src.helpers.bounds import ( - get_layer_dimensions, - get_dimensions_from_bounds, LayerDimensions, + get_dimensions_from_bounds, + get_layer_dimensions, + get_layer_height, get_layer_width, - get_layer_height) +) from src.helpers.selection import ( - select_overlapping, check_selection_bounds, - select_bounds) + select_bounds, + select_overlapping, +) from src.helpers.text import get_font_size, set_text_size_and_leading from src.utils.adobe import ReferenceLayer # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs # Positioning @@ -41,7 +43,10 @@ * Alignment Funcs """ -DimensionNames = Literal["width", "height", "center_x", "center_y", "left", "right", "top", "bottom"] +DimensionNames = Literal[ + "width", "height", "center_x", "center_y", "left", "right", "top", "bottom" +] + def align( axis: DimensionNames | Sequence[DimensionNames] | None = None, @@ -61,11 +66,17 @@ def align( x, y = 0, 0 # Get the dimensions of layer and reference if not provided - layer = layer or APP.activeDocument.activeLayer + layer = layer or APP.instance.activeDocument.activeLayer item = get_layer_dimensions(layer) - area = ref if isinstance(ref, dict) else ( - get_dimensions_from_bounds(APP.activeDocument.selection.bounds) - if not ref else get_layer_dimensions(ref)) + area = ( + ref + if isinstance(ref, dict) + else ( + get_dimensions_from_bounds(APP.instance.activeDocument.selection.bounds) + if not ref + else get_layer_dimensions(ref) + ) + ) # Single axis provided for n in axis: @@ -80,7 +91,7 @@ def align( def align_all( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing CenterX and CenterY to align function.""" align(["center_x", "center_y"], layer, ref) @@ -88,7 +99,7 @@ def align_all( def align_vertical( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing CenterY to align function.""" align("center_y", layer, ref) @@ -96,7 +107,7 @@ def align_vertical( def align_horizontal( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing CenterX to align function.""" align("center_x", layer, ref) @@ -104,7 +115,7 @@ def align_horizontal( def align_left( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing Left to align function.""" align("left", layer, ref) @@ -112,7 +123,7 @@ def align_left( def align_right( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing Right to align function.""" align("right", layer, ref) @@ -120,7 +131,7 @@ def align_right( def align_top( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing Top to align function.""" align("top", layer, ref) @@ -128,7 +139,7 @@ def align_top( def align_bottom( layer: ArtLayer | LayerSet | None = None, - ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None + ref: ArtLayer | LayerSet | ReferenceLayer | LayerDimensions | None = None, ) -> None: """Utility definition for passing Bottom to align function.""" align("bottom", layer, ref) @@ -143,7 +154,7 @@ def position_between_layers( layer: ArtLayer | LayerSet, top_layer: ArtLayer | LayerSet, bottom_layer: ArtLayer | LayerSet, - docref: Document | None = None + docref: Document | None = None, ) -> None: """Align layer vertically between two reference layers. @@ -153,7 +164,7 @@ def position_between_layers( bottom_layer: Reference layer below the layer to be aligned. docref: Document reference, use active if not provided. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument bounds = (0, top_layer.bounds[3], docref.width, bottom_layer.bounds[1]) align_vertical(layer, get_dimensions_from_bounds(bounds)) @@ -161,7 +172,7 @@ def position_between_layers( def position_dividers( dividers: Sequence[ArtLayer | LayerSet], layers: Sequence[ArtLayer | LayerSet], - docref: Document | None = None + docref: Document | None = None, ) -> None: """Positions a list of dividers between a list of layers. @@ -175,7 +186,8 @@ def position_dividers( layer=dividers[i], top_layer=layers[i], bottom_layer=layers[i + 1], - docref=docref) + docref=docref, + ) def spread_layers_over_reference( @@ -183,7 +195,7 @@ def spread_layers_over_reference( ref: ReferenceLayer, gap: float = 0, inside_gap: float = 0, - outside_matching: bool = True + outside_matching: bool = True, ) -> None: """Spread layers apart across a reference layer. @@ -195,13 +207,14 @@ def spread_layers_over_reference( outside_matching: If enabled, will enforce top and bottom gap to match. """ # Get reference dimensions if not provided - height = ref.dims['height'] + height = ref.dims["height"] # Calculate outside gap if not provided outside_gap = gap if not gap: total_space = height - sum( - [get_layer_dimensions(layer)['height'] for layer in layers]) + [get_layer_dimensions(layer)["height"] for layer in layers] + ) outside_gap = total_space / (len(layers) + 1) # Position the top layer relative to the reference @@ -214,7 +227,8 @@ def spread_layers_over_reference( ignored = 2 if outside_matching else 1 spaces = len(layers) - 1 if outside_matching else len(layers) total_space = height - sum( - [get_layer_dimensions(layer)['height'] for layer in layers]) + [get_layer_dimensions(layer)["height"] for layer in layers] + ) inside_gap = (total_space - (ignored * gap)) / spaces elif not gap: # Use the outside gap uniformly @@ -267,8 +281,9 @@ def frame_layer( # Scale the layer to fit either the largest, or the smallest dimension action = min if smallest else max scale = scale * action( - (ref_dim['width'] / layer_dim['width']), - (ref_dim['height'] / layer_dim['height'])) + (ref_dim["width"] / layer_dim["width"]), + (ref_dim["height"] / layer_dim["height"]), + ) layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical @@ -295,7 +310,7 @@ def frame_layer_by_height( ref_dim = ref if isinstance(ref, dict) else get_layer_dimensions(ref) # Scale the layer to fit the height of the reference - scale = scale * (ref_dim['height'] / get_layer_height(layer)) + scale = scale * (ref_dim["height"] / get_layer_height(layer)) layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical @@ -322,7 +337,7 @@ def frame_layer_by_width( ref_dim = ref if isinstance(ref, dict) else get_layer_dimensions(ref) # Scale the layer to fit the height of the reference - scale = scale * (ref_dim['width'] / get_layer_width(layer)) + scale = scale * (ref_dim["width"] / get_layer_width(layer)) layer.resize(scale, scale, anchor) # Default alignments are center horizontal and vertical @@ -336,8 +351,8 @@ def frame_layer_by_width( def check_reference_overlap( layer: ArtLayer, - ref_bounds: tuple[float,float,float,float], - docsel: Selection | None = None + ref_bounds: tuple[float, float, float, float], + docsel: Selection | None = None, ): """Checks if a layer is overlapping with given set of bounds. @@ -349,7 +364,7 @@ def check_reference_overlap( Returns: Bounds if overlap exists, otherwise None. """ - selection = docsel or APP.activeDocument.selection + selection = docsel or APP.instance.activeDocument.selection select_bounds(ref_bounds, selection=selection) select_overlapping(layer) if bounds := check_selection_bounds(selection): @@ -359,9 +374,7 @@ def check_reference_overlap( def clear_reference_vertical( - layer: ArtLayer, - ref: ReferenceLayer, - docsel: Selection | None = None + layer: ArtLayer, ref: ReferenceLayer, docsel: Selection | None = None ) -> int | float: """Nudges a layer clear vertically of a given reference layer or area. @@ -374,7 +387,7 @@ def clear_reference_vertical( The number of pixels layer was translated by (negative or positive indicating direction). """ # Use active layer if not provided - docsel = docsel or APP.activeDocument.selection + docsel = docsel or APP.instance.activeDocument.selection delta = check_reference_overlap(layer=layer, ref_bounds=ref.bounds, docsel=docsel) # Check if selection is empty, if not translate our layer to clear the reference @@ -393,7 +406,7 @@ def clear_reference_vertical_multi( font_size: float | None = None, step: float = 0.2, docref: Document | None = None, - docsel: Selection | None = None + docsel: Selection | None = None, ) -> None: """Shift or resize multiple text layers to prevent vertical collision with a reference area. @@ -419,21 +432,24 @@ def clear_reference_vertical_multi( if font_size is None: font_size = get_font_size(text_layers[0]) layers = text_layers.copy() - movable = len(layers)-1 + movable = len(layers) - 1 # Calculate inside gap - total_space = ref.dims['height'] - sum([get_layer_height(layer) for layer in text_layers]) + total_space = ref.dims["height"] - sum( + [get_layer_height(layer) for layer in text_layers] + ) if not uniform_gap: - inside_gap = ((total_space - space) - (ref.bounds[3] - layers[-1].bounds[1])) / movable + inside_gap = ( + (total_space - space) - (ref.bounds[3] - layers[-1].bounds[1]) + ) / movable else: inside_gap = total_space / (len(layers) + 1) leftover = (inside_gap - space) * movable # Does the bottom layer overlap with the loyalty box? delta = check_reference_overlap( - layer=layers[-1], - ref_bounds=loyalty_ref.bounds, - docsel=docsel) + layer=layers[-1], ref_bounds=loyalty_ref.bounds, docsel=docsel + ) if delta >= 0: return @@ -441,30 +457,28 @@ def clear_reference_vertical_multi( total_move = 0 layers.pop(0) for n, lyr in enumerate(layers): - total_move += math.fabs(delta) * ((len(layers) - n)/len(layers)) + total_move += math.fabs(delta) * ((len(layers) - n) / len(layers)) # Text layers can just be shifted upwards if total_move < leftover: layers.reverse() for n, lyr in enumerate(layers): - move_y = delta * ((len(layers) - n)/len(layers)) + move_y = delta * ((len(layers) - n) / len(layers)) lyr.translate(0, move_y) return # Layer gap would be too small, need to resize text then shift upward font_size -= step for lyr in text_layers: - set_text_size_and_leading( - layer=lyr, - size=font_size, - leading=font_size) + set_text_size_and_leading(layer=lyr, size=font_size, leading=font_size) # Space apart planeswalker text evenly spread_layers_over_reference( layers=text_layers, ref=ref, gap=space if not uniform_gap else 0, - outside_matching=False) + outside_matching=False, + ) # Check for another iteration clear_reference_vertical_multi( @@ -475,4 +489,5 @@ def clear_reference_vertical_multi( uniform_gap=uniform_gap, font_size=font_size, docref=docref, - docsel=docsel) + docsel=docsel, + ) diff --git a/src/helpers/selection.py b/src/helpers/selection.py index 24d1f58d..46e0435f 100644 --- a/src/helpers/selection.py +++ b/src/helpers/selection.py @@ -1,6 +1,7 @@ """ * Helpers: Selection """ + # Standard Library Imports from contextlib import suppress @@ -9,18 +10,13 @@ from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection -from photoshop.api import ( - ActionDescriptor, - ActionReference, - DialogModes, - LayerKind) +from photoshop.api import ActionDescriptor, ActionReference, DialogModes, LayerKind # Local Imports from src import APP from src.utils.adobe import PS_EXCEPTIONS # Photoshop infrastructure -cID, sID = APP.charIDToTypeID, APP.stringIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -29,8 +25,7 @@ def select_bounds( - bounds: tuple[float,float,float,float], - selection: Selection | None = None + bounds: tuple[float, float, float, float], selection: Selection | None = None ) -> None: """Create a selection using a list of bound values. @@ -38,16 +33,14 @@ def select_bounds( bounds: List of bound values (left, top, right, bottom). selection: App selection object, pull from active document if not provided. """ - selection = selection or APP.activeDocument.selection + selection = selection or APP.instance.activeDocument.selection left, top, right, bottom = bounds - selection.select( - ((left, top), - (right, top), - (right, bottom), - (left, bottom))) + selection.select(((left, top), (right, top), (right, bottom), (left, bottom))) -def select_layer_bounds(layer: ArtLayer | LayerSet | None = None, selection: Selection | None = None) -> None: +def select_layer_bounds( + layer: ArtLayer | LayerSet | None = None, selection: Selection | None = None +) -> None: """Select the bounding box of a given layer. Args: @@ -55,7 +48,7 @@ def select_layer_bounds(layer: ArtLayer | LayerSet | None = None, selection: Sel selection: App selection object, pull from active document if not provided. """ if not layer: - layer = APP.activeDocument.activeLayer + layer = APP.instance.activeDocument.activeLayer select_bounds(layer.bounds, selection) @@ -66,14 +59,16 @@ def select_overlapping(layer: ArtLayer) -> None: layer: Layer with pixels to select. """ with suppress(*PS_EXCEPTIONS): - idChannel = sID('channel') + idChannel = APP.instance.sID("channel") desc1, ref1, ref2 = ActionDescriptor(), ActionReference(), ActionReference() - ref1.putEnumerated(idChannel, idChannel, sID("transparencyEnum")) - ref1.putIdentifier(sID("layer"), layer.id) - desc1.putReference(sID("target"), ref1) - ref2.putProperty(idChannel, sID("selection")) - desc1.putReference(sID("with"), ref2) - APP.executeAction(sID("interfaceIconFrameDimmed"), desc1, NO_DIALOG) + ref1.putEnumerated(idChannel, idChannel, APP.instance.sID("transparencyEnum")) + ref1.putIdentifier(APP.instance.sID("layer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + ref2.putProperty(idChannel, APP.instance.sID("selection")) + desc1.putReference(APP.instance.sID("with"), ref2) + APP.instance.executeAction( + APP.instance.sID("interfaceIconFrameDimmed"), desc1, NO_DIALOG + ) def select_canvas(docref: Document | None = None, bleed: int = 0): @@ -83,12 +78,14 @@ def select_canvas(docref: Document | None = None, bleed: int = 0): docref: Document reference, use active if not provided. bleed: Amount of bleed edge to leave around selection, defaults to 0. """ - docref = docref or APP.activeDocument + docref = docref or APP.instance.activeDocument docref.selection.select( - ((0 + bleed, 0 + bleed), - (docref.width - bleed, 0 + bleed), - (docref.width - bleed, docref.height - bleed), - (0 + bleed, docref.height - bleed)) + ( + (0 + bleed, 0 + bleed), + (docref.width - bleed, 0 + bleed), + (docref.width - bleed, docref.height - bleed), + (0 + bleed, docref.height - bleed), + ) ) @@ -108,13 +105,17 @@ def select_layer_pixels(layer: ArtLayer | None = None) -> None: des1 = ActionDescriptor() ref1 = ActionReference() ref2 = ActionReference() - ref1.putProperty(sID("channel"), sID("selection")) - des1.putReference(sID("target"), ref1) - ref2.putEnumerated(sID("channel"), sID("channel"), sID("transparencyEnum")) + ref1.putProperty(APP.instance.sID("channel"), APP.instance.sID("selection")) + des1.putReference(APP.instance.sID("target"), ref1) + ref2.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("transparencyEnum"), + ) if layer: - ref2.putIdentifier(sID("layer"), layer.id) - des1.putReference(sID("to"), ref2) - APP.executeAction(sID("set"), des1, NO_DIALOG) + ref2.putIdentifier(APP.instance.sID("layer"), layer.id) + des1.putReference(APP.instance.sID("to"), ref2) + APP.instance.executeAction(APP.instance.sID("set"), des1, NO_DIALOG) def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: @@ -126,15 +127,19 @@ def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() ref2 = ActionReference() - ref1.putProperty(sID("channel"), sID("selection")) - desc1.putReference(sID("target"), ref1) - ref2.putEnumerated(sID("path"), sID("path"), sID("vectorMask")) + ref1.putProperty(APP.instance.sID("channel"), APP.instance.sID("selection")) + desc1.putReference(APP.instance.sID("target"), ref1) + ref2.putEnumerated( + APP.instance.sID("path"), + APP.instance.sID("path"), + APP.instance.sID("vectorMask"), + ) if layer: - ref2.putIdentifier(sID("layer"), layer.id) - desc1.putReference(sID("to"), ref2) - desc1.putInteger(sID("version"), 1) - desc1.putBoolean(sID("vectorMaskParams"), True) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + ref2.putIdentifier(APP.instance.sID("layer"), layer.id) + desc1.putReference(APP.instance.sID("to"), ref2) + desc1.putInteger(APP.instance.sID("version"), 1) + desc1.putBoolean(APP.instance.sID("vectorMaskParams"), True) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) """ @@ -142,7 +147,9 @@ def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: """ -def check_selection_bounds(selection: Selection | None = None) -> tuple[float, float, float, float] | None: +def check_selection_bounds( + selection: Selection | None = None, +) -> tuple[float, float, float, float] | None: """Verifies if a selection has valid bounds. Args: @@ -151,7 +158,7 @@ def check_selection_bounds(selection: Selection | None = None) -> tuple[float, f Returns: An empty list if selection is invalid, otherwise return bounds of selection. """ - selection = selection or APP.activeDocument.selection + selection = selection or APP.instance.activeDocument.selection with suppress(*PS_EXCEPTIONS): if selection.bounds != (0, 0, 0, 0): return selection.bounds diff --git a/src/helpers/text.py b/src/helpers/text.py index cbd692ca..b59aadb5 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -1,6 +1,7 @@ """ * Helpers: Text Items """ + # Standard Library Imports from typing import Literal, overload from collections.abc import Sequence @@ -11,7 +12,8 @@ ActionDescriptor, ActionReference, ActionList, - LayerKind) + LayerKind, +) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -23,12 +25,12 @@ get_layer_height, get_layer_width, get_textbox_width, - get_width_no_effects) + get_width_no_effects, +) from src.helpers.document import pixels_to_points from src.utils.adobe import PS_EXCEPTIONS # QOL Definitions -sID, cID = APP.stringIDToTypeID, APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -52,9 +54,9 @@ def get_text_key(layer: ArtLayer) -> ActionDescriptor: layer: ArtLayer which must be a TextLayer kind. """ reference = ActionReference() - reference.putIdentifier(sID('layer'), layer.id) - descriptor = APP.executeActionGet(reference) - return descriptor.getObjectValue(sID('textKey')) + reference.putIdentifier(APP.instance.sID("layer"), layer.id) + descriptor = APP.instance.executeActionGet(reference) + return descriptor.getObjectValue(APP.instance.sID("textKey")) def apply_text_key(text_layer: ArtLayer, text_key: ActionDescriptor) -> None: @@ -65,10 +67,10 @@ def apply_text_key(text_layer: ArtLayer, text_key: ActionDescriptor) -> None: text_key: TextKey extracted from a TextLayer that has been modified. """ action, ref = ActionDescriptor(), ActionReference() - ref.putIdentifier(sID("layer"), text_layer.id) - action.putReference(sID("target"), ref) - action.putObject(sID("to"), sID("textLayer"), text_key) - APP.executeAction(sID("set"), action, NO_DIALOG) + ref.putIdentifier(APP.instance.sID("layer"), text_layer.id) + action.putReference(APP.instance.sID("target"), ref) + action.putObject(APP.instance.sID("to"), APP.instance.sID("textLayer"), text_key) + APP.instance.executeAction(APP.instance.sID("set"), action, NO_DIALOG) def get_line_count(layer: ArtLayer, docref: Document | None = None) -> int: @@ -81,11 +83,11 @@ def get_line_count(layer: ArtLayer, docref: Document | None = None) -> int: Returns: Number of lines in the TextItem. """ - docref = docref or APP.activeDocument - return round(pixels_to_points( - number=get_layer_height(layer), - docref=docref - ) / layer.textItem.leading) + docref = docref or APP.instance.activeDocument + return round( + pixels_to_points(number=get_layer_height(layer), docref=docref) + / layer.textItem.leading + ) """ @@ -102,20 +104,22 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: replace: Text to replace the found text with. """ # Establish our text key and reference text - idTextKey = sID("textKey") + idTextKey = APP.instance.sID("textKey") text_key: ActionDescriptor = get_text_key(layer) current_text = text_key.getString(idTextKey) # Check if our target text exists if find not in current_text: - print(f"Text replacement couldn't find the text '{find}' " - f"in layer with name '{layer.name}'!") + print( + f"Text replacement couldn't find the text '{find}' " + f"in layer with name '{layer.name}'!" + ) return # Reusable ID's - idTo = sID('to') - idFrom = sID('from') - idTextStyleRange = sID('textStyleRange') + idTo = APP.instance.sID("to") + idFrom = APP.instance.sID("from") + idTextStyleRange = APP.instance.sID("textStyleRange") # Track length difference and whether replacement was made offset = len(replace) - len(find) @@ -127,7 +131,7 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: style = style_range.getObjectValue(i) idxFrom = style.getInteger(idFrom) idxTo = style.getInteger(idTo) - if not replaced and find in current_text[idxFrom: idxTo]: + if not replaced and find in current_text[idxFrom:idxTo]: # Found the text to replace replaced = True style.putInteger(idTo, idxTo + offset) @@ -140,8 +144,10 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: # Skip applying changes if no replacement could be made if not replaced: - print(f"Text replacement couldn't find the text '{find}' " - f"in layer with name '{layer.name}'!") + print( + f"Text replacement couldn't find the text '{find}' " + f"in layer with name '{layer.name}'!" + ) return # Apply changes @@ -154,7 +160,7 @@ def replace_text_legacy( find: str, replace: str, layer: ArtLayer | None = None, - targeted_replace: bool = True + targeted_replace: bool = True, ) -> None: """Replace all instances of `replace_this` in the specified layer with `replace_with`, using Photoshop's built-in search and replace feature. Slower than `replace_text`, but can handle strings broken into @@ -168,35 +174,41 @@ def replace_text_legacy( """ # Set the active layer if layer: - APP.activeDocument.activeLayer = layer + APP.instance.activeDocument.activeLayer = layer # Find and replace - idFindReplace = sID("findReplace") + idFindReplace = APP.instance.sID("findReplace") desc31 = ActionDescriptor() ref3 = ActionReference() desc32 = ActionDescriptor() - ref3.putProperty(sID("property"), idFindReplace) - ref3.putEnumerated(sID("textLayer"), sID("ordinal"), sID("targetEnum")) - desc31.putReference(sID("target"), ref3) - desc32.putString(sID("find"), f"""{find}""") - desc32.putString(sID("replace"), f"""{replace}""") + ref3.putProperty(APP.instance.sID("property"), idFindReplace) + ref3.putEnumerated( + APP.instance.sID("textLayer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc31.putReference(APP.instance.sID("target"), ref3) + desc32.putString(APP.instance.sID("find"), f"""{find}""") + desc32.putString(APP.instance.sID("replace"), f"""{replace}""") desc32.putBoolean( - sID("checkAll"), # Targeted replace doesn't work on old PS versions - False if targeted_replace and APP.supports_target_text_replace() else True + APP.instance.sID( + "checkAll" + ), # Targeted replace doesn't work on old PS versions + False + if targeted_replace and APP.instance.supports_target_text_replace() + else True, ) - desc32.putBoolean(sID("forward"), True) - desc32.putBoolean(sID("caseSensitive"), True) - desc32.putBoolean(sID("wholeWord"), False) - desc32.putBoolean(sID("ignoreAccents"), True) - desc31.putObject(sID("using"), idFindReplace, desc32) + desc32.putBoolean(APP.instance.sID("forward"), True) + desc32.putBoolean(APP.instance.sID("caseSensitive"), True) + desc32.putBoolean(APP.instance.sID("wholeWord"), False) + desc32.putBoolean(APP.instance.sID("ignoreAccents"), True) + desc31.putObject(APP.instance.sID("using"), idFindReplace, desc32) try: - APP.executeAction(idFindReplace, desc31, NO_DIALOG) + APP.instance.executeAction(idFindReplace, desc31, NO_DIALOG) except PS_EXCEPTIONS: replace_text_legacy( - find=find, - replace=replace, - layer=layer, - targeted_replace=False) + find=find, replace=replace, layer=layer, targeted_replace=False + ) def remove_trailing_text(layer: ArtLayer, idx: int) -> None: @@ -207,23 +219,24 @@ def remove_trailing_text(layer: ArtLayer, idx: int) -> None: idx: Index to remove after. """ # Action Descriptor ID's - idTextKey = sID("textKey") - idFrom = sID("from") - idTo = sID("to") + idTextKey = APP.instance.sID("textKey") + idFrom = APP.instance.sID("from") + idTo = APP.instance.sID("to") # Establish our text key and descriptor ID's key: ActionDescriptor = get_text_key(layer) current_text = key.getString(idTextKey) - new_text = current_text[0:idx - 1] + new_text = current_text[0 : idx - 1] # Find the range where target text exists - for n in [sID("textStyleRange"), sID("paragraphStyleRange")]: - + for n in [ + APP.instance.sID("textStyleRange"), + APP.instance.sID("paragraphStyleRange"), + ]: # Iterate over list of style ranges style_ranges: list[ActionDescriptor] = [] text_range = key.getList(n) for i in range(text_range.count): - # Get position of this style range style = text_range.getObjectValue(i) i_left = style.getInteger(idFrom) @@ -254,24 +267,25 @@ def remove_leading_text(layer: ArtLayer, idx: int) -> None: idx: Index to remove up to. """ # Action Descriptor ID's - idTextKey = sID("textKey") - idFrom = sID("from") - idTo = sID("to") + idTextKey = APP.instance.sID("textKey") + idFrom = APP.instance.sID("from") + idTo = APP.instance.sID("to") # Establish our text key and descriptor ID's key: ActionDescriptor = get_text_key(layer) current_text = key.getString(idTextKey) - new_text = current_text[idx + 1:] + new_text = current_text[idx + 1 :] offset = idx + 1 # Find the range where target text exists - for n in [sID("textStyleRange"), sID("paragraphStyleRange")]: - + for n in [ + APP.instance.sID("textStyleRange"), + APP.instance.sID("paragraphStyleRange"), + ]: # Iterate over list of style ranges style_ranges: list[ActionDescriptor] = [] text_range = key.getList(n) for i in range(text_range.count): - # Get position of this style range style = text_range.getObjectValue(i) i_left = style.getInteger(idFrom) @@ -305,24 +319,23 @@ def remove_leading_text(layer: ArtLayer, idx: int) -> None: ScaleAxis = Literal["xx", "yy"] + @overload def get_text_scale_factor( - layer: ArtLayer, - axis: list[ScaleAxis], - text_key: ActionDescriptor | None=None + layer: ArtLayer, axis: list[ScaleAxis], text_key: ActionDescriptor | None = None ) -> list[float]: ... + @overload def get_text_scale_factor( - layer: ArtLayer, - axis: ScaleAxis = 'yy', - text_key: ActionDescriptor | None=None + layer: ArtLayer, axis: ScaleAxis = "yy", text_key: ActionDescriptor | None = None ) -> float: ... + def get_text_scale_factor( layer: ArtLayer, - axis: ScaleAxis | list[ScaleAxis] = 'yy', - text_key: ActionDescriptor | None=None + axis: ScaleAxis | list[ScaleAxis] = "yy", + text_key: ActionDescriptor | None = None, ) -> float | list[float]: """Get the scale factor of the document for changing text size. @@ -335,31 +348,33 @@ def get_text_scale_factor( Float scale factor """ - # Get the textKey if not provided if not text_key: # Get text key text_key = get_text_key(layer) - idTransform = sID('transform') + idTransform = APP.instance.sID("transform") # Check for the "transform" descriptor if text_key.hasKey(idTransform): transform = text_key.getObjectValue(idTransform) # Check list of axis if isinstance(axis, list): - return [transform.getUnitDoubleValue(sID(n)) for n in axis] + return [transform.getUnitDoubleValue(APP.instance.sID(n)) for n in axis] # String axis - return transform.getUnitDoubleValue(sID(axis)) + return transform.getUnitDoubleValue(APP.instance.sID(axis)) if isinstance(axis, list): return [1.0] * len(axis) return 1.0 + """ * Text Alignment """ -def align_text(action_list: ActionList, start: int, end: int, alignment: str = "right") -> ActionList: +def align_text( + action_list: ActionList, start: int, end: int, alignment: str = "right" +) -> ActionList: """Align a slice of text in an action using given alignment. Examples: @@ -376,13 +391,17 @@ def align_text(action_list: ActionList, start: int, end: int, alignment: str = " """ d1 = ActionDescriptor() d2 = ActionDescriptor() - paraStyle = sID("paragraphStyle") - d1.putInteger(sID("from"), start) - d1.putInteger(sID("to"), end) - d2.putBoolean(sID("styleSheetHasParent"), True) - d2.putEnumerated(sID("align"), sID("alignmentType"), sID(alignment)) + paraStyle = APP.instance.sID("paragraphStyle") + d1.putInteger(APP.instance.sID("from"), start) + d1.putInteger(APP.instance.sID("to"), end) + d2.putBoolean(APP.instance.sID("styleSheetHasParent"), True) + d2.putEnumerated( + APP.instance.sID("align"), + APP.instance.sID("alignmentType"), + APP.instance.sID(alignment), + ) d1.putObject(paraStyle, paraStyle, d2) - action_list.putObject(sID("paragraphStyleRange"), d1) + action_list.putObject(APP.instance.sID("paragraphStyleRange"), d1) return action_list @@ -415,13 +434,19 @@ def set_space_after(space: int | float) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() deesc2 = ActionDescriptor() - ref1.putProperty(sID("property"), sID("paragraphStyle")) - ref1.putEnumerated(sID("textLayer"), sID("ordinal"), sID("targetEnum")) - desc1.putReference(sID("target"), ref1) - deesc2.putInteger(sID("textOverrideFeatureName"), 808464438) - deesc2.putUnitDouble(sID("spaceAfter"), sID("pointsUnit"), space) - desc1.putObject(sID("to"), sID("paragraphStyle"), deesc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + ref1.putProperty(APP.instance.sID("property"), APP.instance.sID("paragraphStyle")) + ref1.putEnumerated( + APP.instance.sID("textLayer"), + APP.instance.sID("ordinal"), + APP.instance.sID("targetEnum"), + ) + desc1.putReference(APP.instance.sID("target"), ref1) + deesc2.putInteger(APP.instance.sID("textOverrideFeatureName"), 808464438) + deesc2.putUnitDouble( + APP.instance.sID("spaceAfter"), APP.instance.sID("pointsUnit"), space + ) + desc1.putObject(APP.instance.sID("to"), APP.instance.sID("paragraphStyle"), deesc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def set_text_leading(layer: ArtLayer, leading: float | int) -> None: @@ -434,12 +459,14 @@ def set_text_leading(layer: ArtLayer, leading: float | int) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - ref1.putProperty(sID("property"), sID("textStyle")) - ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putUnitDouble(sID("leading"), sID("pointsUnit"), leading) - desc1.putObject(sID("to"), sID("textStyle"), desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + ref1.putProperty(APP.instance.sID("property"), APP.instance.sID("textStyle")) + ref1.putIdentifier(APP.instance.sID("textLayer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putUnitDouble( + APP.instance.sID("leading"), APP.instance.sID("pointsUnit"), leading + ) + desc1.putObject(APP.instance.sID("to"), APP.instance.sID("textStyle"), desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def set_text_size(layer: ArtLayer, size: float | int) -> None: @@ -452,16 +479,18 @@ def set_text_size(layer: ArtLayer, size: float | int) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - textStyle = sID("textStyle") - ref1.putProperty(sID("property"), textStyle) - ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putUnitDouble(sID("size"), sID("pointsUnit"), size) - desc1.putObject(sID("to"), textStyle, desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + textStyle = APP.instance.sID("textStyle") + ref1.putProperty(APP.instance.sID("property"), textStyle) + ref1.putIdentifier(APP.instance.sID("textLayer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putUnitDouble(APP.instance.sID("size"), APP.instance.sID("pointsUnit"), size) + desc1.putObject(APP.instance.sID("to"), textStyle, desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) -def set_text_size_and_leading(layer: ArtLayer, size: int | float, leading: int | float) -> None: +def set_text_size_and_leading( + layer: ArtLayer, size: int | float, leading: int | float +) -> None: """Manually assign font size and leading space to a text layer using action descriptors. Args: @@ -472,15 +501,15 @@ def set_text_size_and_leading(layer: ArtLayer, size: int | float, leading: int | desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - textStyle = sID("textStyle") - ptUnit = sID("pointsUnit") - ref1.putProperty(sID("property"), textStyle) - ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putUnitDouble(sID("size"), ptUnit, size) - desc2.putUnitDouble(sID("leading"), ptUnit, leading) - desc1.putObject(sID("to"), textStyle, desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + textStyle = APP.instance.sID("textStyle") + ptUnit = APP.instance.sID("pointsUnit") + ref1.putProperty(APP.instance.sID("property"), textStyle) + ref1.putIdentifier(APP.instance.sID("textLayer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putUnitDouble(APP.instance.sID("size"), ptUnit, size) + desc2.putUnitDouble(APP.instance.sID("leading"), ptUnit, leading) + desc1.putObject(APP.instance.sID("to"), textStyle, desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def set_composer(layer: ArtLayer, every: bool = False) -> None: @@ -494,13 +523,13 @@ def set_composer(layer: ArtLayer, every: bool = False) -> None: desc1 = ActionDescriptor() ref1 = ActionReference() desc2 = ActionDescriptor() - paraStyle = sID("paragraphStyle") - ref1.putProperty(sID("property"), paraStyle) - ref1.putIdentifier(sID("textLayer"), layer.id) - desc1.putReference(sID("target"), ref1) - desc2.putBoolean(sID("textEveryLineComposer"), every) - desc1.putObject(sID("to"), paraStyle, desc2) - APP.executeAction(sID("set"), desc1, NO_DIALOG) + paraStyle = APP.instance.sID("paragraphStyle") + ref1.putProperty(APP.instance.sID("property"), paraStyle) + ref1.putIdentifier(APP.instance.sID("textLayer"), layer.id) + desc1.putReference(APP.instance.sID("target"), ref1) + desc2.putBoolean(APP.instance.sID("textEveryLineComposer"), every) + desc1.putObject(APP.instance.sID("to"), paraStyle, desc2) + APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) def set_composer_single_line(layer: ArtLayer) -> None: @@ -529,7 +558,7 @@ def set_font(layer: ArtLayer, font_name: str) -> None: layer: ArtLayer containing TextItem. font_name: Name of the font to set. """ - if font := APP.fonts.getByName(font_name): + if font := APP.instance.fonts.getByName(font_name): layer.textItem.font = font.postScriptName @@ -557,7 +586,9 @@ def ensure_visible_reference(reference: ArtLayer) -> TextItem | None: return None -def scale_text_right_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) -> None: +def scale_text_right_overlap( + layer: ArtLayer, reference: ArtLayer, gap: int = 30 +) -> None: """Scales a text layer down (in 0.2 pt increments) until its right bound has a 30 px~ (based on DPI) clearance from a reference layer's left bound. @@ -573,19 +604,18 @@ def scale_text_right_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30 # Set starting variables font_size = old_size = get_font_size(layer) - ref_left_bound = reference.bounds[0] - APP.scale_by_dpi(gap) + ref_left_bound = reference.bounds[0] - APP.instance.scale_by_dpi(gap) step, half_step = 0.4, 0.2 # Guard against reference being left of the layer if ref_left_bound < layer.bounds[0]: # Reset reference if ref_TI: - ref_TI.contents = '' + ref_TI.contents = "" return # Make our first check if scaling is necessary if continue_scaling := bool(layer.bounds[2] > ref_left_bound): - # Step down the font till it clears the reference while continue_scaling: font_size -= step @@ -607,10 +637,12 @@ def scale_text_right_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30 # Fix corrected reference layer if ref_TI: # Reset reference - ref_TI.contents = '' + ref_TI.contents = "" -def scale_text_left_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) -> None: +def scale_text_left_overlap( + layer: ArtLayer, reference: ArtLayer, gap: int = 30 +) -> None: """Scales a text layer down (in 0.2 pt increments) until its left bound has a 30 px~ (based on DPI) clearance from a reference layer's right bound. @@ -626,19 +658,18 @@ def scale_text_left_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) # Set starting variables font_size = old_size = get_font_size(layer) - ref_right_bound = reference.bounds[2] + APP.scale_by_dpi(gap) + ref_right_bound = reference.bounds[2] + APP.instance.scale_by_dpi(gap) step, half_step = 0.4, 0.2 # Guard against reference being right of the layer if layer.bounds[0] < reference.bounds[0]: # Reset reference if ref_TI: - ref_TI.contents = '' + ref_TI.contents = "" return # Make our first check if scaling is necessary if continue_scaling := bool(ref_right_bound > layer.bounds[0]): - # Step down the font till it clears the reference while continue_scaling: font_size -= step @@ -659,7 +690,7 @@ def scale_text_left_overlap(layer: ArtLayer, reference: ArtLayer, gap: int = 30) # Fix corrected reference layer if ref_TI: - ref_TI.contents = '' + ref_TI.contents = "" def scale_text_to_width( @@ -667,7 +698,7 @@ def scale_text_to_width( width: int | float, spacing: int = 64, step: float = 0.4, - font_size: float | None = None + font_size: float | None = None, ) -> float | None: """Resize a given text layer's font size/leading until it fits inside a reference width. @@ -682,7 +713,7 @@ def scale_text_to_width( Font size if font size is calculated during operation, otherwise None. """ # Cancel if we're already within expected bounds - width = width - APP.scale_by_dpi(spacing) + width = width - APP.instance.scale_by_dpi(spacing) continue_scaling = bool(width < get_layer_width(layer)) if not continue_scaling: return @@ -714,7 +745,7 @@ def scale_text_to_height( height: float, spacing: float = 64, step: float = 0.4, - font_size: float | None = None + font_size: float | None = None, ) -> float | None: """Resize a given text layer's font size/leading until it fits inside a reference width. @@ -729,7 +760,7 @@ def scale_text_to_height( Font size if font size is calculated during operation, otherwise None. """ # Cancel if we're already within expected bounds - height = height - APP.scale_by_dpi(spacing) + height = height - APP.instance.scale_by_dpi(spacing) continue_scaling = bool(height < get_layer_height(layer)) if not continue_scaling: return @@ -757,9 +788,7 @@ def scale_text_to_height( def scale_text_to_width_textbox( - layer: ArtLayer, - font_size: float | None = None, - step: float = 0.1 + layer: ArtLayer, font_size: float | None = None, step: float = 0.1 ) -> None: """Check if the text in a TextLayer exceeds its bounding box. @@ -783,7 +812,7 @@ def scale_text_layers_to_height( text_layers: list[ArtLayer], ref_height: int | float, font_size: float | None = None, - step_sizes: Sequence[float] | None = None + step_sizes: Sequence[float] | None = None, ) -> float | None: """Scale multiple text layers until they all can fit within the same given height dimension. @@ -808,9 +837,13 @@ def scale_text_layers_to_height( # Adjust text size down and up in decreasing steps for idx, step_size in enumerate(step_sizes): uneven_round = bool(idx % 2) - + # Compare height of all 3 elements vs total reference height - while total_layer_height < ref_height if uneven_round else total_layer_height > ref_height: + while ( + total_layer_height < ref_height + if uneven_round + else total_layer_height > ref_height + ): total_layer_height = 0 if uneven_round: diff --git a/src/schema/colors.py b/src/schema/colors.py index 3fd44aec..f4d898ad 100644 --- a/src/schema/colors.py +++ b/src/schema/colors.py @@ -101,8 +101,8 @@ class GradientColor(ArbitrarySchema): """Defines a color within a gradient.""" color: ColorObject = (0, 0, 0) - location: float = 0 - midpoint: float = 50 + location: int = 0 + midpoint: int = 50 class GradientConfig(TypedDict): diff --git a/src/templates/_core.py b/src/templates/_core.py index 5f1e5ceb..a5b6e422 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -4,12 +4,12 @@ # Standard Library Imports import os.path as osp +from collections.abc import Callable, Sequence from contextlib import suppress from functools import cached_property from pathlib import Path from threading import Event from typing import Any, Unpack -from collections.abc import Callable, Sequence from omnitils.files import get_unique_filename @@ -22,7 +22,6 @@ from photoshop.api._selection import Selection from PIL import Image -from src.gui.console import GUIConsole import src.helpers as psd # Local Imports @@ -39,8 +38,8 @@ WatermarkMode, ) from src.frame_logic import is_multicolor_string +from src.gui.console import GUIConsole from src.helpers.adjustments import CreateColorLayerKwargs -from src.schema.colors import GradientConfig, is_rgb_or_cmyk_tuple from src.helpers.effects import LayerEffects from src.helpers.position import DimensionNames from src.layouts import NormalLayout, SplitLayout @@ -48,7 +47,9 @@ from src.schema.colors import ( ColorObject, GradientColor, + GradientConfig, basic_watermark_color_map, + is_rgb_or_cmyk_tuple, watermark_color_map, ) from src.text_layers import ( @@ -215,7 +216,7 @@ def console(self) -> GUIConsole | TerminalConsole: @property def app(self) -> PhotoshopHandler: """PhotoshopHandler: Photoshop Application object used to communicate with Photoshop.""" - return APP + return APP.instance @cached_property def docref(self) -> Document: @@ -1586,7 +1587,7 @@ def execute(self) -> bool: return False if CFG.minimize_photoshop: - APP.set_window_state(WindowState.MINIMIZE) + APP.instance.set_window_state(WindowState.MINIMIZE) # Pre-process layout data if not self.run_tasks( diff --git a/src/text_layers.py b/src/text_layers.py index b397ceb8..3d5dbf17 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -53,8 +53,6 @@ from src.utils.adobe import ReferenceLayer # QOL Definitions -sID = APP.stringIDToTypeID -cID = APP.charIDToTypeID NO_DIALOG = DialogModes.DisplayNoDialogs """ @@ -169,7 +167,7 @@ def is_text_layer(self) -> bool: @cached_property def docref(self) -> Document: """The currently active Photoshop document.""" - return APP.activeDocument + return APP.instance.activeDocument @cached_property def doc_selection(self) -> Selection: @@ -578,31 +576,31 @@ def format_text(self): main_list = ActionList() # Descriptor ID's - idTo = sID("to") - size = sID("size") - idFrom = sID("from") - textLayer = sID("textLayer") - textStyle = sID("textStyle") - ptUnit = sID("pointsUnit") - spaceAfter = sID("spaceAfter") - autoLeading = sID("autoLeading") - startIndent = sID("startIndent") - spaceBefore = sID("spaceBefore") - leadingType = sID("leadingType") - styleRange = sID("textStyleRange") - paragraphStyle = sID("paragraphStyle") - firstLineIndent = sID("firstLineIndent") - fontPostScriptName = sID("fontPostScriptName") - paragraphStyleRange = sID("paragraphStyleRange") + idTo = APP.instance.sID("to") + size = APP.instance.sID("size") + idFrom = APP.instance.sID("from") + textLayer = APP.instance.sID("textLayer") + textStyle = APP.instance.sID("textStyle") + ptUnit = APP.instance.sID("pointsUnit") + spaceAfter = APP.instance.sID("spaceAfter") + autoLeading = APP.instance.sID("autoLeading") + startIndent = APP.instance.sID("startIndent") + spaceBefore = APP.instance.sID("spaceBefore") + leadingType = APP.instance.sID("leadingType") + styleRange = APP.instance.sID("textStyleRange") + paragraphStyle = APP.instance.sID("paragraphStyle") + firstLineIndent = APP.instance.sID("firstLineIndent") + fontPostScriptName = APP.instance.sID("fontPostScriptName") + paragraphStyleRange = APP.instance.sID("paragraphStyleRange") # Spin up the text insertion action - main_desc.putString(sID("textKey"), self.input) + main_desc.putString(APP.instance.sID("textKey"), self.input) main_range.putInteger(idFrom, 0) main_range.putInteger(idTo, len(self.input)) apply_color(main_style, self.color) main_style.putBoolean(autoLeading, False) main_style.putUnitDouble(size, ptUnit, self.font_size) - main_style.putUnitDouble(sID("leading"), ptUnit, self.font_size) + main_style.putUnitDouble(APP.instance.sID("leading"), ptUnit, self.font_size) main_style.putString(fontPostScriptName, self.font) main_range.putObject(textStyle, textStyle, main_style) main_list.putObject(styleRange, main_range) @@ -641,11 +639,13 @@ def format_text(self): para_range.putInteger(idTo, len(self.input)) para_style.putUnitDouble(firstLineIndent, ptUnit, 0) para_style.putUnitDouble(startIndent, ptUnit, 0) - para_style.putUnitDouble(sID("endIndent"), ptUnit, 0) + para_style.putUnitDouble(APP.instance.sID("endIndent"), ptUnit, 0) para_style.putUnitDouble(spaceBefore, ptUnit, self.line_break_lead) para_style.putUnitDouble(spaceAfter, ptUnit, 0) - para_style.putInteger(sID("dropCapMultiplier"), 1) - para_style.putEnumerated(leadingType, leadingType, sID("leadingBelow")) + para_style.putInteger(APP.instance.sID("dropCapMultiplier"), 1) + para_style.putEnumerated( + leadingType, leadingType, APP.instance.sID("leadingBelow") + ) # Adjust paragraph formatting for modal card with bullet points if self.is_modal: @@ -658,7 +658,9 @@ def format_text(self): default_style.putString(fontPostScriptName, self.font_mana) default_style.putUnitDouble(size, ptUnit, 12) default_style.putBoolean(autoLeading, False) - para_style.putObject(sID("defaultStyle"), textStyle, default_style) + para_style.putObject( + APP.instance.sID("defaultStyle"), textStyle, default_style + ) para_range.putObject(paragraphStyle, paragraphStyle, para_style) style_list.putObject(paragraphStyleRange, para_range) @@ -669,8 +671,10 @@ def format_text(self): para_range.putInteger(idTo, self.flavor_start + 4) para_style.putUnitDouble(startIndent, ptUnit, 0) para_style.putUnitDouble(firstLineIndent, ptUnit, 0) - para_style.putUnitDouble(sID("impliedStartIndent"), ptUnit, 0) - para_style.putUnitDouble(sID("impliedFirstLineIndent"), ptUnit, 0) + para_style.putUnitDouble(APP.instance.sID("impliedStartIndent"), ptUnit, 0) + para_style.putUnitDouble( + APP.instance.sID("impliedFirstLineIndent"), ptUnit, 0 + ) para_style.putUnitDouble(spaceBefore, ptUnit, self.flavor_text_lead) para_range.putObject(paragraphStyle, paragraphStyle, para_style) style_list.putObject(paragraphStyleRange, para_range) @@ -697,9 +701,11 @@ def format_text(self): if self.right_align_quote and '"\r—' in self.flavor_text: para_range.putInteger(idFrom, self.input.find('"\r—') + 2) para_range.putInteger(idTo, self.flavor_end) - para_style.putBoolean(sID("styleSheetHasParent"), True) + para_style.putBoolean(APP.instance.sID("styleSheetHasParent"), True) para_range.putEnumerated( - sID("align"), sID("alignmentType"), sID("right") + APP.instance.sID("align"), + APP.instance.sID("alignmentType"), + APP.instance.sID("right"), ) para_range.putObject(paragraphStyle, paragraphStyle, para_style) style_list.putObject(paragraphStyleRange, para_range) @@ -709,10 +715,12 @@ def format_text(self): main_desc.putList(styleRange, main_list) # Push changes to text layer - main_ref.putEnumerated(textLayer, sID("ordinal"), sID("targetEnum")) - main_target.putReference(sID("target"), main_ref) + main_ref.putEnumerated( + textLayer, APP.instance.sID("ordinal"), APP.instance.sID("targetEnum") + ) + main_target.putReference(APP.instance.sID("target"), main_ref) main_target.putObject(idTo, textLayer, main_desc) - APP.executeAction(sID("set"), main_target, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("set"), main_target, NO_DIALOG) def execute(self): super().execute() diff --git a/src/utils/event.py b/src/utils/event.py new file mode 100644 index 00000000..7fee35f3 --- /dev/null +++ b/src/utils/event.py @@ -0,0 +1,34 @@ +from collections.abc import Callable +from typing import Generic, TypeVar + +T = TypeVar("T") + + +class SubscriptableEvent(Generic[T]): + def __init__(self) -> None: + self._listeners: set[Callable[[T], None]] = set() + + def add_listener(self, listener: Callable[[T], None]) -> None: + self._listeners.add(listener) + + def listen_once(self, listener: Callable[[T], None]) -> None: + def one_off(value: T) -> None: + try: + self._listeners.remove(one_off) + except KeyError: + pass + listener(value) + + self.add_listener(one_off) + + def remove_listener(self, listener: Callable[[T], None]) -> bool: + try: + self._listeners.remove(listener) + return True + except KeyError: + return False + + def trigger(self, value: T) -> None: + # Copy the set in order to allow listeners to remove themselves during iteration. + for listener in self._listeners.copy(): + listener(value) diff --git a/src/utils/fonts.py b/src/utils/fonts.py index 9a226651..b7881a90 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -185,7 +185,7 @@ def get_font_details(path: str) -> tuple[str, FontDetails] | None: return -def get_fonts_from_folder(folder: str) -> dict[str, FontDetails]: +def get_fonts_from_folder(folder: str | os.PathLike[str]) -> dict[str, FontDetails]: """Return a dictionary of font details for the fonts contained in a target directory. Args: @@ -295,7 +295,7 @@ def get_missing_fonts( def check_app_fonts( ps_app: PhotoshopHandler, - folders: list[str] + folders: list[str | os.PathLike[str]] ) -> tuple[dict[str, FontDetails], dict[str, FontDetails]]: """Checks each font in a folder to see if it is installed or outdated. diff --git a/src/utils/threading.py b/src/utils/threading.py new file mode 100644 index 00000000..4c24b70d --- /dev/null +++ b/src/utils/threading.py @@ -0,0 +1,55 @@ +from collections.abc import Callable +from functools import cached_property +from threading import Thread +from typing import Generic, TypeVar + +from src.utils.event import SubscriptableEvent + +T = TypeVar("T") + + +class ThreadInitializedInstance(Generic[T]): + def __init__(self, factory: Callable[[], T]) -> None: + self._on_ready: SubscriptableEvent[T] = SubscriptableEvent() + self._factory = factory + self.ready: bool = False + self._initialization: Thread | None = None + + @cached_property + def instance(self) -> T: + # Instance access is synchronous + if thread := self.initialize(): + thread.join() + return self.instance + + def initialize(self) -> Thread | None: + """ + Start initialization of instance. + + Initialize should be called well ahead of the moment + when the instance is actually needed, because instance + access is synchronous. + + Subscribe to `on_ready` to access the instance in a callback once it's ready. + """ + if self.ready: + return None + + if not self._initialization: + + def init_in_thread() -> None: + self.instance = self._factory() + self.ready = True + self._initialization = None + self._on_ready.trigger(self.instance) + + self._initialization = Thread(target=init_in_thread) + self._initialization.start() + + return self._initialization + + def add_listener(self, listener: Callable[[T], None]) -> None: + self._on_ready.listen_once(listener) + + def remove_listener(self, listener: Callable[[T], None]) -> None: + self._on_ready.remove_listener(listener) From efcbf7ed7b597f1c861159ca892c28e58e0c1fd0 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 29 Aug 2025 16:14:51 +0300 Subject: [PATCH 031/190] docs(README.md): Update Python setup guide --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 48e10f12..b035b8d9 100644 --- a/README.md +++ b/README.md @@ -126,11 +126,11 @@ See `pyproject.toml` for supported Python versions. poetry install ``` 4. Install the fonts included in the `fonts/` folder. Do not delete these after install, some are used by the GUI. -5. Create a folder called `art` in the root directory. This is where you place art images for cards you wish to render. +5. Create a folder called `art` in the root directory. This is where you place art images for cards you wish to batch render. 6. Run the app. ```bash # OPTION 1) Execute via poetry - poetry run main.py + poetry run python main.py # OPTION 2) Enter the poetry environment, then execute with cli poetry shell @@ -142,7 +142,7 @@ See `pyproject.toml` for supported Python versions. ``` 7. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. -# Development Environment +# 🖥 Development Environment If you want to contribute to Proxyshop you should ensure that your code plays well with the strict type checking of [Pyright](https://github.com/microsoft/pyright) or [Mypy](https://github.com/python/mypy). For example, using [VS Code](https://code.visualstudio.com/) with the extensions below will allow you to see type checking results and take advantage of code completions, such as auto-imports, while writing code, though you are free to use any other setup that suits you as well: - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) From fcf144867772da62d6bc781a4a30a2899ea432b1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 31 Aug 2025 14:28:42 +0300 Subject: [PATCH 032/190] refactor(rate_limit): Switch to rate limiting library `limits`, which is more reliable and type annotated --- poetry.lock | 452 ++++++++++++++++++++++++++-------------- pyproject.toml | 2 +- src/utils/hexapi.py | 31 +-- src/utils/rate_limit.py | 42 ++++ src/utils/scryfall.py | 29 +-- 5 files changed, 373 insertions(+), 183 deletions(-) create mode 100644 src/utils/rate_limit.py diff --git a/poetry.lock b/poetry.lock index de901a3f..5634ba4a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,14 +171,14 @@ extras = ["regex"] [[package]] name = "beautifulsoup4" -version = "4.13.4" +version = "4.13.5" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, - {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, + {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, + {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, ] [package.dependencies] @@ -645,14 +645,14 @@ tomlkit = ">=0.5.3,<1.0.0" [[package]] name = "comtypes" -version = "1.4.11" +version = "1.4.12" description = "Pure Python COM package" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "comtypes-1.4.11-py3-none-any.whl", hash = "sha256:1760d5059ca7ca1d61b574c998378d879c271a86c41f88926619ea97497592bb"}, - {file = "comtypes-1.4.11.zip", hash = "sha256:0a4259370ec48b685fe4483b0944ba1df0aa45163922073fe9b7df1d187db09e"}, + {file = "comtypes-1.4.12-py3-none-any.whl", hash = "sha256:e0fa9cc19c489fa7feea4c1710f4575c717e2673edef5b99bf99efd507908e44"}, + {file = "comtypes-1.4.12.zip", hash = "sha256:3ff06c442c2de8a2b25785407f244eb5b6f809d21cf068a855071ba80a76876f"}, ] [[package]] @@ -786,6 +786,24 @@ files = [ {file = "decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656"}, ] +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + [[package]] name = "distlib" version = "0.4.0" @@ -872,70 +890,70 @@ files = [ [[package]] name = "fonttools" -version = "4.59.1" +version = "4.59.2" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "fonttools-4.59.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e90a89e52deb56b928e761bb5b5f65f13f669bfd96ed5962975debea09776a23"}, - {file = "fonttools-4.59.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d29ab70658d2ec19422b25e6ace00a0b0ae4181ee31e03335eaef53907d2d83"}, - {file = "fonttools-4.59.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f9721a564978a10d5c12927f99170d18e9a32e5a727c61eae56f956a4d118b"}, - {file = "fonttools-4.59.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c8758a7d97848fc8b514b3d9b4cb95243714b2f838dde5e1e3c007375de6214"}, - {file = "fonttools-4.59.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2aeb829ad9d41a2ef17cab8bb5d186049ba38a840f10352e654aa9062ec32dc1"}, - {file = "fonttools-4.59.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac216a2980a2d2b3b88c68a24f8a9bfb203e2490e991b3238502ad8f1e7bfed0"}, - {file = "fonttools-4.59.1-cp310-cp310-win32.whl", hash = "sha256:d31dc137ed8ec71dbc446949eba9035926e6e967b90378805dcf667ff57cabb1"}, - {file = "fonttools-4.59.1-cp310-cp310-win_amd64.whl", hash = "sha256:5265bc52ed447187d39891b5f21d7217722735d0de9fe81326566570d12851a9"}, - {file = "fonttools-4.59.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4909cce2e35706f3d18c54d3dcce0414ba5e0fb436a454dffec459c61653b513"}, - {file = "fonttools-4.59.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbec204fa9f877641747f2d9612b2b656071390d7a7ef07a9dbf0ecf9c7195c"}, - {file = "fonttools-4.59.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39dfd42cc2dc647b2c5469bc7a5b234d9a49e72565b96dd14ae6f11c2c59ef15"}, - {file = "fonttools-4.59.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b11bc177a0d428b37890825d7d025040d591aa833f85f8d8878ed183354f47df"}, - {file = "fonttools-4.59.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b9b4c35b3be45e5bc774d3fc9608bbf4f9a8d371103b858c80edbeed31dd5aa"}, - {file = "fonttools-4.59.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:01158376b8a418a0bae9625c476cebfcfcb5e6761e9d243b219cd58341e7afbb"}, - {file = "fonttools-4.59.1-cp311-cp311-win32.whl", hash = "sha256:cf7c5089d37787387123f1cb8f1793a47c5e1e3d1e4e7bfbc1cc96e0f925eabe"}, - {file = "fonttools-4.59.1-cp311-cp311-win_amd64.whl", hash = "sha256:c866eef7a0ba320486ade6c32bfc12813d1a5db8567e6904fb56d3d40acc5116"}, - {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43ab814bbba5f02a93a152ee61a04182bb5809bd2bc3609f7822e12c53ae2c91"}, - {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4f04c3ffbfa0baafcbc550657cf83657034eb63304d27b05cff1653b448ccff6"}, - {file = "fonttools-4.59.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d601b153e51a5a6221f0d4ec077b6bfc6ac35bfe6c19aeaa233d8990b2b71726"}, - {file = "fonttools-4.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c735e385e30278c54f43a0d056736942023c9043f84ee1021eff9fd616d17693"}, - {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1017413cdc8555dce7ee23720da490282ab7ec1cf022af90a241f33f9a49afc4"}, - {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c6d8d773470a5107052874341ed3c487c16ecd179976d81afed89dea5cd7406"}, - {file = "fonttools-4.59.1-cp312-cp312-win32.whl", hash = "sha256:2a2d0d33307f6ad3a2086a95dd607c202ea8852fa9fb52af9b48811154d1428a"}, - {file = "fonttools-4.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b9e4fa7eaf046ed6ac470f6033d52c052481ff7a6e0a92373d14f556f298dc0"}, - {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:89d9957b54246c6251345297dddf77a84d2c19df96af30d2de24093bbdf0528b"}, - {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8156b11c0d5405810d216f53907bd0f8b982aa5f1e7e3127ab3be1a4062154ff"}, - {file = "fonttools-4.59.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8387876a8011caec52d327d5e5bca705d9399ec4b17afb8b431ec50d47c17d23"}, - {file = "fonttools-4.59.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb13823a74b3a9204a8ed76d3d6d5ec12e64cc5bc44914eb9ff1cdac04facd43"}, - {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e1ca10da138c300f768bb68e40e5b20b6ecfbd95f91aac4cc15010b6b9d65455"}, - {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2beb5bfc4887a3130f8625349605a3a45fe345655ce6031d1bac11017454b943"}, - {file = "fonttools-4.59.1-cp313-cp313-win32.whl", hash = "sha256:419f16d750d78e6d704bfe97b48bba2f73b15c9418f817d0cb8a9ca87a5b94bf"}, - {file = "fonttools-4.59.1-cp313-cp313-win_amd64.whl", hash = "sha256:c536f8a852e8d3fa71dde1ec03892aee50be59f7154b533f0bf3c1174cfd5126"}, - {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d5c3bfdc9663f3d4b565f9cb3b8c1efb3e178186435b45105bde7328cfddd7fe"}, - {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ea03f1da0d722fe3c2278a05957e6550175571a4894fbf9d178ceef4a3783d2b"}, - {file = "fonttools-4.59.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57a3708ca6bfccb790f585fa6d8f29432ec329618a09ff94c16bcb3c55994643"}, - {file = "fonttools-4.59.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:729367c91eb1ee84e61a733acc485065a00590618ca31c438e7dd4d600c01486"}, - {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f8ef66ac6db450193ed150e10b3b45dde7aded10c5d279968bc63368027f62b"}, - {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:075f745d539a998cd92cb84c339a82e53e49114ec62aaea8307c80d3ad3aef3a"}, - {file = "fonttools-4.59.1-cp314-cp314-win32.whl", hash = "sha256:c2b0597522d4c5bb18aa5cf258746a2d4a90f25878cbe865e4d35526abd1b9fc"}, - {file = "fonttools-4.59.1-cp314-cp314-win_amd64.whl", hash = "sha256:e9ad4ce044e3236f0814c906ccce8647046cc557539661e35211faadf76f283b"}, - {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:652159e8214eb4856e8387ebcd6b6bd336ee258cbeb639c8be52005b122b9609"}, - {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:43d177cd0e847ea026fedd9f099dc917da136ed8792d142298a252836390c478"}, - {file = "fonttools-4.59.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e54437651e1440ee53a95e6ceb6ee440b67a3d348c76f45f4f48de1a5ecab019"}, - {file = "fonttools-4.59.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6065fdec8ff44c32a483fd44abe5bcdb40dd5e2571a5034b555348f2b3a52cea"}, - {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42052b56d176f8b315fbc09259439c013c0cb2109df72447148aeda677599612"}, - {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bcd52eaa5c4c593ae9f447c1d13e7e4a00ca21d755645efa660b6999425b3c88"}, - {file = "fonttools-4.59.1-cp314-cp314t-win32.whl", hash = "sha256:02e4fdf27c550dded10fe038a5981c29f81cb9bc649ff2eaa48e80dab8998f97"}, - {file = "fonttools-4.59.1-cp314-cp314t-win_amd64.whl", hash = "sha256:412a5fd6345872a7c249dac5bcce380393f40c1c316ac07f447bc17d51900922"}, - {file = "fonttools-4.59.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ab4c1fb45f2984b8b4a3face7cff0f67f9766e9414cbb6fd061e9d77819de98"}, - {file = "fonttools-4.59.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ee39da0227950f88626c91e219659e6cd725ede826b1c13edd85fc4cec9bbe6"}, - {file = "fonttools-4.59.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58a8844f96cff35860647a65345bfca87f47a2494bfb4bef754e58c082511443"}, - {file = "fonttools-4.59.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f021cea6e36410874763f4a517a5e2d6ac36ca8f95521f3a9fdaad0fe73dc"}, - {file = "fonttools-4.59.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf5fb864f80061a40c1747e0dbc4f6e738de58dd6675b07eb80bd06a93b063c4"}, - {file = "fonttools-4.59.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c29ea087843e27a7cffc78406d32a5abf166d92afde7890394e9e079c9b4dbe9"}, - {file = "fonttools-4.59.1-cp39-cp39-win32.whl", hash = "sha256:a960b09ff50c2e87864e83f352e5a90bcf1ad5233df579b1124660e1643de272"}, - {file = "fonttools-4.59.1-cp39-cp39-win_amd64.whl", hash = "sha256:e3680884189e2b7c3549f6d304376e64711fd15118e4b1ae81940cb6b1eaa267"}, - {file = "fonttools-4.59.1-py3-none-any.whl", hash = "sha256:647db657073672a8330608970a984d51573557f328030566521bc03415535042"}, - {file = "fonttools-4.59.1.tar.gz", hash = "sha256:74995b402ad09822a4c8002438e54940d9f1ecda898d2bb057729d7da983e4cb"}, + {file = "fonttools-4.59.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2a159e36ae530650acd13604f364b3a2477eff7408dcac6a640d74a3744d2514"}, + {file = "fonttools-4.59.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bd733e47bf4c6dee2b2d8af7a1f7b0c091909b22dbb969a29b2b991e61e5ba4"}, + {file = "fonttools-4.59.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7bb32e0e33795e3b7795bb9b88cb6a9d980d3cbe26dd57642471be547708e17a"}, + {file = "fonttools-4.59.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cdcdf7aad4bab7fd0f2938624a5a84eb4893be269f43a6701b0720b726f24df0"}, + {file = "fonttools-4.59.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4d974312a9f405628e64f475b1f5015a61fd338f0a1b61d15c4822f97d6b045b"}, + {file = "fonttools-4.59.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:12dc4670e6e6cc4553e8de190f86a549e08ca83a036363115d94a2d67488831e"}, + {file = "fonttools-4.59.2-cp310-cp310-win32.whl", hash = "sha256:1603b85d5922042563eea518e272b037baf273b9a57d0f190852b0b075079000"}, + {file = "fonttools-4.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:2543b81641ea5b8ddfcae7926e62aafd5abc604320b1b119e5218c014a7a5d3c"}, + {file = "fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f"}, + {file = "fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d"}, + {file = "fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2"}, + {file = "fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d"}, + {file = "fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144"}, + {file = "fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf"}, + {file = "fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570"}, + {file = "fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104"}, + {file = "fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea"}, + {file = "fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a"}, + {file = "fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e"}, + {file = "fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25"}, + {file = "fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31"}, + {file = "fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9"}, + {file = "fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c"}, + {file = "fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da"}, + {file = "fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:381bde13216ba09489864467f6bc0c57997bd729abfbb1ce6f807ba42c06cceb"}, + {file = "fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f33839aa091f7eef4e9078f5b7ab1b8ea4b1d8a50aeaef9fdb3611bba80869ec"}, + {file = "fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6235fc06bcbdb40186f483ba9d5d68f888ea68aa3c8dac347e05a7c54346fbc8"}, + {file = "fonttools-4.59.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83ad6e5d06ef3a2884c4fa6384a20d6367b5cfe560e3b53b07c9dc65a7020e73"}, + {file = "fonttools-4.59.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d029804c70fddf90be46ed5305c136cae15800a2300cb0f6bba96d48e770dde0"}, + {file = "fonttools-4.59.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:95807a3b5e78f2714acaa26a33bc2143005cc05c0217b322361a772e59f32b89"}, + {file = "fonttools-4.59.2-cp313-cp313-win32.whl", hash = "sha256:b3ebda00c3bb8f32a740b72ec38537d54c7c09f383a4cfefb0b315860f825b08"}, + {file = "fonttools-4.59.2-cp313-cp313-win_amd64.whl", hash = "sha256:a72155928d7053bbde499d32a9c77d3f0f3d29ae72b5a121752481bcbd71e50f"}, + {file = "fonttools-4.59.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d09e487d6bfbe21195801323ba95c91cb3523f0fcc34016454d4d9ae9eaa57fe"}, + {file = "fonttools-4.59.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dec2f22486d7781087b173799567cffdcc75e9fb2f1c045f05f8317ccce76a3e"}, + {file = "fonttools-4.59.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1647201af10993090120da2e66e9526c4e20e88859f3e34aa05b8c24ded2a564"}, + {file = "fonttools-4.59.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47742c33fe65f41eabed36eec2d7313a8082704b7b808752406452f766c573fc"}, + {file = "fonttools-4.59.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92ac2d45794f95d1ad4cb43fa07e7e3776d86c83dc4b9918cf82831518165b4b"}, + {file = "fonttools-4.59.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fa9ecaf2dcef8941fb5719e16322345d730f4c40599bbf47c9753de40eb03882"}, + {file = "fonttools-4.59.2-cp314-cp314-win32.whl", hash = "sha256:a8d40594982ed858780e18a7e4c80415af65af0f22efa7de26bdd30bf24e1e14"}, + {file = "fonttools-4.59.2-cp314-cp314-win_amd64.whl", hash = "sha256:9cde8b6a6b05f68516573523f2013a3574cb2c75299d7d500f44de82ba947b80"}, + {file = "fonttools-4.59.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:036cd87a2dbd7ef72f7b68df8314ced00b8d9973aee296f2464d06a836aeb9a9"}, + {file = "fonttools-4.59.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14870930181493b1d740b6f25483e20185e5aea58aec7d266d16da7be822b4bb"}, + {file = "fonttools-4.59.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ff58ea1eb8fc7e05e9a949419f031890023f8785c925b44d6da17a6a7d6e85d"}, + {file = "fonttools-4.59.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dee142b8b3096514c96ad9e2106bf039e2fe34a704c587585b569a36df08c3c"}, + {file = "fonttools-4.59.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8991bdbae39cf78bcc9cd3d81f6528df1f83f2e7c23ccf6f990fa1f0b6e19708"}, + {file = "fonttools-4.59.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:53c1a411b7690042535a4f0edf2120096a39a506adeb6c51484a232e59f2aa0c"}, + {file = "fonttools-4.59.2-cp314-cp314t-win32.whl", hash = "sha256:59d85088e29fa7a8f87d19e97a1beae2a35821ee48d8ef6d2c4f965f26cb9f8a"}, + {file = "fonttools-4.59.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7ad5d8d8cc9e43cb438b3eb4a0094dd6d4088daa767b0a24d52529361fd4c199"}, + {file = "fonttools-4.59.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3cdf9d32690f0e235342055f0a6108eedfccf67b213b033bac747eb809809513"}, + {file = "fonttools-4.59.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f9640d6b31d66c0bc54bdbe8ed50983c755521c101576a25e377a8711e8207"}, + {file = "fonttools-4.59.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464d15b58a9fd4304c728735fc1d42cd812fd9ebc27c45b18e78418efd337c28"}, + {file = "fonttools-4.59.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a039c38d5644c691eb53cd65360921338f54e44c90b4e764605711e046c926ee"}, + {file = "fonttools-4.59.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e4f5100e66ec307cce8b52fc03e379b5d1596e9cb8d8b19dfeeccc1e68d86c96"}, + {file = "fonttools-4.59.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:af6dbd463a3530256abf21f675ddf87646272bc48901803a185c49d06287fbf1"}, + {file = "fonttools-4.59.2-cp39-cp39-win32.whl", hash = "sha256:594a6fd2f8296583ac7babc4880c8deee7c4f05ab0141addc6bce8b8e367e996"}, + {file = "fonttools-4.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:fc21c4a05226fd39715f66c1c28214862474db50df9f08fd1aa2f96698887bc3"}, + {file = "fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37"}, + {file = "fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22"}, ] [package.extras] @@ -1005,14 +1023,14 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "griffe" -version = "1.12.1" +version = "1.13.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "griffe-1.12.1-py3-none-any.whl", hash = "sha256:2d7c12334de00089c31905424a00abcfd931b45b8b516967f224133903d302cc"}, - {file = "griffe-1.12.1.tar.gz", hash = "sha256:29f5a6114c0aeda7d9c86a570f736883f8a2c5b38b57323d56b3d1c000565567"}, + {file = "griffe-1.13.0-py3-none-any.whl", hash = "sha256:470fde5b735625ac0a36296cd194617f039e9e83e301fcbd493e2b58382d0559"}, + {file = "griffe-1.13.0.tar.gz", hash = "sha256:246ea436a5e78f7fbf5f24ca8a727bb4d2a4b442a2959052eea3d0bfe9a076e0"}, ] [package.dependencies] @@ -1508,6 +1526,35 @@ dev = ["changelist (==0.5)"] lint = ["pre-commit (==3.7.0)"] test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] +[[package]] +name = "limits" +version = "5.5.0" +description = "Rate limiting utilities" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "limits-5.5.0-py3-none-any.whl", hash = "sha256:57217d01ffa5114f7e233d1f5e5bdc6fe60c9b24ade387bf4d5e83c5cf929bae"}, + {file = "limits-5.5.0.tar.gz", hash = "sha256:ee269fedb078a904608b264424d9ef4ab10555acc8d090b6fc1db70e913327ea"}, +] + +[package.dependencies] +deprecated = ">=1.2" +packaging = ">=21" +typing_extensions = "*" + +[package.extras] +all = ["coredis (>=3.4.0,<6)", "memcachio (>=0.3)", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<7.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)", "valkey (>=6)", "valkey (>=6)"] +async-memcached = ["memcachio (>=0.3)"] +async-mongodb = ["motor (>=3,<4)"] +async-redis = ["coredis (>=3.4.0,<6)"] +async-valkey = ["valkey (>=6)"] +memcached = ["pymemcache (>3,<5.0.0)"] +mongodb = ["pymongo (>4.1,<5)"] +redis = ["redis (>3,!=4.5.2,!=4.5.3,<7.0.0)"] +rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +valkey = ["valkey (>=6)"] + [[package]] name = "loguru" version = "0.7.3" @@ -1656,67 +1703,67 @@ files = [ [[package]] name = "matplotlib" -version = "3.10.5" +version = "3.10.6" description = "Python plotting package" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f"}, - {file = "matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a"}, - {file = "matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512"}, - {file = "matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343"}, - {file = "matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6"}, - {file = "matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467"}, - {file = "matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf"}, - {file = "matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf"}, - {file = "matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a"}, - {file = "matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc"}, - {file = "matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89"}, - {file = "matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903"}, - {file = "matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420"}, - {file = "matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2"}, - {file = "matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389"}, - {file = "matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea"}, - {file = "matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468"}, - {file = "matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369"}, - {file = "matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b"}, - {file = "matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2"}, - {file = "matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed"}, - {file = "matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1"}, - {file = "matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7"}, - {file = "matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f"}, - {file = "matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4"}, - {file = "matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe"}, - {file = "matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674"}, - {file = "matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c"}, - {file = "matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e"}, - {file = "matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b"}, - {file = "matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea"}, - {file = "matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124"}, - {file = "matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce"}, - {file = "matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154"}, - {file = "matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715"}, - {file = "matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837"}, - {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202"}, - {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb"}, - {file = "matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975"}, - {file = "matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667"}, - {file = "matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516"}, - {file = "matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e"}, - {file = "matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a"}, - {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569"}, - {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012"}, - {file = "matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592"}, - {file = "matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959"}, - {file = "matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b"}, - {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f"}, - {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30"}, - {file = "matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7"}, - {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f"}, - {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b"}, - {file = "matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61"}, - {file = "matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076"}, + {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, + {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, + {file = "matplotlib-3.10.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fa4c43d6bfdbfec09c733bca8667de11bfa4970e8324c471f3a3632a0301c15"}, + {file = "matplotlib-3.10.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea117a9c1627acaa04dbf36265691921b999cbf515a015298e54e1a12c3af837"}, + {file = "matplotlib-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08fc803293b4e1694ee325896030de97f74c141ccff0be886bb5915269247676"}, + {file = "matplotlib-3.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:2adf92d9b7527fbfb8818e050260f0ebaa460f79d61546374ce73506c9421d09"}, + {file = "matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f"}, + {file = "matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76"}, + {file = "matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6"}, + {file = "matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f"}, + {file = "matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce"}, + {file = "matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e"}, + {file = "matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951"}, + {file = "matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347"}, + {file = "matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75"}, + {file = "matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95"}, + {file = "matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb"}, + {file = "matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07"}, + {file = "matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b"}, + {file = "matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa"}, + {file = "matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a"}, + {file = "matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf"}, + {file = "matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a"}, + {file = "matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110"}, + {file = "matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2"}, + {file = "matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18"}, + {file = "matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6"}, + {file = "matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f"}, + {file = "matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27"}, + {file = "matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833"}, + {file = "matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa"}, + {file = "matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706"}, + {file = "matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e"}, + {file = "matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5"}, + {file = "matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899"}, + {file = "matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c"}, + {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438"}, + {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453"}, + {file = "matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47"}, + {file = "matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98"}, + {file = "matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a"}, + {file = "matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b"}, + {file = "matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c"}, + {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3"}, + {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf"}, + {file = "matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a"}, + {file = "matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3"}, + {file = "matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:13fcd07ccf17e354398358e0307a1f53f5325dca22982556ddb9c52837b5af41"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:470fc846d59d1406e34fa4c32ba371039cd12c2fe86801159a965956f2575bd1"}, + {file = "matplotlib-3.10.6-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7173f8551b88f4ef810a94adae3128c2530e0d07529f7141be7f8d8c365f051"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf"}, + {file = "matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb"}, + {file = "matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c"}, ] [package.dependencies] @@ -1820,14 +1867,14 @@ mkdocs = ">=1.2.3" [[package]] name = "mkdocs-autorefs" -version = "1.4.2" +version = "1.4.3" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13"}, - {file = "mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749"}, + {file = "mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9"}, + {file = "mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75"}, ] [package.dependencies] @@ -1885,14 +1932,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.1.6" +version = "7.1.7" description = "Mkdocs Markdown includer plugin." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_include_markdown_plugin-7.1.6-py3-none-any.whl", hash = "sha256:7975a593514887c18ecb68e11e35c074c5499cfa3e51b18cd16323862e1f7345"}, - {file = "mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677"}, + {file = "mkdocs_include_markdown_plugin-7.1.7-py3-none-any.whl", hash = "sha256:a0c13efe4f6b05a419c022e201055bf43145eed90de65f2353c33fb4005b6aa5"}, + {file = "mkdocs_include_markdown_plugin-7.1.7.tar.gz", hash = "sha256:677637e04c2d3497c50340be522e2a7f614124f592c7982d88b859f88d527a4c"}, ] [package.dependencies] @@ -2021,18 +2068,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.17.0" +version = "1.18.2" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocstrings_python-1.17.0-py3-none-any.whl", hash = "sha256:49903fa355dfecc5ad0b891e78ff5d25d30ffd00846952801bbe8331e123d4b0"}, - {file = "mkdocstrings_python-1.17.0.tar.gz", hash = "sha256:c6295962b60542a9c7468a3b515ce8524616ca9f8c1a38c790db4286340ba501"}, + {file = "mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d"}, + {file = "mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323"}, ] [package.dependencies] -griffe = ">=1.12.1" +griffe = ">=1.13" mkdocs-autorefs = ">=1.4" mkdocstrings = ">=0.30" @@ -2401,7 +2448,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -2483,7 +2530,7 @@ wheel = "^0.45.0" type = "git" url = "https://github.com/pappnu/photoshop-python-api.git" reference = "type-annotation" -resolved_reference = "e790d52e8aa4a753d2a1b6044afe062af545e4c4" +resolved_reference = "9f8496ef88c96918a8ba4434f5878968e034fcaa" [[package]] name = "pillow" @@ -2612,14 +2659,14 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, ] [package.extras] @@ -2664,14 +2711,14 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.51" +version = "3.0.52" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, - {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, + {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, + {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, ] [package.dependencies] @@ -3636,14 +3683,14 @@ typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.13\"" [[package]] name = "questionary" -version = "2.1.0" +version = "2.1.1" description = "Python library to build pretty command line user prompts ⭐️" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"}, - {file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"}, + {file = "questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59"}, + {file = "questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d"}, ] [package.dependencies] @@ -3947,14 +3994,14 @@ files = [ [[package]] name = "soupsieve" -version = "2.7" +version = "2.8" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, - {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, + {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, + {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, ] [[package]] @@ -3986,14 +4033,14 @@ files = [ [[package]] name = "tifffile" -version = "2025.6.11" +version = "2025.8.28" description = "Read and write TIFF files" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542"}, - {file = "tifffile-2025.6.11.tar.gz", hash = "sha256:0ece4c2e7a10656957d568a093b07513c0728d30c1bd8cc12725901fffdb7143"}, + {file = "tifffile-2025.8.28-py3-none-any.whl", hash = "sha256:b274a6d9eeba65177cf7320af25ef38ecf910b3369ac6bc494a94a3f6bd99c78"}, + {file = "tifffile-2025.8.28.tar.gz", hash = "sha256:82929343c70f6f776983f6a817f0b92e913a1bbb3dc3f436af44419b872bb467"}, ] [package.dependencies] @@ -4112,14 +4159,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] @@ -4277,6 +4324,97 @@ files = [ [package.extras] dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] +[[package]] +name = "wrapt" +version = "1.17.3" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, + {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, + {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, + {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, + {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, + {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, + {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, + {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, + {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, + {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, + {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, + {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, + {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, + {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, + {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, + {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, + {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, + {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, + {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, + {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, + {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, + {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, + {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, + {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, +] + [[package]] name = "yarl" version = "1.20.1" @@ -4419,4 +4557,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.15" -content-hash = "2e804e72c253b0b2bb6df8acbe3b93eb3c85ea5c4182ebdb8e57b1b7e292f92e" +content-hash = "f1231aff33158110e08dc924a6c96d2115e784d3a2675c3f5ad3810be48dddc3" diff --git a/pyproject.toml b/pyproject.toml index 9c4f83b9..27c68ee4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ asynckivy = "^0.9.0" Pillow = "^11.3.0" kivy = "^2.3.1" typing-extensions = "^4.14.1" -ratelimit = "^2.2.1" +limits = "^5.5.0" backoff = "^2.2.1" pathvalidate = "^3.3.1" fonttools = "^4.59.1" diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 46abb086..81a98a2d 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -2,28 +2,32 @@ * Handles Requests to the hexproof.io API """ # Standard Library Imports +from collections.abc import Callable from contextlib import suppress from functools import cache from pathlib import Path from typing import Any, ParamSpec, TypeVar -from collections.abc import Callable -# Third Party Imports -from pydantic import BaseModel import requests -from requests import RequestException -from ratelimit import RateLimitDecorator, sleep_and_retry -from backoff import on_exception, expo -from omnitils.exceptions import log_on_exception, return_on_exception, ExceptionLogger +from backoff import expo, on_exception +from hexproof.hexapi.enums import HexURL +from hexproof.hexapi.schema.meta import Meta +from limits import RateLimitItemPerSecond +from limits.storage import MemoryStorage +from limits.strategies import MovingWindowRateLimiter +from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception from omnitils.fetch import download_file -from omnitils.files.archive import unpack_zip from omnitils.files import dump_data_file -from hexproof.hexapi.schema.meta import Meta +from omnitils.files.archive import unpack_zip + +# Third Party Imports +from pydantic import BaseModel +from requests import RequestException # Local Imports from src import CON, CONSOLE, PATH -from hexproof.hexapi.enums import HexURL from src.utils.download import HEADERS +from src.utils.rate_limit import rate_limit """ * Types @@ -47,7 +51,9 @@ class HexproofSet(BaseModel): """ # Rate limiter to safely limit Hexproof.io requests -hexproof_rate_limit = RateLimitDecorator(calls=20, period=1) +_rate_limit_storage = MemoryStorage() +_hexproof_rate_limit = MovingWindowRateLimiter(_rate_limit_storage) +_rate_limit = RateLimitItemPerSecond(20) # Hexproof.io HTTP header hexproof_http_header = HEADERS.Default.copy() @@ -71,8 +77,7 @@ def hexproof_request_wrapper(fallback: T, logr: ExceptionLogger | None = None) - def decorator(func: Callable[P,T]): @return_on_exception(fallback) @log_on_exception(logr) - @sleep_and_retry - @hexproof_rate_limit + @rate_limit(strategy=_hexproof_rate_limit, limit=_rate_limit, reschedule=0.1) @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) diff --git a/src/utils/rate_limit.py b/src/utils/rate_limit.py new file mode 100644 index 00000000..d14948fc --- /dev/null +++ b/src/utils/rate_limit.py @@ -0,0 +1,42 @@ +from collections.abc import Callable, Iterable +from time import sleep +from typing import ParamSpec, TypeVar + +from limits import RateLimitItem +from limits.strategies import RateLimiter + +T = TypeVar("T") +P = ParamSpec("P") + + +class RateLimitError(Exception): + pass + + +def rate_limit( + strategy: RateLimiter, + limit: RateLimitItem, + reschedule: float = 1, + identifiers: Iterable[str] = tuple(), +) -> Callable[[Callable[P, T]], Callable[P, T]]: + """Decorator for rate_limiting function calls. + + Args: + strategy: Limiting strategy. + limit: The limit, e.g, 10 calls per second. + reschedule: How long to sleep in seconds before reattempting. Pass 0 to raise `RateLimitError` instead when limit is exceeded. + identifier: Identifiers that can be used to separate this limit from oters. + """ + + def decorator(func: Callable[P, T]) -> Callable[P, T]: + def wrapper(*args: P.args, **kwargs: P.kwargs): + if reschedule: + while not strategy.hit(limit, *identifiers): + sleep(reschedule) + elif not strategy.hit(limit, *identifiers): + raise RateLimitError(f"Limit exceeded for '{identifiers}' '{limit}'") + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 767f28da..edd1762b 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -3,33 +3,37 @@ """ # Standard Library Imports +from collections.abc import Callable, Sequence from pathlib import Path from shutil import copyfileobj from typing import ( Any, + Literal, + NotRequired, ParamSpec, SupportsInt, - TypeVar, TypedDict, - Literal, - NotRequired, + TypeVar, Unpack, ) -from collections.abc import Sequence, Callable + +import requests +import yarl # Third Party Imports -from backoff import on_exception, expo +from backoff import expo, on_exception from hexproof.scryfall.enums import ScryURL -from omnitils.exceptions import log_on_exception, return_on_exception, ExceptionLogger -from ratelimit import sleep_and_retry, RateLimitDecorator -import requests +from limits import RateLimitItemPerSecond +from limits.storage import MemoryStorage +from limits.strategies import MovingWindowRateLimiter +from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception from requests.exceptions import RequestException -import yarl # Local Imports from src import CONSOLE, PATH from src.console import get_bullet_points from src.utils.download import HEADERS +from src.utils.rate_limit import rate_limit """ * Types @@ -59,7 +63,9 @@ class ScryfallError(TypedDict): """ # Rate limiter to safely limit Scryfall requests -scryfall_rate_limit = RateLimitDecorator(calls=20, period=1) +_rate_limit_storage = MemoryStorage() +_scryfall_rate_limit = MovingWindowRateLimiter(_rate_limit_storage) +_rate_limit = RateLimitItemPerSecond(10) # Scryfall HTTP header scryfall_http_header = HEADERS.Default.copy() @@ -140,8 +146,7 @@ def decorator(func: Callable[P, T]): @on_exception( expo, requests.exceptions.RequestException, max_tries=2, max_time=1 ) - @sleep_and_retry - @scryfall_rate_limit + @rate_limit(strategy=_scryfall_rate_limit, limit=_rate_limit, reschedule=0.1) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) From e2e6fda81b169d7e16c058690ab79f1c487870a6 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 31 Aug 2025 14:43:27 +0300 Subject: [PATCH 033/190] refactor(rate_limit): Simplify rescheduling of rate limited calls --- src/utils/hexapi.py | 2 +- src/utils/rate_limit.py | 7 ++++--- src/utils/scryfall.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 81a98a2d..2c72b651 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -77,7 +77,7 @@ def hexproof_request_wrapper(fallback: T, logr: ExceptionLogger | None = None) - def decorator(func: Callable[P,T]): @return_on_exception(fallback) @log_on_exception(logr) - @rate_limit(strategy=_hexproof_rate_limit, limit=_rate_limit, reschedule=0.1) + @rate_limit(strategy=_hexproof_rate_limit, limit=_rate_limit) @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) diff --git a/src/utils/rate_limit.py b/src/utils/rate_limit.py index d14948fc..bde49d59 100644 --- a/src/utils/rate_limit.py +++ b/src/utils/rate_limit.py @@ -1,5 +1,5 @@ from collections.abc import Callable, Iterable -from time import sleep +from time import sleep, time from typing import ParamSpec, TypeVar from limits import RateLimitItem @@ -16,7 +16,7 @@ class RateLimitError(Exception): def rate_limit( strategy: RateLimiter, limit: RateLimitItem, - reschedule: float = 1, + reschedule: bool = True, identifiers: Iterable[str] = tuple(), ) -> Callable[[Callable[P, T]], Callable[P, T]]: """Decorator for rate_limiting function calls. @@ -32,7 +32,8 @@ def decorator(func: Callable[P, T]) -> Callable[P, T]: def wrapper(*args: P.args, **kwargs: P.kwargs): if reschedule: while not strategy.hit(limit, *identifiers): - sleep(reschedule) + window = strategy.get_window_stats(limit, *identifiers) + sleep(window.reset_time - time()) elif not strategy.hit(limit, *identifiers): raise RateLimitError(f"Limit exceeded for '{identifiers}' '{limit}'") return func(*args, **kwargs) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index edd1762b..8aca7183 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -146,7 +146,7 @@ def decorator(func: Callable[P, T]): @on_exception( expo, requests.exceptions.RequestException, max_tries=2, max_time=1 ) - @rate_limit(strategy=_scryfall_rate_limit, limit=_rate_limit, reschedule=0.1) + @rate_limit(strategy=_scryfall_rate_limit, limit=_rate_limit) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) From 65476fb37eaf4afea19a6ec2404e6a9c72ba8870 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 2 Sep 2025 18:33:26 +0300 Subject: [PATCH 034/190] fix(process_card_data): Set card as front after failing to match the file name with any of the card faces --- src/cards.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cards.py b/src/cards.py index 29cf360a..019ccaee 100644 --- a/src/cards.py +++ b/src/cards.py @@ -5,7 +5,7 @@ # Standard Library Imports from contextlib import suppress from pathlib import Path -from typing import TypedDict, Any, cast +from typing import TypedDict, Any # Third Party Imports from omnitils.strings import normalize_str @@ -190,10 +190,15 @@ def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: # Check for alternate MDFC / Transform layouts if 'card_faces' in data: # Select the corresponding face - card_faces = cast(list[dict[str,Any]], data['card_faces']) - card_face, i = (card_faces[0], 0) if ( - normalize_str(card_faces[0].get('name', ''), True) == name_normalized - ) else (card_faces[1], 1) + card_faces = data['card_faces'] + # Default to front face + i = 0 + card_face = card_faces[0] + for idx, face in enumerate(card_faces): + if normalize_str(face.get('name', ''), True) == name_normalized: + i = idx + card_face = face + break # Decide if this is a front face data['front'] = True if i == 0 else False # Transform / MDFC Planeswalker layout From c6ab8950ef76278efa247236a44cbabd0ca3bf65 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 4 Sep 2025 19:47:58 +0300 Subject: [PATCH 035/190] fix(ProxyshopGUIApp): Fix app crashing in test mode --- src/gui/app.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index d99afa81..f2454408 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -292,16 +292,19 @@ def thread_cancelled(self) -> bool: """ @cached_property - def render_target_button(self) -> Button: - return self._app_layout.app_tabs.main_tab.main_panel.render_target_button + def render_target_button(self) -> Button | None: + if isinstance(self._app_layout, AppContainer): + return self._app_layout.app_tabs.main_tab.main_panel.render_target_button @cached_property - def render_all_button(self) -> Button: - return self._app_layout.app_tabs.main_tab.main_panel.render_all_button + def render_all_button(self) -> Button | None: + if isinstance(self._app_layout, AppContainer): + return self._app_layout.app_tabs.main_tab.main_panel.render_all_button @cached_property - def app_settings_button(self) -> Button: - return self._app_layout.app_tabs.main_tab.main_panel.app_settings_button + def app_settings_button(self) -> Button | None: + if isinstance(self._app_layout, AppContainer): + return self._app_layout.app_tabs.main_tab.main_panel.app_settings_button @cached_property def toggle_buttons(self) -> list[Button]: @@ -952,8 +955,10 @@ def render_dropped_files(self, _window, _x, _y) -> None: def on_start(self) -> None: """Fired after build is fired. Run a diagnostic check to see what works.""" - self.disable_buttons() - self.app_settings_button.disabled = False + if not self.env.TEST_MODE: + self.disable_buttons() + if self.app_settings_button: + self.app_settings_button.disabled = False self.console.update("Running startup checks...") @@ -1012,8 +1017,10 @@ def photoshop_checks(app: PhotoshopHandler) -> None: end="", ) finally: - self.render_target_button.disabled = False - self.render_all_button.disabled = False + if self.render_target_button: + self.render_target_button.disabled = False + if self.render_all_button: + self.render_all_button.disabled = False if self._app.ready: photoshop_checks(self.app) From 501a66fa086067a852f7f3c1f8798e65a43bd31a Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 4 Sep 2025 19:52:47 +0300 Subject: [PATCH 036/190] fix(ClassicModernTemplate): Enable the crown for only legendary cards --- src/templates/normal.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/templates/normal.py b/src/templates/normal.py index 1fc1b246..0402ba4a 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -808,7 +808,7 @@ def gold_pt(self) -> bool: """ @cached_property - def has_pinlines(self): + def has_pinlines(self) -> bool: """bool: Allow pinlines for Land and Artifact cards.""" return bool(self.is_land or self.is_artifact) @@ -820,7 +820,7 @@ def has_pinlines(self): def pinlines_colors( self, ) -> ColorObject | Sequence[ColorObject] | Sequence[GradientConfig]: - """Union[list[int], list[dict]]: Allow pinlines on Land and Artifact cards.""" + """Allow pinlines on Land and Artifact cards.""" if self.has_pinlines: if len(self.identity) >= self.color_limit: if len(self.identity) == 2: @@ -2466,10 +2466,11 @@ def crown_colors(self) -> str: @cached_property def crown_shape(self) -> ArtLayer | None: # Support Normal and Extended - return psd.getLayer( - LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, - [LAYERS.LEGENDARY_CROWN, LAYERS.SHAPE], - ) + if self.is_legendary: + return psd.getLayer( + LAYERS.EXTENDED if self.is_extended else LAYERS.NORMAL, + [LAYERS.LEGENDARY_CROWN, LAYERS.SHAPE], + ) @cached_property def border_shape(self) -> ArtLayer | None: From 7196336cb71f6e2b53c0b3048bc0a7da8f62cca0 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 6 Sep 2025 11:36:15 +0300 Subject: [PATCH 037/190] fix(FormattedTextArea): Width scaling should be off by default --- src/text_layers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_layers.py b/src/text_layers.py index 3d5dbf17..2668d3d1 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -770,7 +770,7 @@ def scale_height(self) -> bool: @cached_property def scale_width(self) -> bool: """Scale text to fit reference width (Default: False).""" - return self.kwargs.get("scale_width", True) + return self.kwargs.get("scale_width", False) @cached_property def fix_overflow_width(self) -> bool: From d3def46a5ae711ab8e7c6d2f2ed61e64394c6a72 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 6 Sep 2025 11:42:04 +0300 Subject: [PATCH 038/190] test(template_renders.toml): Add test cases for Saga Creatures, Cases, Stations and Levelers with blank levels --- src/data/tests/template_renders.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/data/tests/template_renders.toml b/src/data/tests/template_renders.toml index 6df9e23c..5a7c6c34 100644 --- a/src/data/tests/template_renders.toml +++ b/src/data/tests/template_renders.toml @@ -50,6 +50,7 @@ Gemrazer = "Creature" [leveler] "Guul Draz Assassin" = "Creature - 2" +"Lighthouse Chronologist" = "Blank Level" [saga] "Time of Ice" = "Mono Color" @@ -57,11 +58,17 @@ Gemrazer = "Creature" "The Elder Dragon War" = "Read Ahead" "The Great Synthesis [MOM]" = "Transform Back" "The Restoration of Eiganjo" = "Transform Front" +"Bahamut, Warden of Light" = "Legendary Transform Back Creature" +"Summon: Fat Chocobo" = "Creature with Flavor text" [class] "Wizard Class" = "Mono Color" "Rogue Class" = "Dual Color" +[case] +"Case of the Filched Falcon" = "Mono" +"Case of the Shattered Pact" = "Colorless" + [planeswalker] "Liliana of the Veil" = "Mono" "Kasmina, Enigma Sage" = "Dual" @@ -101,3 +108,8 @@ Gemrazer = "Creature" "Invasion of Kaldheim [MOM]" = "Mono Color" "Invasion of Tolvada [MOM]" = "Dual Color" "Invasion of Alara [MOM]" = "Gold" + +[station] +"Adagia, Windswept Bastion" = "Mono Land" +"Extinguisher Battleship" = "Colorless" +"Inspirit, Flagship Vessel" = "Legendary Multi Color" \ No newline at end of file From 25fa59f80b4d562b82d963b10a3961f0e3221837 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 6 Sep 2025 14:42:11 +0300 Subject: [PATCH 039/190] fix(AdventureLayout): Sort Adventure color identity in the MTG canonical color order --- src/layouts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index 9279ab23..598ef543 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1143,7 +1143,7 @@ def flavor_text_adventure(self) -> str: @cached_property def color_identity_adventure(self) -> list[str]: """Colors present in the adventure side mana cost.""" - return [n for n in get_mana_cost_colors(self.mana_adventure)] + return [n for n in get_ordered_colors(get_mana_cost_colors(self.mana_adventure))] @cached_property def adventure_colors(self) -> str: From 538cd30f1b750b2e3211f93717b9ac4e069a6880 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 6 Sep 2025 18:01:25 +0300 Subject: [PATCH 040/190] refactor(app.py): Improve type annotations --- src/gui/app.py | 143 ++++++++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 61 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index f2454408..8736d1a6 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -2,6 +2,7 @@ * Proxyshop GUI Launcher """ # Standard Library Imports +from __future__ import annotations import os import json from io import BytesIO @@ -9,6 +10,7 @@ from time import perf_counter from concurrent.futures import ThreadPoolExecutor from contextlib import suppress +from typing import Any, Concatenate, ParamSpec, TypeVar import win32clipboard as clipboard from datetime import datetime as dt from functools import cached_property @@ -66,6 +68,9 @@ from src.utils.fonts import check_app_fonts from src.utils.threading import ThreadInitializedInstance +T = TypeVar("T") +P = ParamSpec("P") + """ * App Tab Containers @@ -76,7 +81,7 @@ class AppContainer(BoxLayout, GlobalAccess): """Container for overall app.""" @cached_property - def app_tabs(self) -> "AppTabs": + def app_tabs(self) -> AppTabs: return AppTabs() def on_load(self, *args) -> None: @@ -93,7 +98,7 @@ def __init__(self, **kwargs): '0dp', '0dp', '0dp', '0dp') @cached_property - def main_tab(self) -> "MainTab": + def main_tab(self) -> MainTab: return MainTab() def on_load(self, *args) -> None: @@ -166,8 +171,8 @@ def __init__( self._templates = templates self._template_map = template_map self._templates_default = templates_default - self._templates_selected = {} - self._current_render = None + self._templates_selected: TemplateSelectedMap = {} + self._current_render: BaseTemplate | None = None self._result = False """ @@ -258,7 +263,7 @@ def templates_default(self) -> TemplateSelectedMap: return self._templates_default @cached_property - def current_render(self) -> BaseTemplate: + def current_render(self) -> BaseTemplate | None: """BaseTemplate: Tracks the current template class being used for rendering.""" return self._current_render @@ -316,7 +321,9 @@ def toggle_buttons(self) -> list[Button]: """ @staticmethod - def render_process_wrapper(func) -> Callable: + def render_process_wrapper( + func: Callable[Concatenate[ProxyshopGUIApp, P], T], + ) -> Callable[Concatenate[ProxyshopGUIApp, P], T | None]: """Decorator to handle state maintenance before and after an initiated render process. Args: @@ -326,7 +333,9 @@ def render_process_wrapper(func) -> Callable: The result of the wrapped function. """ - def wrapper(self, *args): + def wrapper( + self: ProxyshopGUIApp, *args: P.args, **kwargs: P.kwargs + ) -> T | None: # Never render on two threads at once if self._render_lock.locked(): return @@ -339,15 +348,15 @@ def wrapper(self, *args): # Ensure Photoshop is responsive while check := self.app.refresh_app(): if not self.console.await_choice( - thr=Event(), - msg=get_photoshop_error_message(check), - end="Hit Continue to try again, or Cancel to end the operation.\n" + thr=Event(), + msg=get_photoshop_error_message(check), + end="Hit Continue to try again, or Cancel to end the operation.\n", ): # Cancel this operation return # Call the function - result = func(self, *args) + result = func(self, *args, **kwargs) # Enable buttons / close document on exit self.reset(enable_buttons=True, close_document=True) @@ -461,22 +470,32 @@ def render_all(self, target: bool = False, files: list[Path] | None = None) -> N layouts: dict[str, dict[str, list[NormalLayout]]] = {} failed: list[str] = [] for c in cards: - # Add failed card if isinstance(c, str): failed.append(c) continue + temp = temps.get(c.card_class, None) + + if not temp: + msg_error( + msg=c.display_name, + reason=f"No suitable template found for type '{c.card_class}'", + ) + continue + # Assign card as failure if template isn't installed - if not temps[c.card_class]['object'].is_installed: - c = msg_error( + if not temp["object"].is_installed: + msg_error( msg=c.display_name, - reason=f"Template '{temps[c.card_class]['name']}' with type " - f"'{c.card_class}' is not installed!") + reason=f"Template '{temp['name']}' with type " + f"'{c.card_class}' is not installed!", + ) + continue # Map card to its template path and layout type layouts.setdefault( - str(temps[c.card_class]['object'].path_psd), {} + str(temp["object"].path_psd), {} ).setdefault(c.card_class, []).append(c) # Did any cards fail to find? @@ -501,46 +520,46 @@ def render_all(self, target: bool = False, files: list[Path] | None = None) -> N times: list[float] = [] for (i), (_path, class_map) in enumerate(layouts.items()): for layout, cards in class_map.items(): + if temp := temps[layout]: + # Initialize the template's python class module + loaded_class = temp['object'].get_template_class( + temp['class_name']) + if not loaded_class: - # Initialize the template's python class module - loaded_class = temps[layout]['object'].get_template_class( - temps[layout]['class_name']) - if not loaded_class: - - # Failed to load module or python class - self.console.update(msg_error( - "Unable to load Python class: " - f"{msg_bold(temps[layout]['class_name'])}")) - - # If more templates in the queue, ask to continue - if (i + 1) < len(layouts): - - # Get a list of cards using this template - failed_cards = get_bullet_points( - text=[str(c.display_name) for c in cards], - char='-') - if self.console.error( - msg=f"{msg_error('The following cards have been cancelled:')}" - f"{failed_cards}" - ): - # Continue to next template - self.console.update() - continue - - # Cancel render process - return + # Failed to load module or python class + self.console.update(msg_error( + "Unable to load Python class: " + f"{msg_bold(temp['class_name'])}")) + + # If more templates in the queue, ask to continue + if (i + 1) < len(layouts): + + # Get a list of cards using this template + failed_cards = get_bullet_points( + text=[str(c.display_name) for c in cards], + char='-') + if self.console.error( + msg=f"{msg_error('The following cards have been cancelled:')}" + f"{failed_cards}" + ): + # Continue to next template + self.console.update() + continue + + # Cancel render process + return - # Load constants and config for this template - self.cfg.load(temps[layout]['config']) - self.con.reload() + # Load constants and config for this template + self.cfg.load(temp['config']) + self.con.reload() - # Render each card with this PSD and class - for c in cards: - result = self.start_render(c, temps[layout], loaded_class) - if self.thread_cancelled: - return - if result is not None: - times.append(result) + # Render each card with this PSD and class + for c in cards: + result = self.start_render(c, temp, loaded_class) + if self.thread_cancelled: + return + if result is not None: + times.append(result) # Render group complete self.close_document() @@ -552,7 +571,7 @@ def render_all(self, target: bool = False, files: list[Path] | None = None) -> N self.console.update(f'Average time: {avg} seconds') @render_process_wrapper - def render_custom(self, template: TemplateDetails, scryfall: dict) -> None: + def render_custom(self, template: TemplateDetails, scryfall: dict[str,Any]) -> None: """Set up custom render job, then execute. Args: @@ -573,7 +592,8 @@ def render_custom(self, template: TemplateDetails, scryfall: dict) -> None: 'name': scryfall.get('name', ''), 'artist': scryfall.get('artist', ''), 'set': scryfall.get('set', ''), - 'creator': None + 'number': "", + 'creator': "" }) except Exception as e: self.console.update( @@ -786,7 +806,7 @@ def start_render( timed = round(self.timer - start_time, 1) # Return execution time if successful - if not self.thread.is_set() and result: + if self.thread and not self.thread.is_set() and result: if not self.env.TEST_MODE: self.console.update(f"[i]Time completed: {timed} seconds[/i]\n") return timed @@ -795,7 +815,7 @@ def start_render( except Exception as error: # Reset document - if self.docref: + if self.docref and self.current_render: self.current_render.reset() # Log the error @@ -892,7 +912,7 @@ def enable_buttons(self) -> None: def toggle_window_locked(self) -> None: """Toggle whether to pin the window above all other windows.""" - window: Window = self.root_window + window = self.root_window window.always_on_top = not window.always_on_top def screenshot_window(self) -> None: @@ -911,7 +931,8 @@ def screenshot_window(self) -> None: data = bmp.getvalue()[14:] # Apply data to the clipboard - clipboard.OpenClipboard(), clipboard.EmptyClipboard() + clipboard.OpenClipboard() + clipboard.EmptyClipboard() clipboard.SetClipboardData(clipboard.CF_DIB, data) clipboard.CloseClipboard() @@ -1083,7 +1104,7 @@ def add_console(self) -> None: def on_stop(self) -> None: """Called when the app is closed.""" - if self.thread and isinstance(self.thread, Event): + if self.thread: self.thread.set() """ From 7e5630a0daf03c86c44965645a72ac78e1909bd3 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 7 Sep 2025 14:12:12 +0300 Subject: [PATCH 041/190] feat(ClassMod): Fall back to default rules text and pt logic if the card is not a Class --- src/templates/classes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/templates/classes.py b/src/templates/classes.py index 5c9fd279..f6010d39 100644 --- a/src/templates/classes.py +++ b/src/templates/classes.py @@ -125,7 +125,9 @@ def class_stage_layers(self, value: list[LayerSet]): def rules_text_and_pt_layers(self) -> None: """Skip this step for Class cards.""" - pass + if self.is_class_layout and not self.is_creature: + return + return super().rules_text_and_pt_layers() """ * Class Text Layer Methods From d90b8beeb8e20cda8a319bebdaf48725bea542b1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 7 Sep 2025 14:12:54 +0300 Subject: [PATCH 042/190] feat(SagaMod): Fall back to default rules text and pt logic if the card is not a Saga --- src/templates/saga.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/templates/saga.py b/src/templates/saga.py index 8c65b208..5b0b1ef9 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -148,7 +148,9 @@ def reminder_reference(self) -> ReferenceLayer | None: def rules_text_and_pt_layers(self) -> None: """Skip this step for Saga cards.""" - pass + if self.is_layout_saga and not self.is_creature: + return + return super().rules_text_and_pt_layers() """ * Saga Frame Layer Methods From 6e56e6f7eb22bb8b82f989fb8142f99020172e6b Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 7 Sep 2025 14:21:10 +0300 Subject: [PATCH 043/190] feat(text_layers.py): Assign blank text to text item if that is what is given to the text formatting class Text fields in the PSD files usually have placeholder text in them, so in some rare situations, e.g. with Tokens, where the text should be cleared it isn't unless that specific case is separately handled in the template code. This change allows relying on the default text processing logic in such situations. --- src/text_layers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/text_layers.py b/src/text_layers.py index 2668d3d1..a12b08d6 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -725,6 +725,10 @@ def format_text(self): def execute(self): super().execute() + # No need to format if there's no content + if not self.input: + return + # Format text self.format_text() @@ -857,13 +861,10 @@ def position_within_reference(self): ) def execute(self): - # Skip if both are empty - if not self.input: - return font_size = None # Pre-scaling to prevent text overflow errors - if self.fix_overflow_height and self.reference_dims: + if self.input and self.fix_overflow_height and self.reference_dims: font_size = self.pre_scale_to_fit() # Execute text formatting From 93f7ca71f19372cb355f11d8440a4e3983c90968 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 7 Sep 2025 14:39:25 +0300 Subject: [PATCH 044/190] fix(ENV): Override the defaults with `env.yml` and not the other way around --- src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__init__.py b/src/__init__.py index e5120bb0..e6312b46 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -30,7 +30,7 @@ def _get_proj_version(path: Path) -> str: # Global environment object (dynaconf) ENV = AppEnvironment( envvar_prefix='PROXYSHOP', - settings_files=[PATH.SRC_DATA_ENV, PATH.SRC_DATA_ENV_DEFAULT], + settings_files=[PATH.SRC_DATA_ENV_DEFAULT, PATH.SRC_DATA_ENV], validators=[ Validator('API_GOOGLE', cast=str, default=''), Validator('API_AMAZON', cast=str, default=''), From c87156593395d5e6fb143c4a901e53e896e6b233 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Sep 2025 18:07:36 +0300 Subject: [PATCH 045/190] test(ProxyshopGUIApp): Fix test image assignment for Split layout --- src/gui/app.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 8736d1a6..d1d28b0d 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -58,6 +58,7 @@ from src.gui.tabs.tools import ToolsPanel from src.gui.test import TestApp from src.layouts import ( + SplitLayout, layout_map, assign_layout, join_dual_card_layouts, @@ -671,7 +672,10 @@ def test_all(self, deep: bool = False) -> None: continue # Grab the template class and start the render thread - layout.art_file = PATH.SRC_IMG / 'test.jpg' + test_image_path = PATH.SRC_IMG / 'test.jpg' + layout.art_file = test_image_path + if isinstance(layout, SplitLayout): + layout.art_files = [test_image_path, test_image_path] result = self.start_render(layout, template, loaded_class) if result is None: failures.append((card_name, card_case, 'Failed to render')) @@ -732,7 +736,10 @@ def test_target(self, card_type: str, template: TemplateDetails) -> None: continue # Start the render - layout.art_file = PATH.SRC_IMG / 'test.jpg' + test_image_path = PATH.SRC_IMG / 'test.jpg' + layout.art_file = test_image_path + if isinstance(layout, SplitLayout): + layout.art_files = [test_image_path, test_image_path] result = self.start_render( card=layout, template=template, From 7583d6ba24cd7ea34cdd223ebee60aee0415d8c5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Sep 2025 18:08:17 +0300 Subject: [PATCH 046/190] test(template_renders.toml): Add test cases for Tokens --- src/data/tests/template_renders.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/tests/template_renders.toml b/src/data/tests/template_renders.toml index 5a7c6c34..b71b609c 100644 --- a/src/data/tests/template_renders.toml +++ b/src/data/tests/template_renders.toml @@ -11,6 +11,8 @@ Mountain = "Basic Land" "Ghost Ark" = "Vehicle" "Graven Lore" = "Snow" Terminus = "Miracle" +"Elemental [TCMM] {37}" = "Dual Color Token" +"Tuktuk the Returned [TCM2] {15}" = "Legendary Token" [transform_front] "Docent of Perfection [EMN]" = "Moon Eldrazi DFC" From 1238fa66ad56978e1fb3c0868e3f36aa46c2583d Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 13 Sep 2025 17:54:49 +0300 Subject: [PATCH 047/190] feat: Add support for pawprint symbol --- fonts/Proxyglyph.ttf | Bin 46300 -> 46852 bytes src/_state.py | 2 +- src/enums/mtg.py | 3 ++- src/text_layers.py | 10 +++++++++- src/utils/mtg.py | 16 +++++++++++----- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/fonts/Proxyglyph.ttf b/fonts/Proxyglyph.ttf index ef951dcbdcaa4316da63ed0f02142b8aaf0aa291..b6688a94733df18b68c40b926d0d9828a1d6d4a6 100644 GIT binary patch delta 1777 zcmYjRX;4#F6h8N@Nyy6rA;b_Mh9r+og%B1oB1OzXY@pRT=vZ4Y2)2MxDa9FUx}c)9 zR*A({I~1KxrTkDV4CAzpRcl>vskMkZQbgR)I*x4}1k;;{PV-IfIp6ut`|dLD-K%$q zx(9?85CF*VKmk~-vtQjYRXi1-^ER$(Y+4q9>^}hD z-|he2--6>e{;mE-e}li?U+pjSXFQ`uACG2_CX6Zo`u6ng>T9}wsJB0`j@|>kI{~if zaf*DsjMKXs*5G_C)Wf=fj(_W)2lx~=KydAtZ-eb{9(u-<3ve-*U4qMSC74}>Yhzk3 z+{D=++#2Jz;oev_1oz=Dc!(!v@PDl6YhjCgND%EUhL1lPtKqQm^Ua$Yd|zy7-1>3} z088Dai?6-+8N7RA`8)UOHr!q|bib^6VDM)5or>B&VEYFPt8SgR3zaK+OICdZM~|KF zxpeu$#cdCoj`u^~p8!{D;L7z6S9{mM+WK`bcKJ)!SP98htS4ORY&eN4sA8htv#7!CSvL$+b1q1!NMv>M&UR^y=Y886{Ad;)LhOZZy8i9f)1@%?f7I7?i8TyMN4 zz9hagelQ_4p(J6yH(@w&L82#dW8$}oor&Fv!%2#yxTLzIBNJ2;awfVaEl8f7+>&zE zRAj0(?KkzOic__zd8v)59cD0F%#G%ew7j$}X%Evg(_1sB40lFnrY^HNb7Zo6a!*!a zR@W4zCDXFgGLS9Fc26tMalcZN+micu`cPiqjJsBswZ`ggw6<9bGOo^lSk z#4f#Sfor|%psU|CQlKc9Sl}sW8^gImO<{K7vci3Z|GYL22nc+aCZZL;_H4VATf%#3 zX!ie0JRmk>DHqD{JKBj`(o>UJ21B8Yz`$9ojyDF7k)Dx_@Q|Kij4)v>8UO+h2{J?^Q5)4U26Mb5I!d8-rO8Axl291 O19$mVfotgh_x697Kf+o7 delta 1247 zcmYjQ3rtgI6h8NsM+2ygDI45_Sbz{mh#F=MTii5^IAy#>5P1m&8A3&-Til#B;N2j{(Js&?*G?3 zBxmoFIv@bR6O|KyM3SyJ6JPik;Bqf|{iG>k3GEO$hdc*)rfP4v;BDf~7676S`TN<$ z8f{_opa{Tu2!I^Vt|(_X4ZCIlxJO{zGf$geyzO%MR{-=@jOXTS%Cr;(H2hvB#_9P* zhje)}A<>BdTVerP>kD!OMVo_ZoGMqJ;oJt)Z4 zic03OJ9j!R;BHO>hwTN{ZWq=cag7l~jO$;UnKoky;ZHP} z>xun@@fpdWRYRNEk9L#v8Nn{?ll229;u6|DAOdIbfPF@rvx?9dWiDxHi_{NPFLjN& z3P4?;Iw_nJ^(AGZPE!q(fhwa^l<+xix2%O9jltQ&S;fo!_o1kAPV-4M{CH#h_SE#m zWYe!LH}Asy0`~nO%vf|aRY#%LQ1@?}eLwwNIyf|3F-ndMn49S#G@Z-#Cp%c)JG}>9 z@_O0HXRFVQuip2Huf>n&*XY;nZ}LAMP!kvsm>gIUc#d(AVSB|A)ED#zlVl2+87NiE zUKWp4!0Kg(z6#Zhu{ILA3%oF2|duxBtgxIB1(OXn)N?c6c$ z^AKf7XUMXSC+B7IKIWa`wexy;%b^guJyaXo9p)4k_G;WNdAKONG2(lE3O|S6$sgfA z6?h0D1bRWMU@ejxsgJZqMMPCc%|^4LP0_yz)x!3etugg6)>w6H-|mFnJ#l0_EB<8s zXo4+K{aX6#YEf2FW70zM)Eh%76XG~=mRK*=HH)u{mn0HNt)xR@mDr>nQjRoM`i`_h zYLZ@&j!M^MUNVMkmrO0w%DQCBsjO6eswwq`oGXu&%j6mID*3nasXb2>o(j35LUCR( zqOdAKxl@^`Y<9>s6EvhB>vq=cT6CohG@cg@)led%xo2P~CZbX~;aFX}{ z%>a?c-(7YPHsj1CgE`wAL^B$Cczcua3`dtjhT47^Nqp@$ R%l SymbolColorMap: return SymbolColorMap() @tracked_prop - def symbol_map(self) -> dict[str, tuple[str, list[ColorObject]]]: + def symbol_map(self) -> dict[str, tuple[str, list[ColorObject | None]]]: """dict[str, tuple[str, list[ColorObject]]]: Uses the symbol map and mana_colors to map symbol character strings and colors to their Scryfall symbol string.""" return {sym: (n, get_symbol_colors(sym, n, self.mana_colors)) for sym, n in self.mana_symbols.items()} diff --git a/src/enums/mtg.py b/src/enums/mtg.py index f2f37811..5fab5942 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -269,7 +269,8 @@ class CardTypesSuper(StrConstant): "{2/G}": "QqWV", "{S}": "omn", "{Q}": "ol", - "{CHAOS}": "?" + "{CHAOS}": "?", + "{P}": "`", } """ diff --git a/src/text_layers.py b/src/text_layers.py index a12b08d6..bfcb0b1b 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -149,7 +149,15 @@ def kw_font_bold(self) -> str | None: @cached_property def kw_symbol_map(self) -> dict[str, tuple[str, list[ColorObject]]]: """Symbol map to use for formatting mana symbols.""" - return self.kwargs.get("symbol_map", CON.symbol_map) + if not (symbol_map := self.kwargs.get("symbol_map")): + symbol_map = {} + # Use text layer's color for all None colors + for key, value in CON.symbol_map.items(): + symbol_map[key] = ( + value[0], + [item if item is not None else self.TI.color for item in value[1]], + ) + return symbol_map """ * Checks diff --git a/src/utils/mtg.py b/src/utils/mtg.py index a77194f5..93d8d071 100644 --- a/src/utils/mtg.py +++ b/src/utils/mtg.py @@ -1,6 +1,7 @@ """ * MTG Related Utiltiies """ + from src.schema.colors import SymbolColorMap, ColorObject from src.enums.mtg import CardTextPatterns as _P @@ -9,7 +10,9 @@ """ -def get_symbol_colors(symbol: str, chars: str, color_map: SymbolColorMap) -> list[ColorObject]: +def get_symbol_colors( + symbol: str, chars: str, color_map: SymbolColorMap +) -> list[ColorObject | None]: """Determines the colors of a symbol (represented as Scryfall string) and returns an array Symbol colors. Args: @@ -31,12 +34,15 @@ def get_symbol_colors(symbol: str, chars: str, color_map: SymbolColorMap) -> lis elif symbol == "{Q}": # Untap symbol return [color_map.primary, color_map.secondary] + elif symbol == "{P}": + # Pawprint symbol + return [None] # Normal mana symbol if normal_symbol_match := _P.MANA_NORMAL.match(symbol): return [ color_map.colors[normal_symbol_match[1]], - color_map.colors_inner[normal_symbol_match[1]] + color_map.colors_inner[normal_symbol_match[1]], ] # Hybrid @@ -47,14 +53,14 @@ def get_symbol_colors(symbol: str, chars: str, color_map: SymbolColorMap) -> lis colors[hybrid_match[2]], colors[hybrid_match[1]], color_map.colors_inner[hybrid_match[1]], - color_map.colors_inner[hybrid_match[2]] + color_map.colors_inner[hybrid_match[2]], ] # Phyrexian if phyrexian_match := _P.MANA_PHYREXIAN.match(symbol): return [ color_map.hybrid[phyrexian_match[1]], - color_map.hybrid_inner[phyrexian_match[1]] + color_map.hybrid_inner[phyrexian_match[1]], ] # Phyrexian hybrid @@ -63,7 +69,7 @@ def get_symbol_colors(symbol: str, chars: str, color_map: SymbolColorMap) -> lis color_map.colors[phyrexian_hybrid_match[2]], color_map.colors[phyrexian_hybrid_match[1]], color_map.colors_inner[phyrexian_hybrid_match[1]], - color_map.colors_inner[phyrexian_hybrid_match[2]] + color_map.colors_inner[phyrexian_hybrid_match[2]], ] # Weird situation? From 8429278e133a35a6b6f58e7429e17f2c89a9cb04 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 13 Sep 2025 17:56:36 +0300 Subject: [PATCH 048/190] fix(process_card_data): Add a temporary workaround for incorrect mana costs in Adventure Land Scryfall data --- src/cards.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cards.py b/src/cards.py index 019ccaee..a00d3c8d 100644 --- a/src/cards.py +++ b/src/cards.py @@ -210,6 +210,25 @@ def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: # Battle layout if 'Battle' in card_face['type_line']: data['layout'] = 'battle' + + # Fix Adventure Land mana costs locally, as Scryfall seems unwilling to + # fix the issue on their end even after reporting it and waiting for months. + # Once Scryfall corrects their data this fix can be removed. + if data["layout"] == "adventure": + card_faces = data["card_faces"] + main_face = card_faces[0] + adventure_face = card_faces[1] + if ( + main_face["type_line"].startswith("Land ") + and main_face["mana_cost"] + and not adventure_face["mana_cost"] + ): + # Swap the Land and Adventure faces' mana costs + main_face["mana_cost"], adventure_face["mana_cost"] = ( + adventure_face["mana_cost"], + main_face["mana_cost"], + ) + return data # Add Mutate layout @@ -227,10 +246,12 @@ def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: # Check for Saga Creature layout if 'Saga' in type_line and 'Creature' in type_line: data['layout'] = 'saga' + return data # Check for Station layout if 'STATION ' in data.get('oracle_text', ''): data['layout'] = 'station' + return data # Return updated data return data From 964fb35cb0cb0b17de5148343bf89ab5ac57e527 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 27 Sep 2025 07:03:45 +0300 Subject: [PATCH 049/190] feat: Add a setting to use the printed name of the card when it's available --- src/_config.py | 3 +++ src/data/config/app.toml | 6 ++++++ src/layouts.py | 18 ++++++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/_config.py b/src/_config.py index 2fe0a6a7..6a6013c0 100644 --- a/src/_config.py +++ b/src/_config.py @@ -52,6 +52,9 @@ def update_definitions(self): # APP - DATA self.lang = self.file.get("APP.DATA", "Scryfall.Language", fallback="en") + self.use_printed_name = self.file.getboolean( + "APP.DATA", "Scryfall.Use.Printed.Name", fallback=False + ) self.scry_sorting = self.get_option( "APP.DATA", "Scryfall.Sorting", ScryfallSorting ) diff --git a/src/data/config/app.toml b/src/data/config/app.toml index a6893ee1..0e4725f6 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -43,6 +43,12 @@ type = "options" default = "en" options = ["en", "es", "fr", "de", "it", "pt", "jp", "kr", "ru", "cs", "ct"] +[DATA."Scryfall.Use.Printed.Name"] +title = "Use printed name" +desc = """Use the printed name of the card, if it's available in the Scryfall data, instead of the canonical English name. Non-English cards use printed name regardless of this setting's value.""" +type = "bool" +default = 0 + [DATA."Scryfall.Sorting"] title = "Scryfall Sorting" desc = """The method used to sort the Scryfall card lookup results.""" diff --git a/src/layouts.py b/src/layouts.py index 598ef543..73c41a84 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -2,6 +2,7 @@ * Card Layout Data """ # Standard Library Imports +from collections.abc import Mapping from datetime import date, datetime import re from typing import Any, NotRequired, TypedDict, Union @@ -63,6 +64,11 @@ class ClassLine(TypedDict): level: str text: str +def get_card_name(data: Mapping[str,Any], use_printed_name: bool = CFG.use_printed_name) -> str: + if use_printed_name and (printed_name := data.get("printed_name", None)): + return printed_name + return data.get("name", "") + """ * Layout Processing """ @@ -314,12 +320,12 @@ def promo_types(self) -> list[str]: @cached_property def name(self) -> str: """Card name, supports alternate language source.""" - return self.card.get('printed_name', self.name_raw) if self.is_alt_lang else self.name_raw + return get_card_name(self.card, CFG.use_printed_name or self.is_alt_lang) @cached_property def name_raw(self) -> str: """Card name, enforced English representation.""" - return self.card.get('name', '') + return get_card_name(self.card, False) @cached_property def nickname(self) -> str: @@ -1113,9 +1119,7 @@ def mana_adventure(self) -> str: @cached_property def name_adventure(self) -> str: """Name of the Adventure side.""" - if self.is_alt_lang and 'printed_name' in self.adventure: - return self.adventure.get('printed_name', '') - return self.adventure.get('name', '') + return get_card_name(self.adventure, CFG.use_printed_name or self.is_alt_lang) @cached_property def type_line_adventure(self) -> str: @@ -1568,9 +1572,7 @@ def name(self) -> str: @cached_property def names(self) -> list[str]: """Both side names.""" - if self.is_alt_lang: - return [c.get('printed_name', c.get('name', '')) for c in self.cards] - return [c.get('name', '') for c in self.cards] + return [get_card_name(c, CFG.use_printed_name or self.is_alt_lang) for c in self.cards] @cached_property def name_raw(self) -> str: From d06f5f57104fed649133bffc03a017086672c304 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 29 Sep 2025 15:28:54 +0300 Subject: [PATCH 050/190] feat: Validate Scryfall data using Pydantic models and assign normal layout to meld results BREAKING CHANGE: --- src/cards.py | 116 ++++---- src/frame_logic.py | 38 ++- src/layouts.py | 279 ++++++++++--------- src/utils/manual_actions.py | 26 +- src/utils/scryfall.py | 532 +++++++++++++++++++++++++++++------- 5 files changed, 682 insertions(+), 309 deletions(-) diff --git a/src/cards.py b/src/cards.py index a00d3c8d..eb58b47a 100644 --- a/src/cards.py +++ b/src/cards.py @@ -5,16 +5,15 @@ # Standard Library Imports from contextlib import suppress from pathlib import Path -from typing import TypedDict, Any +from typing import Any, TypedDict # Third Party Imports from omnitils.strings import normalize_str -import yarl # Local Imports from src._config import AppConfig from src.console import TerminalConsole, msg_warn -from src.enums.mtg import TransformIcons, non_italics_abilities, CardTextPatterns +from src.enums.mtg import CardTextPatterns, TransformIcons, non_italics_abilities from src.gui.console import GUIConsole from src.schema.colors import ColorObject from src.utils import scryfall @@ -55,7 +54,11 @@ class FrameDetails(TypedDict): """ -def get_card_data(card: CardDetails, cfg: AppConfig, logger: GUIConsole | TerminalConsole | None = None) -> dict[str,Any] | None: +def get_card_data( + card: CardDetails, + cfg: AppConfig, + logger: GUIConsole | TerminalConsole | None = None, +) -> scryfall.ScryfallCard | None: """Fetch card data from the Scryfall API. Args: @@ -146,7 +149,7 @@ def parse_card_info(file_path: Path) -> CardDetails: """ -def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: +def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfall.ScryfallCard: """Process any additional required data before sending it to the layout object. Args: @@ -160,97 +163,108 @@ def process_card_data(data: dict[str,Any], card: CardDetails) -> dict[str,Any]: name_normalized = normalize_str(card['name'], no_space=True) # Modify meld card data to fit transform layout - if data['layout'] == 'meld': + if data.layout == 'meld': # Ignore tokens and other objects - front: list[dict[str,Any]] = [] - back: dict[str,Any] | None = None - for part in data.get('all_parts', []): - if part.get('component') == 'meld_part': + front: list[scryfall.ScryfallRelatedCard] = [] + back: scryfall.ScryfallRelatedCard | None = None + for part in data.all_parts if data.all_parts else []: + if part.component == 'meld_part': front.append(part) - if part.get('component') == 'meld_result': + if part.component == 'meld_result': back = part # Figure out if card is a front or a back - faces: list[dict[str,Any] | None] = [front[0], back] if back and ( - name_normalized == normalize_str(back.get('name', ''), True) or - name_normalized == normalize_str(front[0].get('name', ''), True) - ) else [front[1], back] + is_back = back and name_normalized == normalize_str(back.name, no_space=True) - # Pull JSON data for each face and set object to card_face - data['card_faces'] = [{ - **scryfall.get_uri_object(yarl.URL(face['uri'])), - 'object': 'card_face' - } for face in faces if face] + faces: list[scryfall.ScryfallRelatedCard | None] = [front[0], back] if ( + is_back or + name_normalized == normalize_str(front[0].name, no_space=True) + ) else [front[1], back] - # Add meld transform icon if none provided - if not any([bool(n in TransformIcons) for n in data.get('frame_effects', [])]): - data.setdefault('frame_effects', []).append(TransformIcons.MELD) - data['layout'] = 'transform' + if is_back and back: + data = scryfall.get_card_via_url(str(back.uri)) + data.layout = "normal" + else: + # Pull JSON data for each face and set object to card_face + data.card_faces = [] + for face in faces: + if face: + face_data = scryfall.get_card_via_url(str(face.uri)) + face_data_dict = face_data.model_dump() + face_data_dict["object"] = "card_face" + data.card_faces.append(scryfall.ScryfallCardFace(**face_data_dict)) + + # Add meld transform icon if none provided + if not data.frame_effects or not any([bool(n in TransformIcons) for n in data.frame_effects]): + data.frame_effects = ["meld"] + data.layout = "transform" # Check for alternate MDFC / Transform layouts - if 'card_faces' in data: + if data.card_faces: # Select the corresponding face - card_faces = data['card_faces'] + card_faces = data.card_faces # Default to front face i = 0 card_face = card_faces[0] for idx, face in enumerate(card_faces): - if normalize_str(face.get('name', ''), True) == name_normalized: + if normalize_str(face.name, True) == name_normalized: i = idx card_face = face break # Decide if this is a front face - data['front'] = True if i == 0 else False + data.front = i == 0 # Transform / MDFC Planeswalker layout - if 'Planeswalker' in card_face.get('type_line', ''): - data['layout'] = 'planeswalker_tf' if data.get('layout') == 'transform' else 'planeswalker_mdfc' - # Transform Saga layout - if 'Saga' in card_face['type_line']: - data['layout'] = 'saga' - # Battle layout - if 'Battle' in card_face['type_line']: - data['layout'] = 'battle' + if card_face.type_line: + if 'Planeswalker' in card_face.type_line: + data.layout = 'planeswalker_tf' if data.layout == 'transform' else 'planeswalker_mdfc' + # Transform Saga layout + elif 'Saga' in card_face.type_line: + data.layout = 'saga' + # Battle layout + elif 'Battle' in card_face.type_line: + data.layout = 'battle' # Fix Adventure Land mana costs locally, as Scryfall seems unwilling to # fix the issue on their end even after reporting it and waiting for months. # Once Scryfall corrects their data this fix can be removed. - if data["layout"] == "adventure": - card_faces = data["card_faces"] + if data.layout == "adventure": + card_faces = data.card_faces main_face = card_faces[0] adventure_face = card_faces[1] if ( - main_face["type_line"].startswith("Land ") - and main_face["mana_cost"] - and not adventure_face["mana_cost"] + main_face.type_line and + main_face.type_line.startswith("Land ") + and main_face.mana_cost + and not adventure_face.mana_cost ): # Swap the Land and Adventure faces' mana costs - main_face["mana_cost"], adventure_face["mana_cost"] = ( - adventure_face["mana_cost"], - main_face["mana_cost"], + main_face.mana_cost, adventure_face.mana_cost = ( + adventure_face.mana_cost, + main_face.mana_cost, ) return data # Add Mutate layout - if 'Mutate' in data.get('keywords', []): - data['layout'] = 'mutate' + if 'Mutate' in data.keywords: + data.layout = 'mutate' return data - type_line = data.get('type_line', '') + type_line = data.type_line # Add Planeswalker layout if 'Planeswalker' in type_line: - data['layout'] = 'planeswalker' + data.layout = 'planeswalker' return data # Check for Saga Creature layout if 'Saga' in type_line and 'Creature' in type_line: - data['layout'] = 'saga' + data.layout = 'saga' return data # Check for Station layout - if 'STATION ' in data.get('oracle_text', ''): - data['layout'] = 'station' + if data.oracle_text and 'STATION ' in data.oracle_text: + data.layout = 'station' return data # Return updated data diff --git a/src/frame_logic.py b/src/frame_logic.py index de2c74d1..6a5c6226 100644 --- a/src/frame_logic.py +++ b/src/frame_logic.py @@ -1,15 +1,13 @@ """ * Frame Logic Module """ -# Standard Library Imports from collections.abc import Iterable, Iterator from functools import cache, cached_property -from typing import Any -# Local Imports from src.cards import FrameDetails from src.enums.layers import LAYERS from src.enums.mtg import Rarity +from src.utils.scryfall import MagicColor, ScryfallCard, ScryfallCardFace """ * Planned Utility Classes @@ -195,8 +193,8 @@ def get_color_identity_nonland( mana_cost: str, type_line: str, oracle_text: str, - color_indicator: list[str], - color_list: list[str] + color_indicator: list[MagicColor], + color_list: list[MagicColor] ) -> str: """Get the assumed color identity of Non-Land card based on a priority list of factors. @@ -277,7 +275,7 @@ def check_hybrid_mana_cost(color_identity: str | list[str], mana_cost: str) -> b """ -def get_frame_details(card: dict[str,Any]) -> FrameDetails: +def get_frame_details(card: ScryfallCard | ScryfallCardFace) -> FrameDetails: """Figure out which layers to use for pinlines, background, twins and define the color identity. Pass the card to an appropriate function based on card type. @@ -287,12 +285,12 @@ def get_frame_details(card: dict[str,Any]) -> FrameDetails: Returns: Dict containing FrameDetails representing the card's frame makeup. """ - if 'Land' in card.get('type_line', ''): + if card.type_line and 'Land' in card.type_line: return get_frame_details_land(card) return get_frame_details_nonland(card) -def get_frame_details_land(card: dict[str,Any]) -> FrameDetails: +def get_frame_details_land(card: ScryfallCard | ScryfallCardFace) -> FrameDetails: """Card is a Land card, must check a variety of cases to identify the appropriate color identity. Args: @@ -302,7 +300,7 @@ def get_frame_details_land(card: dict[str,Any]) -> FrameDetails: Dict containing FrameDetails representing the card's frame makeup. """ # Grab the attributes we need - type_line, oracle_text = card.get('type_line', ''), card.get('oracle_text', '') + type_line, oracle_text = card.type_line or "", card.oracle_text or "" twins = colors_tapped = basic_identity = '' result: FrameDetails = { "background": LAYERS.LAND, @@ -450,7 +448,7 @@ def get_frame_details_land(card: dict[str,Any]) -> FrameDetails: return result -def get_frame_details_nonland(card: dict[str,Any]) -> FrameDetails: +def get_frame_details_nonland(card: ScryfallCard | ScryfallCardFace) -> FrameDetails: """Get frame details related to a Non-Land card. Must discern the frame color identity, for example Noble Hierarch's color identity is [W, U, G] on Scryfall, but the frame is Green. @@ -458,17 +456,17 @@ def get_frame_details_nonland(card: dict[str,Any]) -> FrameDetails: card: Dict containing Scryfall data for this card. """ # Establish the attributes we need - mana_cost = card.get('mana_cost', '') - type_line = card.get('type_line', '') - oracle_text = card.get('oracle_text', '') + mana_cost = card.mana_cost or "" + type_line = card.type_line or "" + oracle_text = card.oracle_text or "" # Establish the initial assumed color identity color_identity = get_color_identity_nonland( mana_cost=mana_cost, type_line=type_line, oracle_text=oracle_text, - color_indicator=card.get('color_indicator', []), - color_list=card.get('color_identity', card.get('colors', [])) + color_indicator=card.color_indicator or [], + color_list=card.color_identity if isinstance(card, ScryfallCard) else card.colors or [] ) # Default results @@ -514,7 +512,7 @@ def get_frame_details_nonland(card: dict[str,Any]) -> FrameDetails: hybrid = check_hybrid_color_card( color_identity=color_identity, mana_cost=mana_cost, - is_dfc=bool(card.get('object') == 'card_face') + is_dfc=card.object == 'card_face' ) # Is this card hybrid? @@ -560,7 +558,7 @@ def get_frame_details_nonland(card: dict[str,Any]) -> FrameDetails: """ -def get_special_rarity(rarity: str, card: dict[str,Any]) -> str: +def get_special_rarity(rarity: str, card: ScryfallCard) -> str: """Control for special rarities. Args: @@ -572,13 +570,13 @@ def get_special_rarity(rarity: str, card: dict[str,Any]) -> str: """ if rarity == Rarity.S: # Timeshifted cards - if card.get('frame') == '1997': + if card.frame == '1997': return Rarity.T # Championship cards - if 'Champion' in card.get('set_name', ''): + if 'Champion' in card.set_name: return Rarity.M # Masterpiece - if card.get('set_type') == 'masterpiece': + if card.set_type == 'masterpiece': return Rarity.M # Case like Prismatic Piper return Rarity.C diff --git a/src/layouts.py b/src/layouts.py index 73c41a84..12d50a64 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1,26 +1,27 @@ """ * Card Layout Data """ -# Standard Library Imports -from collections.abc import Mapping -from datetime import date, datetime import re -from typing import Any, NotRequired, TypedDict, Union -from re import Match +from collections.abc import Sequence +from datetime import date +from functools import cached_property from os import path as osp from pathlib import Path -from functools import cached_property +from pprint import pprint +from re import Match +from typing import Any, NotRequired, TypedDict, Union -# Third Party Imports from omnitils.strings import get_line, get_lines, normalize_str, strip_lines -# Local Imports from src import CFG, CON, CONSOLE, ENV, PATH -from src.cards import CardDetails, FrameDetails, get_card_data, parse_card_info, process_card_data +from src.cards import ( + CardDetails, + FrameDetails, + get_card_data, + parse_card_info, + process_card_data, +) from src.console import msg_error, msg_success, msg_warn -from src.utils.hexapi import get_watermark_svg, get_watermark_svg_from_set -from src.utils.manual_actions import manually_modify_dict -from src.utils.scryfall import get_cards_oracle from src.enums.layers import LAYERS from src.enums.mtg import ( CardTextPatterns, @@ -28,16 +29,27 @@ CardTypesSuper, LayoutScryfall, LayoutType, - planeswalkers_tall, Rarity, - TransformIcons) + TransformIcons, + planeswalkers_tall, +) from src.enums.settings import CollectorMode, WatermarkMode from src.frame_logic import ( + check_hybrid_mana_cost, get_frame_details, + get_mana_cost_colors, get_ordered_colors, get_special_rarity, - check_hybrid_mana_cost, - get_mana_cost_colors) +) +from src.utils.hexapi import get_watermark_svg, get_watermark_svg_from_set +from src.utils.manual_actions import manually_modify_model +from src.utils.scryfall import ( + FrameEffect, + MagicColor, + ScryfallCard, + ScryfallCardFace, + get_cards_oracle, +) class PowerToughness(TypedDict): @@ -64,10 +76,10 @@ class ClassLine(TypedDict): level: str text: str -def get_card_name(data: Mapping[str,Any], use_printed_name: bool = CFG.use_printed_name) -> str: - if use_printed_name and (printed_name := data.get("printed_name", None)): +def get_card_name(data: ScryfallCard | ScryfallCardFace, use_printed_name: bool = CFG.use_printed_name) -> str: + if use_printed_name and (printed_name := data.printed_name): return printed_name - return data.get("name", "") + return data.name """ * Layout Processing @@ -102,7 +114,7 @@ def assign_layout(filename: Path) -> "str | CardLayout": if CFG.manually_edit_card_data: try: - scryfall = manually_modify_dict(scryfall, CFG.manual_text_editor) + scryfall = manually_modify_model(scryfall, CFG.manual_text_editor) except Exception as e: CONSOLE.log_exception(e) return msg_error(name_failed, reason="Manual card data modification failed") @@ -110,9 +122,9 @@ def assign_layout(filename: Path) -> "str | CardLayout": scryfall = process_card_data(scryfall, card) # Instantiate layout object - if scryfall.get('layout', 'None') in layout_map: + if scryfall.layout in layout_map: try: - layout = layout_map[scryfall['layout']](scryfall, card) + layout = layout_map[scryfall.layout](scryfall, card) except Exception as e: # Couldn't instantiate layout object CONSOLE.log_exception(e) @@ -183,7 +195,7 @@ def is_transform(self) -> bool: def is_mdfc(self) -> bool: return False - def __init__(self, scryfall: dict[str,Any], file: CardDetails): + def __init__(self, scryfall: ScryfallCard, file: CardDetails): # Establish core properties self._file = file @@ -209,7 +221,7 @@ def file(self) -> CardDetails: return self._file @cached_property - def scryfall(self) -> dict[str,Any]: + def scryfall(self) -> ScryfallCard: """Card data fetched from Scryfall.""" return self._scryfall @@ -226,7 +238,9 @@ def art_file(self) -> Path: @cached_property def scryfall_scan(self) -> str: """Scryfall large image scan, if available.""" - return self.card.get('image_uris', {}).get('large', '') + if self.card.image_uris: + return str(self.card.image_uris.large) + return "" """ * Set Data @@ -235,44 +249,41 @@ def scryfall_scan(self) -> str: @cached_property def set(self) -> str: """Card set code, uppercase enforced, falls back to 'MTG' if missing.""" - return self.scryfall.get('set', 'MTG').upper() + return self.scryfall.set.upper() @cached_property def set_data(self) -> dict[str,Any]: """Set data from the current hexproof.io data file.""" - return CON.set_data.get(self.scryfall.get('set', 'mtg').lower(), {}) + return CON.set_data.get(self.scryfall.set.lower(), {}) @cached_property def set_type(self) -> str: """str: Type of set the card was printed in, e.g. promo, draft_innovation, etc.""" - return self.scryfall.get('set_type', '') + return self.scryfall.set_type @cached_property def date(self) -> date: """date: The date this card was released.""" - _released = self.scryfall.get('released_at') - if not _released: - return date.today() - return datetime.strptime(_released, '%Y-%m-%d').date() + return self.scryfall.released_at """ * Gameplay Info """ @cached_property - def card(self) -> dict[str,Any]: + def card(self) -> ScryfallCard | ScryfallCardFace: """Main card data object to pull most relevant data from.""" - if faces := self.scryfall.get('card_faces', []): + if faces := self.scryfall.card_faces: # Card with multiple faces, first index is always front side - matching_face: dict[str, Any] | None = None + matching_face: ScryfallCardFace | None = None for face in faces: - if normalize_str(face['name']) == normalize_str(self.input_name): + if normalize_str(face.name) == normalize_str(self.input_name): matching_face = face break if not matching_face: - face_listing = '\n'.join(f" - {face['name']}" for face in faces) + face_listing = '\n'.join(f" - {face.name}" for face in faces) CONSOLE.update( msg_warn( f"""None of the card faces @@ -289,29 +300,30 @@ def card(self) -> dict[str,Any]: return self.scryfall @cached_property - def first_print(self) -> dict[str,Any]: + def first_print(self) -> ScryfallCard | None: """Card data fetched from Scryfall representing the first print of this card.""" - first = get_cards_oracle(self.scryfall.get('oracle_id', '')) - return first[0] if first else {} + if self.scryfall.oracle_id: + cards = get_cards_oracle(str(self.scryfall.oracle_id)) + return cards[0] if cards else None """ * Card Collections """ @cached_property - def frame_effects(self) -> list[str]: + def frame_effects(self) -> list[FrameEffect]: """Array of frame effects, e.g. nyxtouched, snow, etc.""" - return self.scryfall.get('frame_effects', []) + return self.scryfall.frame_effects or [] @cached_property def keywords(self) -> list[str]: """Array of keyword abilities, e.g. Flying, Haste, etc.""" - return self.scryfall.get('keywords', []) + return self.scryfall.keywords @cached_property def promo_types(self) -> list[str]: """list[str]: Promo types this card matches, e.g. stamped, datestamped, etc.""" - return self.scryfall.get('promo_types', []) + return self.scryfall.promo_types or [] """ * Text Info @@ -331,7 +343,9 @@ def name_raw(self) -> str: def nickname(self) -> str: """Nickname, typically set inside template logic but can be passed in filename.""" # Todo: Add user-provided text - return self.card.get('flavor_name', '') + if isinstance(self.card, ScryfallCard): + return self.card.flavor_name or "" + return "" @cached_property def display_name(self) -> str: @@ -346,22 +360,22 @@ def input_name(self) -> str: @cached_property def mana_cost(self) -> str: """Scryfall formatted card mana cost.""" - return self.card.get('mana_cost', '') + return self.card.mana_cost or "" @cached_property def oracle_text(self) -> str: """Card rules text, supports alternate language source.""" - return self.card.get('printed_text', self.oracle_text_raw) if self.is_alt_lang else self.oracle_text_raw + return self.card.printed_text if self.is_alt_lang and self.card.printed_text else self.oracle_text_raw @cached_property def oracle_text_raw(self) -> str: """Card rules text, enforced English representation.""" - return self.card.get('oracle_text', '') + return self.card.oracle_text or "" @cached_property def flavor_text(self) -> str: """Card flavor text, alternate language version shares the same key.""" - return self.card.get('flavor_text', '') + return self.card.flavor_text or "" @cached_property def rules_text(self) -> str: @@ -371,12 +385,12 @@ def rules_text(self) -> str: @cached_property def power(self) -> str: """Creature power, if provided.""" - return self.card.get('power', '') + return self.card.power or "" @cached_property def toughness(self) -> str: """Creature toughness, if provided.""" - return self.card.get('toughness', '') + return self.card.toughness or "" """ * Card Types @@ -385,12 +399,12 @@ def toughness(self) -> str: @cached_property def type_line(self) -> str: """Card type line, supports alternate language source.""" - return self.card.get('printed_type_line', self.type_line_raw) if self.is_alt_lang else self.type_line_raw + return self.card.printed_type_line if self.is_alt_lang and self.card.printed_type_line else self.type_line_raw @cached_property def type_line_raw(self) -> str: """Card type line, enforced English representation.""" - return self.card.get('type_line', '') + return self.card.type_line or "" @cached_property def types_raw(self) -> list[str]: @@ -421,14 +435,16 @@ def subtypes(self) -> list[str]: """ @cached_property - def color_identity(self) -> list[str]: + def color_identity(self) -> list[MagicColor]: """Commander relevant color identity array, e.g. [W, U].""" - return self.card.get('color_identity', []) + if isinstance(self.card, ScryfallCard): + return self.card.color_identity + return [] @cached_property def color_indicator(self) -> str: """Color indicator identity , e.g. "WU".""" - return get_ordered_colors(self.card.get('color_indicator', [])) + return get_ordered_colors(self.card.color_indicator or []) """ * Collector Info @@ -444,7 +460,7 @@ def symbol_code(self) -> str: @cached_property def lang(self) -> str: """Card print language, uppercase enforced, falls back to settings defined value.""" - return self.scryfall.get('lang', CFG.lang).upper() + return self.scryfall.lang.upper() @cached_property def rarity(self) -> str: @@ -456,7 +472,7 @@ def rarity(self) -> str: @cached_property def rarity_raw(self) -> str: """Card rarity, doesn't interpret 'special' rarities.""" - return self.scryfall.get('rarity', Rarity.C) + return self.scryfall.rarity @cached_property def rarity_letter(self) -> str: @@ -469,7 +485,7 @@ def artist(self) -> str: if self.file.get('artist'): return self.file['artist'] - artist = self.card.get('artist', 'Unknown') + artist = self.card.artist count: set[str] = set() # Check for duplicate last names @@ -478,7 +494,7 @@ def artist(self) -> str: count.add(w) return ' '.join(count) - return artist + return artist or "Unknown" @cached_property def collector_number(self) -> int: @@ -490,7 +506,7 @@ def collector_number(self) -> int: @cached_property def collector_number_raw(self) -> str | None: """str | None: Card number assigned within release set. Raw string representation, allows non-digits.""" - return self.scryfall.get('collector_number') + return self.scryfall.collector_number @cached_property def card_count(self) -> int | None: @@ -567,7 +583,7 @@ def watermark(self) -> str | None: @cached_property def watermark_raw(self) -> str | None: """Name of the card's watermark from raw Scryfall data, if provided.""" - return self.card.get('watermark') + return self.card.watermark @cached_property def watermark_svg(self) -> Path | None: @@ -590,9 +606,9 @@ def _find_watermark_svg(wm: str) -> Path | None: wm = wm.lower() # Special case watermarks - if wm in ['set', 'symbol']: + if self.first_print and wm in ['set', 'symbol']: return get_watermark_svg_from_set( - self.first_print.get('set', self.set) if wm == 'set' else self.set) + self.first_print.set if wm == 'set' else self.set) # Look for normal watermark return get_watermark_svg(wm) @@ -676,7 +692,7 @@ def is_vehicle(self) -> bool: @cached_property def is_promo(self) -> bool: """True if card is a promotional print.""" - if self.scryfall.get('promo', False): + if self.scryfall.promo: return True if self.set_type == 'promo': return True @@ -687,7 +703,7 @@ def is_promo(self) -> bool: @cached_property def is_front(self) -> bool: """True if card is front face.""" - return bool(self.scryfall.get('front', True)) + return self.scryfall.front @cached_property def is_alt_lang(self) -> bool: @@ -711,9 +727,7 @@ def is_emblem(self) -> bool: @cached_property def is_nyx(self) -> bool: """True if card has Nyx enchantment background texture.""" - if 'nyxtouched' in self.frame_effects: - return True - # Nyxtouched often not provided, check for 'Enchantment Creature' + # Check for 'Enchantment Creature' return bool(self.is_creature and 'Enchantment' in self.type_line_raw) @cached_property @@ -724,7 +738,7 @@ def is_companion(self) -> bool: @cached_property def is_miracle(self) -> bool: """True if card is a 'Miracle' card.""" - return bool("Miracle" in self.frame_effects) + return bool("miracle" in self.frame_effects) @cached_property def is_snow(self) -> bool: @@ -771,18 +785,17 @@ def transform_icon(self) -> str: if effect in TransformIcons: return effect # Cover special Havengul Lab case - if self.scryfall.get('oracle_id') == 'e71ac446-02a4-4468-8d29-f28b21617665': + if self.scryfall.oracle_id == 'e71ac446-02a4-4468-8d29-f28b21617665': return TransformIcons.UPSIDEDOWN # All other cards use the triangle (originally: `convertdfc`) return TransformIcons.CONVERT @cached_property - def other_face(self) -> dict[str,Any]: + def other_face(self) -> ScryfallCardFace | None: """Card data from opposing face if provided.""" - for face in self.scryfall.get('card_faces', []): - if face.get('name') != self.name_raw: + for face in self.scryfall.card_faces or []: + if face.name != self.name_raw: return face - return {} @cached_property def other_face_frame(self) -> FrameDetails | dict[str, Any]: @@ -797,41 +810,51 @@ def other_face_twins(self) -> str: @cached_property def other_face_mana_cost(self) -> str: """Mana cost of opposing face.""" - return self.other_face.get('mana_cost', '') + if self.other_face: + return self.other_face.mana_cost or "" + return "" @cached_property def other_face_type_line(self) -> str: """Type line of opposing face.""" - return self.other_face.get('type_line', '') + if self.other_face: + return self.other_face.type_line or "" + return "" @cached_property def other_face_type_line_raw(self) -> str: """Type line of opposing face, English language enforced.""" - if self.is_alt_lang: - return self.other_face.get('printed_type_line', self.other_face_type_line) + if self.other_face and self.is_alt_lang: + return self.other_face.printed_type_line or self.other_face_type_line return self.other_face_type_line @cached_property def other_face_oracle_text(self) -> str: """Rules text of opposing face.""" - if self.is_alt_lang: - return self.other_face.get('printed_text', self.other_face_oracle_text_raw) + if self.other_face and self.is_alt_lang: + return self.other_face.printed_text or self.other_face_oracle_text_raw return self.other_face_oracle_text_raw @cached_property def other_face_oracle_text_raw(self) -> str: """Rules text of opposing face.""" - return self.other_face.get('oracle_text', '') + if self.other_face: + return self.other_face.oracle_text or "" + return "" @cached_property def other_face_power(self) -> str: """Creature power of opposing face, if provided.""" - return self.other_face.get('power', '') + if self.other_face: + return self.other_face.power or "" + return "" @cached_property def other_face_toughness(self) -> str: """Creature toughness of opposing face, if provided.""" - return self.other_face.get('toughness', '') + if self.other_face: + return self.other_face.toughness or "" + return "" @cached_property def other_face_left(self) -> str: @@ -868,7 +891,7 @@ def card_class(self) -> str: @cached_property def oracle_text_unprocessed(self) -> str: """str: Unaltered text to split between oracle and mutate.""" - return self.card.get('printed_text', self.oracle_text_raw) if self.is_alt_lang else self.oracle_text_raw + return self.card.printed_text if self.is_alt_lang and self.card.printed_text else self.oracle_text_raw @cached_property def oracle_text(self) -> str: @@ -906,7 +929,7 @@ def oracle_text(self) -> str: @cached_property def proto_details(self) -> ProtoDetails: """Returns dictionary containing prototype data and separated oracle text.""" - proto_text, rules_text = self.card.get('oracle_text', "\n").split("\n", 1) + proto_text, rules_text = self.card.oracle_text.split("\n", 1) if self.card.oracle_text else ("", "") match = CardTextPatterns.PROTOTYPE.match(proto_text) return { 'oracle_text': rules_text, @@ -949,7 +972,7 @@ def card_class(self) -> str: def oracle_text(self) -> str: """Fix Scryfall's minus character.""" if self.is_alt_lang: - return self.card.get('printed_text', self.card.get('oracle_text', '')).replace("\u2212", "-") + return (self.card.printed_text or self.card.oracle_text or "").replace("\u2212", "-") return self.oracle_text_raw @cached_property @@ -964,7 +987,7 @@ def oracle_text_raw(self) -> str: @cached_property def loyalty(self) -> str: """Planeswalker starting loyalty.""" - return self.card.get('loyalty', '') + return self.card.loyalty or "" @cached_property def pw_size(self) -> int: @@ -982,7 +1005,7 @@ def pw_abilities(self) -> list[PlaneswalkerAbility]: en_lines = lines.copy() # Process alternate language lines if needed - if self.is_alt_lang and 'printed_text' in self.card: + if self.is_alt_lang and self.card.printed_text: # Separate alternate language lines alt_lines = self.oracle_text.split('\n') @@ -1023,9 +1046,6 @@ def pw_abilities(self) -> list[PlaneswalkerAbility]: class PlaneswalkerTransformLayout(PlaneswalkerLayout): """Transform version of the Planeswalker card layout introduced in Innistrad block.""" - def __init__(self, scryfall: dict[str, Any], file: CardDetails): - super().__init__(scryfall, file) - # Static properties @cached_property def is_transform(self) -> bool: @@ -1085,9 +1105,9 @@ def card_class(self) -> str: @cached_property def oracle_text(self) -> str: """On MDFC alternate language data contains text from both sides, separate them.""" - if self.is_alt_lang and 'printed_text' in self.card: + if self.is_alt_lang and self.card.printed_text: return get_lines( - self.card.get('printed_text', ''), + self.card.printed_text, self.oracle_text_raw.count('\n') + 1) return self.oracle_text_raw @@ -1103,9 +1123,11 @@ def card_class(self) -> str: """ @cached_property - def adventure(self) -> dict[str,Any]: + def adventure(self) -> ScryfallCardFace: """Card object for adventure side.""" - return self.scryfall['card_faces'][1] + if self.scryfall.card_faces: + return self.scryfall.card_faces[1] + raise ValueError(f"Scryfall data doesn't have a card face for Adventure side: {pprint(self.scryfall)}") """ * Adventure Text @@ -1114,7 +1136,7 @@ def adventure(self) -> dict[str,Any]: @cached_property def mana_adventure(self) -> str: """Mana cost of the adventure side.""" - return self.adventure['mana_cost'] + return self.adventure.mana_cost or "" @cached_property def name_adventure(self) -> str: @@ -1124,21 +1146,21 @@ def name_adventure(self) -> str: @cached_property def type_line_adventure(self) -> str: """Type line of the Adventure side.""" - if self.is_alt_lang and 'printed_type_line' in self.adventure: - return self.adventure.get('printed_type_line', '') - return self.adventure.get('type_line', '') + if self.is_alt_lang and self.adventure.printed_type_line: + return self.adventure.printed_type_line + return self.adventure.type_line or "" @cached_property def oracle_text_adventure(self) -> str: """Oracle text of the Adventure side.""" - if self.is_alt_lang and 'printed_text' in self.adventure: - return self.adventure.get('printed_text', '') - return self.adventure.get('oracle_text', '') + if self.is_alt_lang and self.adventure.printed_text: + return self.adventure.printed_text + return self.adventure.oracle_text or "" @cached_property def flavor_text_adventure(self) -> str: """Flavor text of the Adventure side.""" - return self.adventure.get('flavor_text', '') + return self.adventure.flavor_text or "" """ * Adventure Colors @@ -1226,8 +1248,8 @@ def card_class(self) -> str: @cached_property def is_transform(self) -> bool: - """Sage supports both single and double faced cards.""" - return bool('card_faces' in self.scryfall) + """Saga supports both single and double faced cards.""" + return bool(self.scryfall.card_faces) """ * Saga Properties @@ -1366,7 +1388,7 @@ def card_class(self) -> str: @cached_property def defense(self) -> str: """Battle card defense.""" - return self.card.get('defense', '') + return self.card.defense or "" class PlanarLayout(NormalLayout): @@ -1443,36 +1465,27 @@ def display_name(self) -> str: return f"{self.names[0]} // {self.names[1]}" @cached_property - def card(self) -> dict[str,Any]: + def card(self) -> ScryfallCardFace: return self.cards[0] @cached_property - def cards(self) -> list[dict[str,Any]]: + def cards(self) -> Sequence[ScryfallCardFace]: """Both side objects.""" - return [*self.scryfall.get('card_faces', [])] + return self.scryfall.card_faces or [] """ * Colors """ @cached_property - def color_identity(self) -> list[str]: + def color_identity(self) -> list[MagicColor]: """Color identity is shared by both halves, use raw Scryfall instead of 'card' data.""" - return self.scryfall.get('color_identity', []) + return self.scryfall.color_identity @cached_property def color_indicator(self) -> str: """Color indicator is shared by both halves, use raw Scryfall instead of 'card' data.""" - return get_ordered_colors(self.scryfall.get('color_indicator', [])) - - """ - * Images - """ - - @cached_property - def scryfall_scan(self) -> str: - """Scryfall large image scan, if available.""" - return self.scryfall.get('image_uris', {}).get('large', '') + return get_ordered_colors(self.scryfall.color_indicator or []) """ * Symbols @@ -1502,7 +1515,7 @@ def watermark_raw(self) -> str | None: @cached_property def watermarks_raw(self) -> list[str | None]: """Name of the card's watermark from raw Scryfall data, if provided.""" - return [c.get('watermark', '') for c in self.cards] + return [c.watermark or "" for c in self.cards] @cached_property def watermark_svg(self) -> Path | None: @@ -1531,7 +1544,7 @@ def _find_watermark_svg(wm: str) -> Path | None: # Special case watermarks if wm in ['set', 'symbol']: return get_watermark_svg_from_set( - self.first_print.get('set', self.set) if wm == 'set' else self.set) + self.first_print.set if wm == 'set' and self.first_print else self.set) # Look for normal watermark return get_watermark_svg(wm) @@ -1587,8 +1600,8 @@ def type_line(self) -> str: def type_lines(self) -> list[str]: """Both side type lines.""" if self.is_alt_lang: - return [c.get('printed_type_line', c.get('type_line', '')) for c in self.cards] - return [c.get('type_line', '') for c in self.cards] + return [c.printed_type_line or c.type_line or "" for c in self.cards] + return [c.type_line or "" for c in self.cards] @cached_property def mana_cost(self) -> str: @@ -1597,7 +1610,7 @@ def mana_cost(self) -> str: @cached_property def mana_costs(self) -> list[str]: """Both side mana costs.""" - return [c.get('mana_cost', '') for c in self.cards] + return [c.mana_cost or "" for c in self.cards] @cached_property def oracle_text(self) -> str: @@ -1608,8 +1621,8 @@ def oracle_texts(self) -> list[str]: """Both side oracle texts.""" text: list[str] = [] for t in [ - c.get('printed_text', c.get('oracle_text', '')) - if self.is_alt_lang else c.get('oracle_text', '') + c.printed_text or c.oracle_text or "" + if self.is_alt_lang else c.oracle_text or "" for c in self.cards ]: if 'Fuse' in self.keywords: @@ -1629,14 +1642,14 @@ def flavor_text(self) -> str: @cached_property def flavor_texts(self) -> list[str]: """Both sides flavor text.""" - return [c.get('flavor_text', '') for c in self.cards] + return [c.flavor_text or "" for c in self.cards] @cached_property def shared_reminder(self) -> str: prev_match: str = "" for oracle_text in [ - c.get('printed_text', c.get('oracle_text', '')) - if self.is_alt_lang else c.get('oracle_text', '') + c.printed_text or c.oracle_text or "" + if self.is_alt_lang else c.oracle_text or "" for c in self.cards ]: match = CardTextPatterns.TEXT_REMINDER_ENDING.match(oracle_text) @@ -1769,7 +1782,7 @@ def card_class(self) -> str: def oracle_text_unprocessed(self) -> str: """Unaltered oracle text.""" return ( - self.card.get("printed_text", self.oracle_text_raw) + self.card.printed_text or self.oracle_text_raw if self.is_alt_lang else self.oracle_text_raw ) diff --git a/src/utils/manual_actions.py b/src/utils/manual_actions.py index cba8583b..cf38d4c9 100644 --- a/src/utils/manual_actions.py +++ b/src/utils/manual_actions.py @@ -1,13 +1,21 @@ -from json import dump, load -from os import unlink import subprocess +from os import unlink from tempfile import NamedTemporaryFile -from typing import Any +from typing import TypeVar + +from pydantic import BaseModel + +T = TypeVar("T", bound=BaseModel) + +def manually_modify_model(model: T, text_editing_program: str) -> T: + """Opens model as JSON in a text editing program, e.g. notepad, for manual editing, + waits for editing to be done and then converts the edited JSON back to model form. -def manually_modify_dict( - data: dict[str, Any], text_editing_program: str = 'notepad "{}"' -) -> dict[str, Any]: + Args: + model: Model to edit. + text_editing_program: The command to open the text editor. Place curly brackets to where the text file path should go in the call, e.g. `notepad "{}"`. + """ tmp = NamedTemporaryFile( "w", prefix="Proxyshop_manual_dict_modification_", @@ -15,10 +23,10 @@ def manually_modify_dict( delete=False, ) try: - dump(data, tmp, ensure_ascii=False, indent=2) + tmp.write(model.model_dump_json(indent=2)) tmp.close() subprocess.run((text_editing_program.format(tmp.name)), check=True) - with open(tmp.name, "r", encoding="UTF-8") as f: - return load(f) + with open(tmp.name, "rb") as f: + return model.model_validate_json(f.read()) finally: unlink(tmp.name) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 8aca7183..9a6ed95f 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -2,12 +2,12 @@ * Scryfall API Module """ -# Standard Library Imports from collections.abc import Callable, Sequence +from datetime import date from pathlib import Path from shutil import copyfileobj from typing import ( - Any, + Generic, Literal, NotRequired, ParamSpec, @@ -16,20 +16,19 @@ TypeVar, Unpack, ) +from uuid import UUID import requests import yarl - -# Third Party Imports from backoff import expo, on_exception from hexproof.scryfall.enums import ScryURL from limits import RateLimitItemPerSecond from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from pydantic import BaseModel, HttpUrl, ValidationError from requests.exceptions import RequestException -# Local Imports from src import CONSOLE, PATH from src.console import get_bullet_points from src.utils.download import HEADERS @@ -42,8 +41,312 @@ T = TypeVar("T") P = ParamSpec("P") - -class ScryfallError(TypedDict): +type LayoutType = Literal[ + "normal", + "split", + "flip", + "transform", + "modal_dfc", + "meld", + "leveler", + "class", + "case", + "saga", + "adventure", + "mutate", + "prototype", + "battle", + "planar", + "scheme", + "vanguard", + "token", + "double_faced_token", + "emblem", + "augment", + "host", + "art_series", + "reversible_card", + # Non-official extensions + "planeswalker", + "planeswalker_tf", + "planeswalker_mdfc", + "station", +] + +type MagicColor = Literal["W", "U", "B", "R", "G"] + +type Legality = Literal["legal", "not_legal", "restricted", "banned"] + +type BorderColor = Literal["black", "white", "borderless", "yellow", "silver", "gold"] + +type Finish = Literal["foil", "nonfoil", "etched"] + +type FrameEffect = Literal[ + "legendary", + "miracle", + "enchantment", + "draft", + "devoid", + "tombstone", + "colorshifted", + "inverted", + "sunmoondfc", + "compasslanddfc", + "originpwdfc", + "mooneldrazidfc", + "waxingandwaningmoondfc", + "showcase", + "extendedart", + "companion", + "etched", + "snow", + "lesson", + "shatteredglass", + "convertdfc", + "fandfc", + "upsidedowndfc", + "spree", + # Non-official + "meld", +] + +type ScryfallGame = Literal["paper", "arena", "mtgo"] + +type ScryfallImageStatus = Literal["missing", "placeholder", "lowres", "highres_scan"] + +type Rarity = Literal["common", "uncommon", "rare", "special", "mythic", "bonus"] + +type SetType = Literal[ + "core", + "expansion", + "masters", + "eternal", + "alchemy", + "masterpiece", + "arsenal", + "from_the_vault", + "spellbook", + "premium_deck", + "duel_deck", + "draft_innovation", + "treasure_chest", + "commander", + "planechase", + "archenemy", + "vanguard", + "funny", + "starter", + "box", + "promo", + "token", + "memorabilia", + "minigame", +] + + +class ScryfallImageUris(BaseModel): + png: HttpUrl + border_crop: HttpUrl + art_crop: HttpUrl + large: HttpUrl + normal: HttpUrl + small: HttpUrl + + +class ScryfallPrices(BaseModel): + usd: str | None = None + usd_foil: str | None = None + usd_etched: str | None = None + eur: str | None = None + eur_foil: str | None = None + eur_etched: str | None = None + tix: str | None = None + + +class ScryfallPurchaseUris(BaseModel): + tcgplayer: HttpUrl + cardmarket: HttpUrl + cardhoarder: HttpUrl + + +class ScryfallRelatedCard(BaseModel): + id: UUID + object: Literal["related_card"] + component: Literal["token", "meld_part", "meld_result", "combo_piece"] + name: str + type_line: str + uri: HttpUrl + + +class ScryfallPreview(BaseModel): + previewed_at: date | None = None + source_uri: HttpUrl | Literal[""] | None = None + source: str | None = None + + +class ScryfallCardFace(BaseModel): + artist: str | None = None + artist_id: UUID | None = None + cmc: float | None = None + color_indicator: list[MagicColor] | None = None + colors: list[MagicColor] | None = None + defense: str | None = None + flavor_text: str | None = None + illustration_id: UUID | None = None + image_uris: ScryfallImageUris | None = None + layout: LayoutType | None = None + loyalty: str | None = None + mana_cost: str | None = None + name: str + object: Literal["card_face"] + oracle_id: UUID | None = None + oracle_text: str | None = None + power: str | None = None + printed_name: str | None = None + printed_text: str | None = None + printed_type_line: str | None = None + toughness: str | None = None + type_line: str | None = None + watermark: str | None = None + + +class ScryfallCard(BaseModel): + class Config: + fields = {"front": {"exclude": True}} + + # Core Card Fields + arena_id: int | None = None + id: UUID + lang: str + mtgo_id: int | None = None + mtgo_foil_id: int | None = None + multiverse_ids: list[int] | None = None + tcgplayer_id: int | None = None + tcgplayer_etched_id: int | None = None + cardmarket_id: int | None = None + object: Literal["card"] + layout: LayoutType + oracle_id: UUID | None = None + prints_search_uri: HttpUrl + rulings_uri: HttpUrl + scryfall_uri: HttpUrl + uri: HttpUrl + + # Gameplay Fields + all_parts: list[ScryfallRelatedCard] | None = None + card_faces: list[ScryfallCardFace] | None = None + cmc: float + color_identity: list[MagicColor] + color_indicator: list[MagicColor] | None = None + colors: list[MagicColor] | None = None + defense: str | None = None + edhrec_rank: int | None = None + game_changer: bool | None = None + hand_modifier: str | None = None + keywords: list[str] + legalities: dict[str, Legality] + life_modifier: str | None = None + loyalty: str | None = None + mana_cost: str | None = None + name: str + oracle_text: str | None = None + penny_rank: int | None = None + power: str | None = None + produced_mana: list[str] | None = None + reserved: bool + toughness: str | None = None + type_line: str + + # Print Fields + artist: str | None = None + artist_ids: list[UUID] | None = None + attraction_lights: list[int] | None = None + booster: bool + border_color: BorderColor + card_back_id: UUID | None = None + collector_number: str + content_warning: bool | None = None + digital: bool + finishes: list[Finish] + flavor_name: str | None = None + flavor_text: str | None = None + frame_effects: list[FrameEffect] | None = None + frame: str + full_art: bool + games: list[ScryfallGame] + highres_image: bool + illustration_id: UUID | None = None + image_status: ScryfallImageStatus + image_uris: ScryfallImageUris | None = None + oversized: bool + prices: ScryfallPrices + printed_name: str | None = None + printed_text: str | None = None + printed_type_line: str | None = None + promo: bool + promo_types: list[str] | None = None + purchase_uris: ScryfallPurchaseUris | None = None + rarity: Rarity + related_uris: dict[str, HttpUrl] + released_at: date + reprint: bool + scryfall_set_uri: HttpUrl + set_name: str + set_search_uri: HttpUrl + set_type: SetType + set_uri: HttpUrl + set: str + set_id: UUID + story_spotlight: bool + textless: bool + variation: bool + variation_of: UUID | None = None + security_stamp: str | None = None + watermark: str | None = None + preview: ScryfallPreview | None = None + + # Non-official extensions + front: bool = True + + +class ScryfallList(BaseModel, Generic[T]): + object: Literal["list"] + data: list[T] + has_more: bool + next_page: HttpUrl | None = None + total_cards: int | None = None + warnings: list[str] | None = None + + +class ScryfallCardList(ScryfallList[ScryfallCard]): + pass + + +class ScryfallSet(BaseModel): + object: Literal["set"] + id: UUID + code: str + mtgo_code: str | None = None + arena_code: str | None = None + tcgplayer_id: int | None = None + name: str + set_type: SetType + released_at: date | None = None + block_code: str | None = None + block: str | None = None + parent_set_code: str | None = None + card_count: int + printed_size: int | None = None + digital: bool + foil_only: bool + nonfoil_only: bool + scryfall_uri: HttpUrl + uri: HttpUrl + icon_svg_uri: HttpUrl + search_uri: HttpUrl + + +class ScryfallError(BaseModel): """Error object outlined in Scryfall's API docs. Notes: @@ -54,8 +357,8 @@ class ScryfallError(TypedDict): code: str status: int details: str - type: NotRequired[str] - warnings: NotRequired[list[str]] + type: str | None = None + warnings: list[str] | None = None """ @@ -170,8 +473,8 @@ def get_error( Returns: A ScryfallException object. """ - msg = error["details"] - if warns := error.get("warnings"): + msg = error.details + if warns := error.warnings: msg += get_bullet_points(warns, " -") kwargs["exception"] = RequestException(msg, response=response) return ScryfallException(**kwargs) @@ -183,9 +486,31 @@ def get_error( @scryfall_request_wrapper() -def get_card_unique( - card_set: str, card_number: str, lang: str = "en" -) -> dict[str, Any]: +def get_card_via_url(url: str) -> ScryfallCard: + res = requests.get(url=str(url), headers=scryfall_http_header) + + try: + card = ScryfallCard.model_validate_json(res.content) + if is_playable_card(card): + return card + except ValidationError as exc: + # Handle Scryfall error + try: + err = ScryfallError.model_validate_json(res.content) + raise get_error(error=err, response=res) + except ValidationError: + raise ScryfallException(exception=exc) + + # No playable result + raise ScryfallException( + exception=RequestException( + "No playable card found with the provided set and number.", response=res + ) + ) + + +@scryfall_request_wrapper() +def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> ScryfallCard: """Get card using /cards/:code/:number(/:lang) Scryfall API endpoint. Notes: @@ -203,26 +528,41 @@ def get_card_unique( url = ScryURL.API.Cards.Main / card_set.lower() / card_number url = url / lang if lang != "en" else url - # Track the params - params: ScryfallExceptionKwargs = { - "card_set": card_set, - "card_number": card_number, - "lang": lang, - } - # Request the data res = requests.get(url=str(url), headers=scryfall_http_header) - card = res.json() - - # Ensure playable card was returned - if card.get("object") == "error": - raise get_error(error=card, response=res, **params) - if card.get("object") == "card" and is_playable_card(card): - return card - params["exception"] = RequestException( - "No card found with the provided set and number.", response=res + + try: + card = ScryfallCard.model_validate_json(res.content) + if is_playable_card(card): + return card + except ValidationError as exc: + # Handle Scryfall error + try: + err = ScryfallError.model_validate_json(res.content) + raise get_error( + error=err, + response=res, + card_set=card_set, + card_number=card_number, + lang=lang, + ) + except ValidationError: + raise ScryfallException( + exception=exc, + card_set=card_set, + card_number=card_number, + lang=lang, + ) + + # No playable result + raise ScryfallException( + exception=RequestException( + "No playable card found with the provided set and number.", response=res + ), + card_set=card_set, + card_number=card_number, + lang=lang, ) - raise ScryfallException(**params) @scryfall_request_wrapper() @@ -231,7 +571,7 @@ def get_card_search( card_set: str | None = None, lang: str = "en", **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], -) -> dict[str, Any]: +) -> ScryfallCard: """Get card using /cards/search Scryfall API endpoint. Notes: @@ -269,21 +609,32 @@ def get_card_search( ), headers=scryfall_http_header, ) - data = res.json() - # Check for a playable card - if data.get("object") == "error": - raise get_error( - error=data, response=res, card_name=card_name, card_set=card_set, lang=lang - ) - for c in data.get("data", []): - if is_playable_card(c): - return c + try: + list_data = ScryfallCardList.model_validate_json(res.content) + for card in list_data.data: + if is_playable_card(card): + return card + except ValidationError as exc: + # Handle Scryfall error + try: + err = ScryfallError.model_validate_json(res.content) + raise get_error( + error=err, + response=res, + card_name=card_name, + card_set=card_set, + lang=lang, + ) + except ValidationError: + raise ScryfallException( + exception=exc, card_name=card_name, card_set=card_set, lang=lang + ) # No playable results raise ScryfallException( exception=RequestException( - "No card found with the provided search terms.", response=res + "No playable card found with the provided search terms.", response=res ), card_name=card_name, card_set=card_set, @@ -296,8 +647,8 @@ def get_card_search( def get_cards_paged( url: yarl.URL | None = None, all_pages: bool = True, - **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], -) -> list[dict[str, Any]]: + **kwargs: yarl.QueryVariable, +) -> list[ScryfallCard]: """Grab paginated card list from a Scryfall API endpoint. Args: @@ -309,27 +660,34 @@ def get_cards_paged( url = url or ScryURL.API.Cards.Search # Query Scryfall - req = requests.get(url=str(url.with_query(kwargs)), headers=scryfall_http_header) - res = req.json() - cards = res.get("data", []) - - # Check for an error object - if res.get("object") == "error": - raise get_error(error=res, response=req) - - # Add additional pages if any exist - if all_pages and res.get("has_more") and res.get("next_page"): - cards.extend(get_cards_paged(url=res.get["next_page"], all_pages=all_pages)) - return cards + res = requests.get(url=str(url.with_query(kwargs)), headers=scryfall_http_header) + + try: + list_data = ScryfallCardList.model_validate_json(res.content) + cards = list_data.data + if all_pages and list_data.has_more and list_data.next_page: + cards.extend( + get_cards_paged( + url=yarl.URL(str(list_data.next_page)), all_pages=all_pages + ) + ) + return cards + except ValidationError as exc: + # Handle Scryfall error + try: + err = ScryfallError.model_validate_json(res.content) + raise get_error(error=err, response=res) + except ValidationError: + raise ScryfallException(exception=exc) @scryfall_request_wrapper() -@return_on_exception([]) +@return_on_exception(None) def get_cards_oracle( oracle_id: str, all_pages: bool = False, - **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], -) -> list[dict[str, Any]]: + **kwargs: yarl.QueryVariable, +) -> list[ScryfallCard]: """Grab paginated card list from a Scryfall API endpoint using the Oracle ID of the card. Args: @@ -358,7 +716,7 @@ def get_cards_oracle( @scryfall_request_wrapper() @return_on_exception({}) -def get_set(card_set: str) -> dict[str, Any]: +def get_set(card_set: str) -> ScryfallSet: """Grab Set data from Scryfall. Args: @@ -369,15 +727,22 @@ def get_set(card_set: str) -> dict[str, Any]: """ # Make the request res = requests.get( - str(ScryURL.API.Sets.All / card_set.upper()), - headers=scryfall_http_header, + str(ScryURL.API.Sets.All / card_set.upper()), headers=scryfall_http_header ) - data = res.json() - # Check for an error object - if data.get("object") == "error": - raise get_error(error=data, response=res, **{"card_set": card_set}) - return data or {} + try: + return ScryfallSet.model_validate_json(res.content) + except ValidationError as exc: + # Handle Scryfall error + try: + err = ScryfallError.model_validate_json(res.content) + raise get_error( + error=err, + response=res, + card_set=card_set, + ) + except ValidationError: + raise ScryfallException(exception=exc, card_set=card_set) """ @@ -385,29 +750,6 @@ def get_set(card_set: str) -> dict[str, Any]: """ -@scryfall_request_wrapper() -@return_on_exception({}) -def get_uri_object( - url: yarl.URL, - **kwargs: str | SupportsInt | float | Sequence[str | SupportsInt | float], -) -> dict[str, Any]: - """Pull a single object from Scryfall using a URI from a previous Scryfall data set. - - Args: - url: Formatted URL to pull object from. - - Returns: - A Scryfall object, e.g. Card, Set, etc. - """ - res = requests.get(str(url.with_query(kwargs)), headers=scryfall_http_header) - data = res.json() - - # Check for error object - if data.get("object") == "error": - raise get_error(error=data, response=res) - return data - - @scryfall_request_wrapper() @return_on_exception(None) def get_card_scan(img_url: str) -> Path: @@ -435,7 +777,7 @@ def get_card_scan(img_url: str) -> Path: """ -def is_playable_card(card_json: dict[str, Any]) -> bool: +def is_playable_card(card: ScryfallCard) -> bool: """Checks if this card object is a playable game piece. Args: @@ -444,16 +786,14 @@ def is_playable_card(card_json: dict[str, Any]) -> bool: Returns: Valid scryfall data if check passed, else None. """ - if card_json.get("set_type") in ["minigame"]: + if card.set_type == "minigame": # Ignore minigame insert cards return False - if card_json.get("layout") in ["art_series", "reversible_card"]: + if card.layout in ["art_series", "reversible_card"]: # Ignore art series and reversible cards # TODO: Implement support for reversible return False - if card_json.get("set_type") in ["memorabilia"] and "(Theme)" in card_json.get( - "name", "" - ): + if card.set_type == "memorabilia" and "(Theme)" in card.name: # Ignore theme insert cards (Jumpstart) return False return True From 05728a72682049b0bf344ff14719110f8792fc7c Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 29 Sep 2025 15:46:10 +0300 Subject: [PATCH 051/190] feat(layouts.py): Expand printed text setting to apply to type line and rules text as well --- src/_config.py | 15 ++++++--------- src/data/config/app.toml | 6 +++--- src/layouts.py | 18 ++++++++---------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/_config.py b/src/_config.py index 6a6013c0..8a7f3e9c 100644 --- a/src/_config.py +++ b/src/_config.py @@ -2,25 +2,22 @@ * Global Settings Module """ -# Standard Library Imports from typing import Literal, overload -# Third Party Imports -from omnitils.metaclass import Singleton from omnitils.enums import StrConstant +from omnitils.metaclass import Singleton -# Local Imports +from src._loader import ConfigManager from src._state import AppEnvironment from src.enums.settings import ( - CollectorMode, BorderColor, + CollectorMode, + CollectorPromo, OutputFileType, ScryfallSorting, ScryfallUnique, - CollectorPromo, WatermarkMode, ) -from src._loader import ConfigManager class AppConfig: @@ -52,8 +49,8 @@ def update_definitions(self): # APP - DATA self.lang = self.file.get("APP.DATA", "Scryfall.Language", fallback="en") - self.use_printed_name = self.file.getboolean( - "APP.DATA", "Scryfall.Use.Printed.Name", fallback=False + self.use_printed_texts = self.file.getboolean( + "APP.DATA", "Scryfall.Use.Printed.Texts", fallback=False ) self.scry_sorting = self.get_option( "APP.DATA", "Scryfall.Sorting", ScryfallSorting diff --git a/src/data/config/app.toml b/src/data/config/app.toml index 0e4725f6..d0384b9c 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -43,9 +43,9 @@ type = "options" default = "en" options = ["en", "es", "fr", "de", "it", "pt", "jp", "kr", "ru", "cs", "ct"] -[DATA."Scryfall.Use.Printed.Name"] -title = "Use printed name" -desc = """Use the printed name of the card, if it's available in the Scryfall data, instead of the canonical English name. Non-English cards use printed name regardless of this setting's value.""" +[DATA."Scryfall.Use.Printed.Texts"] +title = "Use printed texts" +desc = """Use the printed name, type line and rules text of the card, if they are available in the Scryfall data, instead of the canonical English texts. Non-English cards use printed texts regardless of this setting's value.""" type = "bool" default = 0 diff --git a/src/layouts.py b/src/layouts.py index 12d50a64..57b15ac3 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -76,11 +76,6 @@ class ClassLine(TypedDict): level: str text: str -def get_card_name(data: ScryfallCard | ScryfallCardFace, use_printed_name: bool = CFG.use_printed_name) -> str: - if use_printed_name and (printed_name := data.printed_name): - return printed_name - return data.name - """ * Layout Processing """ @@ -210,6 +205,9 @@ def __str__(self): return (f"{self.name}" f"{f' [{self.set}]' if self.set else ''}" f"{f' {{{self.collector_number_raw}}}' if self.collector_number else ''}") + + def _get_card_name(self, data: ScryfallCard | ScryfallCardFace) -> str: + return data.printed_name if self.is_alt_lang and data.printed_name else data.name """ * Core Data @@ -332,12 +330,12 @@ def promo_types(self) -> list[str]: @cached_property def name(self) -> str: """Card name, supports alternate language source.""" - return get_card_name(self.card, CFG.use_printed_name or self.is_alt_lang) + return self._get_card_name(self.card) @cached_property def name_raw(self) -> str: """Card name, enforced English representation.""" - return get_card_name(self.card, False) + return self.card.name @cached_property def nickname(self) -> str: @@ -708,7 +706,7 @@ def is_front(self) -> bool: @cached_property def is_alt_lang(self) -> bool: """True if language selected isn't English.""" - return bool(self.lang != 'EN') + return CFG.use_printed_texts or bool(self.lang != 'EN') """ * Cosmetic Bool @@ -1141,7 +1139,7 @@ def mana_adventure(self) -> str: @cached_property def name_adventure(self) -> str: """Name of the Adventure side.""" - return get_card_name(self.adventure, CFG.use_printed_name or self.is_alt_lang) + return self._get_card_name(self.adventure) @cached_property def type_line_adventure(self) -> str: @@ -1585,7 +1583,7 @@ def name(self) -> str: @cached_property def names(self) -> list[str]: """Both side names.""" - return [get_card_name(c, CFG.use_printed_name or self.is_alt_lang) for c in self.cards] + return [self._get_card_name(c) for c in self.cards] @cached_property def name_raw(self) -> str: From f8113653e3f39d63bbb1b107ecdf4648617e9708 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 6 Oct 2025 14:51:12 +0300 Subject: [PATCH 052/190] feat: Support rendering Class cards using languages other than English --- src/enums/layers.py | 1 + src/enums/mtg.py | 3 +- src/layouts.py | 10 +++-- src/templates/classes.py | 85 ++++++++++++++++++++++++++++++++++------ src/templates/saga.py | 13 +++--- 5 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/enums/layers.py b/src/enums/layers.py index 5c9d64c6..1ff6ed7c 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -136,6 +136,7 @@ class LAYERS (StrConstant): RULES_TEXT_NONCREATURE_FLIP = 'Rules Text - Noncreature Flip' RULES_TEXT_CREATURE = 'Rules Text - Creature' RULES_TEXT_CREATURE_FLIP = 'Rules Text - Creature Flip' + REMINDER_TEXT = 'Reminder Text' MUTATE = 'Mutate' DIVIDER = 'Divider' CREATOR = 'Creator' diff --git a/src/enums/mtg.py b/src/enums/mtg.py index 5fab5942..d637df17 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -362,7 +362,8 @@ class CardTextPatterns: LEVELER: re.Pattern[str] = re.compile(r"(.*?)\nLEVEL (\d*-\d*)\n(\d*/\d*)\n(.*?)\nLEVEL (\d*\+)\n(\d*/\d*)\n(.*?)$") PROTOTYPE: re.Pattern[str] = re.compile(r"Prototype (.+) [—\-] ([0-9]{0,2}/[0-9]{0,2}) \((.+)\)") PLANESWALKER: re.Pattern[str] = re.compile(r"(^[^:]*$|^.*:.*$)", re.MULTILINE) - CLASS: re.Pattern[str] = re.compile(r"(.+?): Level (\d)\n(.+)") + CLASS: re.Pattern[str] = re.compile(r"(.+?): ([^\d]+ ?)(\d)\n(.+)") + CLASS_NON_ENGLISH: re.Pattern[str] = re.compile(r"//Level_\d//") # Filename - Card Art PATH_ARTIST: re.Pattern[str] = re.compile(r"\(+(.*?)\)") diff --git a/src/layouts.py b/src/layouts.py index 57b15ac3..b4660cc2 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -73,6 +73,7 @@ class SagaLine(TypedDict): class ClassLine(TypedDict): cost: str | None + level_translation: str level: str text: str @@ -1343,7 +1344,9 @@ def class_lines(self) -> list[ClassLine]: # Initial class ability initial, *lines = self.class_text.split('\n') - abilities: list[ClassLine] = [{'text': initial, 'cost': None, 'level': "1"}] + # Non-English Class cards have extra //Level_X// after first line of each level + lines = [line for line in lines if not CardTextPatterns.CLASS_NON_ENGLISH.match(line)] + abilities: list[ClassLine] = [{'text': initial, 'cost': None, 'level': "1", "level_translation": ""}] # Add level-up abilities for line in ["\n".join(lines[i:i + 2]) for i in range(0, len(lines), 2)]: @@ -1352,8 +1355,9 @@ def class_lines(self) -> list[ClassLine]: if details and len(details.groups()) >= 3: abilities.append({ 'cost': details[1], - 'level': details[2], - 'text': details[3] + 'level_translation': details[2], + 'level': details[3], + 'text': details[4] }) continue # Otherwise add line to the previous ability diff --git a/src/templates/classes.py b/src/templates/classes.py index f6010d39..de294a08 100644 --- a/src/templates/classes.py +++ b/src/templates/classes.py @@ -2,21 +2,18 @@ * CLASS TEMPLATES """ -# Standard Library Imports +from collections.abc import Callable, Sequence from functools import cached_property -from collections.abc import Sequence, Callable -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import GradientConfig +from src.enums.layers import LAYERS +from src.helpers.bounds import get_dimensions_from_bounds from src.helpers.layers import get_reference_layer from src.layouts import ClassLayout, NormalLayout -from src.schema.colors import ColorObject, pinlines_color_map +from src.schema.colors import ColorObject, GradientConfig, pinlines_color_map from src.templates._core import NormalTemplate from src.templates._cosmetic import VectorNyxMod from src.templates._vector import MaskAction, VectorTemplate @@ -95,6 +92,10 @@ def stage_group(self) -> LayerSet | None: def text_layer_ability(self) -> ArtLayer | None: return psd.getLayer(LAYERS.TEXT, self.class_group) + @cached_property + def class_text_layer_reminder(self) -> ArtLayer | None: + return psd.getLayer(LAYERS.REMINDER_TEXT, self.class_group) + """ * Class Abilities """ @@ -108,9 +109,13 @@ def class_line_layers(self, value: list[ArtLayer]): self._class_line_layers = value """ - * Class Stage Dividers + * Class Dividers """ + @cached_property + def class_reminder_divider(self) -> ArtLayer | LayerSet | None: + return psd.getLayer(LAYERS.DIVIDER, self.class_group) + @property def class_stage_layers(self) -> list[LayerSet]: return self._class_stage_layers @@ -136,6 +141,15 @@ def rules_text_and_pt_layers(self) -> None: def text_layers_classes(self) -> None: """Add and modify text layers relating to Class type cards.""" if isinstance(self.layout, ClassLayout) and self.text_layer_ability: + # Add reminder text + if self.class_text_layer_reminder: + self.text.append( + FormattedTextField( + layer=self.class_text_layer_reminder, + contents=self.layout.class_description, + ) + ) + # Add first static line self.class_line_layers.append(self.text_layer_ability) self.text.append( @@ -165,7 +179,10 @@ def text_layers_classes(self) -> None: self.text.extend( [ FormattedTextField(layer=cost, contents=f"{line['cost']}:"), - TextField(layer=level, contents=f"Level {line['level']}"), + TextField( + layer=level, + contents=f"{line['level_translation']}{line['level']}", + ), ] ) @@ -184,6 +201,29 @@ def frame_layers_classes(self) -> None: def layer_positioning_classes(self) -> None: """Positions and sizes class ability layers and stage dividers.""" if self.textbox_reference: + # Ensure that textbox reference bounds don't overlap with reminder + reminder_end: float = 0 + if self.class_text_layer_reminder: + reminder_dims = psd.get_layer_dimensions(self.class_text_layer_reminder) + reminder_end = reminder_dims["bottom"] + + if self.class_reminder_divider: + divider_dims = psd.get_layer_dimensions(self.class_reminder_divider) + reminder_end += divider_dims["height"] + + textbox_ref_bounds = self.textbox_reference.bounds + new_bounds = ( + textbox_ref_bounds[0], + max(textbox_ref_bounds[1], reminder_end), + textbox_ref_bounds[2], + textbox_ref_bounds[3], + ) + # Override the cached bounds with modified bounds. + # It is assumed that bounds without effects aren't needed, + # so they aren't overridden. + self.textbox_reference.bounds = new_bounds + self.textbox_reference.dims = get_dimensions_from_bounds(new_bounds) + # Core vars spacing = self.app.scale_by_dpi(80) spaces = len(self.class_line_layers) - 1 @@ -198,7 +238,9 @@ def layer_positioning_classes(self) -> None: ) # Get the exact gap between each layer left over - layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.class_line_layers]) + layer_heights = sum( + [psd.get_layer_height(lyr) for lyr in self.class_line_layers] + ) gap = (ref_height - layer_heights) * (spacing / spacing_total) inside_gap = (ref_height - layer_heights) * ( (spacing + divider_height) / spacing_total @@ -214,9 +256,24 @@ def layer_positioning_classes(self) -> None: # Position a class stage between each ability line psd.position_dividers( - dividers=self.class_stage_layers, layers=self.class_line_layers, docref=self.docref + dividers=self.class_stage_layers, + layers=self.class_line_layers, + docref=self.docref, ) + # Position reminder divider + if self.class_reminder_divider: + first_line_layer_dims = psd.get_layer_dimensions( + self.class_line_layers[0] + ) + divider_dims = psd.get_layer_dimensions(self.class_reminder_divider) + delta = ( + (reminder_text_end := reminder_end - divider_dims["height"]) + + (first_line_layer_dims["top"] - reminder_text_end) / 2 + - divider_dims["center_y"] + ) + self.class_reminder_divider.translate(0, delta) + """ * Template Classes @@ -413,7 +470,11 @@ def pinlines_mask( def enabled_masks( self, ) -> list[ - MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None ]: """Support a pinlines mask.""" return [self.pinlines_mask] diff --git a/src/templates/saga.py b/src/templates/saga.py index 5b0b1ef9..485860c3 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -2,22 +2,20 @@ * Templates: Saga """ -# Standard Library Imports +from collections.abc import Callable, Sequence from functools import cached_property -from collections.abc import Sequence, Callable -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import GradientConfig +import src.text_layers as text_classes +from src.enums.layers import LAYERS from src.helpers.layers import get_reference_layer from src.layouts import NormalLayout, SagaLayout from src.schema.colors import ( ColorObject, + GradientConfig, pinlines_color_map, saga_banner_color_map, saga_stripe_color_map, @@ -26,7 +24,6 @@ from src.templates._core import NormalTemplate from src.templates._vector import MaskAction, VectorTemplate from src.templates.transform import VectorTransformMod -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer """ @@ -128,7 +125,7 @@ def text_layer_ability(self) -> ArtLayer | None: @cached_property def text_layer_reminder(self) -> ArtLayer | None: - return psd.getLayer("Reminder Text", self.saga_group) + return psd.getLayer(LAYERS.REMINDER_TEXT, self.saga_group) """ * References From aa7fb72a7f6059e6f63e9e32c311b9e4c1cd6d14 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 7 Oct 2025 14:38:51 +0300 Subject: [PATCH 053/190] build(poetry): Update dependencies --- poetry.lock | 2326 +++++++++++++++++++++++++----------------------- pyproject.toml | 18 +- 2 files changed, 1211 insertions(+), 1133 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5634ba4a..ed29926e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -104,24 +104,16 @@ asyncgui = ">=0.9.3,<0.10" [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, - {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, + {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, + {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] -[package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] - [[package]] name = "babel" version = "2.17.0" @@ -171,14 +163,14 @@ extras = ["regex"] [[package]] name = "beautifulsoup4" -version = "4.13.5" +version = "4.14.2" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, - {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, + {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, + {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, ] [package.dependencies] @@ -398,96 +390,113 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, - {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, + {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, + {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] markers = "platform_python_implementation == \"PyPy\"" files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "cfgv" @@ -592,14 +601,14 @@ files = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, ] [package.dependencies] @@ -620,14 +629,14 @@ markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "commitizen" -version = "4.8.3" +version = "4.9.1" description = "Python commitizen client tool" optional = false python-versions = "<4.0,>=3.9" groups = ["dev"] files = [ - {file = "commitizen-4.8.3-py3-none-any.whl", hash = "sha256:91f261387ca2bbb4ab6c79a1a6378dc1576ffb40e3b7dbee201724d95aceba38"}, - {file = "commitizen-4.8.3.tar.gz", hash = "sha256:303ebdc271217aadbb6a73a015612121291d180c8cdd05b5251c7923d4a14195"}, + {file = "commitizen-4.9.1-py3-none-any.whl", hash = "sha256:4241b2ecae97b8109af8e587c36bc3b805a09b9a311084d159098e12d6ead497"}, + {file = "commitizen-4.9.1.tar.gz", hash = "sha256:b076b24657718f7a35b1068f2083bd39b4065d250164a1398d1dac235c51753b"}, ] [package.dependencies] @@ -635,9 +644,10 @@ argcomplete = ">=1.12.1,<3.7" charset-normalizer = ">=2.1.0,<4" colorama = ">=0.4.1,<1.0" decli = ">=0.6.0,<1.0" -importlib-metadata = {version = ">=8.0.0,<9.0.0", markers = "python_version != \"3.9\""} +deprecated = ">=1.2.13,<2" jinja2 = ">=2.10.3" packaging = ">=19" +prompt_toolkit = "!=3.0.52" pyyaml = ">=3.08" questionary = ">=2.0,<3.0" termcolor = ">=1.1.0,<4.0.0" @@ -792,7 +802,7 @@ version = "1.2.18" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, @@ -816,27 +826,16 @@ files = [ {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] - [[package]] name = "docutils" -version = "0.22" +version = "0.22.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e"}, - {file = "docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f"}, + {file = "docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8"}, + {file = "docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d"}, ] [[package]] @@ -890,70 +889,70 @@ files = [ [[package]] name = "fonttools" -version = "4.59.2" +version = "4.60.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "fonttools-4.59.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2a159e36ae530650acd13604f364b3a2477eff7408dcac6a640d74a3744d2514"}, - {file = "fonttools-4.59.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bd733e47bf4c6dee2b2d8af7a1f7b0c091909b22dbb969a29b2b991e61e5ba4"}, - {file = "fonttools-4.59.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7bb32e0e33795e3b7795bb9b88cb6a9d980d3cbe26dd57642471be547708e17a"}, - {file = "fonttools-4.59.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cdcdf7aad4bab7fd0f2938624a5a84eb4893be269f43a6701b0720b726f24df0"}, - {file = "fonttools-4.59.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4d974312a9f405628e64f475b1f5015a61fd338f0a1b61d15c4822f97d6b045b"}, - {file = "fonttools-4.59.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:12dc4670e6e6cc4553e8de190f86a549e08ca83a036363115d94a2d67488831e"}, - {file = "fonttools-4.59.2-cp310-cp310-win32.whl", hash = "sha256:1603b85d5922042563eea518e272b037baf273b9a57d0f190852b0b075079000"}, - {file = "fonttools-4.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:2543b81641ea5b8ddfcae7926e62aafd5abc604320b1b119e5218c014a7a5d3c"}, - {file = "fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f"}, - {file = "fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d"}, - {file = "fonttools-4.59.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5729e12a982dba3eeae650de48b06f3b9ddb51e9aee2fcaf195b7d09a96250e2"}, - {file = "fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d"}, - {file = "fonttools-4.59.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f1bbc23ba1312bd8959896f46f667753b90216852d2a8cfa2d07e0cb234144"}, - {file = "fonttools-4.59.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1a1bfe5378962825dabe741720885e8b9ae9745ec7ecc4a5ec1f1ce59a6062bf"}, - {file = "fonttools-4.59.2-cp311-cp311-win32.whl", hash = "sha256:e937790f3c2c18a1cbc7da101550a84319eb48023a715914477d2e7faeaba570"}, - {file = "fonttools-4.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104"}, - {file = "fonttools-4.59.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82906d002c349cad647a7634b004825a7335f8159d0d035ae89253b4abf6f3ea"}, - {file = "fonttools-4.59.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a10c1bd7644dc58f8862d8ba0cf9fb7fef0af01ea184ba6ce3f50ab7dfe74d5a"}, - {file = "fonttools-4.59.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:738f31f23e0339785fd67652a94bc69ea49e413dfdb14dcb8c8ff383d249464e"}, - {file = "fonttools-4.59.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ec99f9bdfee9cdb4a9172f9e8fd578cce5feb231f598909e0aecf5418da4f25"}, - {file = "fonttools-4.59.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0476ea74161322e08c7a982f83558a2b81b491509984523a1a540baf8611cc31"}, - {file = "fonttools-4.59.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95922a922daa1f77cc72611747c156cfb38030ead72436a2c551d30ecef519b9"}, - {file = "fonttools-4.59.2-cp312-cp312-win32.whl", hash = "sha256:39ad9612c6a622726a6a130e8ab15794558591f999673f1ee7d2f3d30f6a3e1c"}, - {file = "fonttools-4.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:980fd7388e461b19a881d35013fec32c713ffea1fc37aef2f77d11f332dfd7da"}, - {file = "fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:381bde13216ba09489864467f6bc0c57997bd729abfbb1ce6f807ba42c06cceb"}, - {file = "fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f33839aa091f7eef4e9078f5b7ab1b8ea4b1d8a50aeaef9fdb3611bba80869ec"}, - {file = "fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6235fc06bcbdb40186f483ba9d5d68f888ea68aa3c8dac347e05a7c54346fbc8"}, - {file = "fonttools-4.59.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83ad6e5d06ef3a2884c4fa6384a20d6367b5cfe560e3b53b07c9dc65a7020e73"}, - {file = "fonttools-4.59.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d029804c70fddf90be46ed5305c136cae15800a2300cb0f6bba96d48e770dde0"}, - {file = "fonttools-4.59.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:95807a3b5e78f2714acaa26a33bc2143005cc05c0217b322361a772e59f32b89"}, - {file = "fonttools-4.59.2-cp313-cp313-win32.whl", hash = "sha256:b3ebda00c3bb8f32a740b72ec38537d54c7c09f383a4cfefb0b315860f825b08"}, - {file = "fonttools-4.59.2-cp313-cp313-win_amd64.whl", hash = "sha256:a72155928d7053bbde499d32a9c77d3f0f3d29ae72b5a121752481bcbd71e50f"}, - {file = "fonttools-4.59.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d09e487d6bfbe21195801323ba95c91cb3523f0fcc34016454d4d9ae9eaa57fe"}, - {file = "fonttools-4.59.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dec2f22486d7781087b173799567cffdcc75e9fb2f1c045f05f8317ccce76a3e"}, - {file = "fonttools-4.59.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1647201af10993090120da2e66e9526c4e20e88859f3e34aa05b8c24ded2a564"}, - {file = "fonttools-4.59.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47742c33fe65f41eabed36eec2d7313a8082704b7b808752406452f766c573fc"}, - {file = "fonttools-4.59.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92ac2d45794f95d1ad4cb43fa07e7e3776d86c83dc4b9918cf82831518165b4b"}, - {file = "fonttools-4.59.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fa9ecaf2dcef8941fb5719e16322345d730f4c40599bbf47c9753de40eb03882"}, - {file = "fonttools-4.59.2-cp314-cp314-win32.whl", hash = "sha256:a8d40594982ed858780e18a7e4c80415af65af0f22efa7de26bdd30bf24e1e14"}, - {file = "fonttools-4.59.2-cp314-cp314-win_amd64.whl", hash = "sha256:9cde8b6a6b05f68516573523f2013a3574cb2c75299d7d500f44de82ba947b80"}, - {file = "fonttools-4.59.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:036cd87a2dbd7ef72f7b68df8314ced00b8d9973aee296f2464d06a836aeb9a9"}, - {file = "fonttools-4.59.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14870930181493b1d740b6f25483e20185e5aea58aec7d266d16da7be822b4bb"}, - {file = "fonttools-4.59.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ff58ea1eb8fc7e05e9a949419f031890023f8785c925b44d6da17a6a7d6e85d"}, - {file = "fonttools-4.59.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dee142b8b3096514c96ad9e2106bf039e2fe34a704c587585b569a36df08c3c"}, - {file = "fonttools-4.59.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8991bdbae39cf78bcc9cd3d81f6528df1f83f2e7c23ccf6f990fa1f0b6e19708"}, - {file = "fonttools-4.59.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:53c1a411b7690042535a4f0edf2120096a39a506adeb6c51484a232e59f2aa0c"}, - {file = "fonttools-4.59.2-cp314-cp314t-win32.whl", hash = "sha256:59d85088e29fa7a8f87d19e97a1beae2a35821ee48d8ef6d2c4f965f26cb9f8a"}, - {file = "fonttools-4.59.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7ad5d8d8cc9e43cb438b3eb4a0094dd6d4088daa767b0a24d52529361fd4c199"}, - {file = "fonttools-4.59.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3cdf9d32690f0e235342055f0a6108eedfccf67b213b033bac747eb809809513"}, - {file = "fonttools-4.59.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f9640d6b31d66c0bc54bdbe8ed50983c755521c101576a25e377a8711e8207"}, - {file = "fonttools-4.59.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464d15b58a9fd4304c728735fc1d42cd812fd9ebc27c45b18e78418efd337c28"}, - {file = "fonttools-4.59.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a039c38d5644c691eb53cd65360921338f54e44c90b4e764605711e046c926ee"}, - {file = "fonttools-4.59.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e4f5100e66ec307cce8b52fc03e379b5d1596e9cb8d8b19dfeeccc1e68d86c96"}, - {file = "fonttools-4.59.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:af6dbd463a3530256abf21f675ddf87646272bc48901803a185c49d06287fbf1"}, - {file = "fonttools-4.59.2-cp39-cp39-win32.whl", hash = "sha256:594a6fd2f8296583ac7babc4880c8deee7c4f05ab0141addc6bce8b8e367e996"}, - {file = "fonttools-4.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:fc21c4a05226fd39715f66c1c28214862474db50df9f08fd1aa2f96698887bc3"}, - {file = "fonttools-4.59.2-py3-none-any.whl", hash = "sha256:8bd0f759020e87bb5d323e6283914d9bf4ae35a7307dafb2cbd1e379e720ad37"}, - {file = "fonttools-4.59.2.tar.gz", hash = "sha256:e72c0749b06113f50bcb80332364c6be83a9582d6e3db3fe0b280f996dc2ef22"}, + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, + {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, + {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, + {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, + {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, + {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, + {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, + {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, + {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, + {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, + {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, + {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, + {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, + {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, + {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, + {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, + {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, + {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, + {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, + {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, + {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, + {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, + {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, + {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, + {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, + {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, + {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, + {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, + {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, + {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, + {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, + {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, + {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, + {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, + {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, + {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, + {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, + {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, ] [package.extras] @@ -1023,14 +1022,14 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "griffe" -version = "1.13.0" +version = "1.14.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "griffe-1.13.0-py3-none-any.whl", hash = "sha256:470fde5b735625ac0a36296cd194617f039e9e83e301fcbd493e2b58382d0559"}, - {file = "griffe-1.13.0.tar.gz", hash = "sha256:246ea436a5e78f7fbf5f24ca8a727bb4d2a4b442a2959052eea3d0bfe9a076e0"}, + {file = "griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0"}, + {file = "griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13"}, ] [package.dependencies] @@ -1048,11 +1047,12 @@ develop = false [package.dependencies] bs4 = "^0.0.2" +limits = "^5.6.0" loguru = "^0.7.3" omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} -pydantic = ">=2.11.7" -PyYAML = ">=6.0.2" -requests = "^2.32.4" +pydantic = ">=2.11.10" +PyYAML = ">=6.0.3" +requests = "^2.32.5" tomli = ">=2.2.1" tomlkit = ">=0.13.3" @@ -1060,7 +1060,7 @@ tomlkit = ">=0.13.3" type = "git" url = "https://github.com/pappnu/hexproof.git" reference = "dev" -resolved_reference = "4c56905b6f412d2266f0b0d7b6962f5829a19446" +resolved_reference = "34958ae70d675e09a30f8c82cda2877a0e453e8d" [[package]] name = "htmlmin2" @@ -1075,14 +1075,14 @@ files = [ [[package]] name = "identify" -version = "2.6.13" +version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, - {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, + {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, + {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, ] [package.extras] @@ -1137,30 +1137,6 @@ rawpy = ["numpy (>2)", "rawpy"] test = ["fsspec[github]", "pytest", "pytest-cov"] tifffile = ["tifffile"] -[[package]] -name = "importlib-metadata" -version = "8.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - [[package]] name = "inflate64" version = "1.0.1" @@ -1528,23 +1504,22 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "limits" -version = "5.5.0" +version = "5.6.0" description = "Rate limiting utilities" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "limits-5.5.0-py3-none-any.whl", hash = "sha256:57217d01ffa5114f7e233d1f5e5bdc6fe60c9b24ade387bf4d5e83c5cf929bae"}, - {file = "limits-5.5.0.tar.gz", hash = "sha256:ee269fedb078a904608b264424d9ef4ab10555acc8d090b6fc1db70e913327ea"}, + {file = "limits-5.6.0-py3-none-any.whl", hash = "sha256:b585c2104274528536a5b68864ec3835602b3c4a802cd6aa0b07419798394021"}, + {file = "limits-5.6.0.tar.gz", hash = "sha256:807fac75755e73912e894fdd61e2838de574c5721876a19f7ab454ae1fffb4b5"}, ] [package.dependencies] deprecated = ">=1.2" packaging = ">=21" -typing_extensions = "*" +typing-extensions = "*" [package.extras] -all = ["coredis (>=3.4.0,<6)", "memcachio (>=0.3)", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<7.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)", "valkey (>=6)", "valkey (>=6)"] async-memcached = ["memcachio (>=0.3)"] async-mongodb = ["motor (>=3,<4)"] async-redis = ["coredis (>=3.4.0,<6)"] @@ -1592,14 +1567,14 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.8.2" +version = "3.9" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, - {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, + {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"}, + {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"}, ] [package.extras] @@ -1632,73 +1607,101 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, ] [[package]] @@ -1932,14 +1935,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.1.7" +version = "7.2.0" description = "Mkdocs Markdown includer plugin." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_include_markdown_plugin-7.1.7-py3-none-any.whl", hash = "sha256:a0c13efe4f6b05a419c022e201055bf43145eed90de65f2353c33fb4005b6aa5"}, - {file = "mkdocs_include_markdown_plugin-7.1.7.tar.gz", hash = "sha256:677637e04c2d3497c50340be522e2a7f614124f592c7982d88b859f88d527a4c"}, + {file = "mkdocs_include_markdown_plugin-7.2.0-py3-none-any.whl", hash = "sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b"}, + {file = "mkdocs_include_markdown_plugin-7.2.0.tar.gz", hash = "sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3"}, ] [package.dependencies] @@ -1951,20 +1954,19 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.6.18" +version = "9.6.21" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.6.18-py3-none-any.whl", hash = "sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701"}, - {file = "mkdocs_material-9.6.18.tar.gz", hash = "sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05"}, + {file = "mkdocs_material-9.6.21-py3-none-any.whl", hash = "sha256:aa6a5ab6fb4f6d381588ac51da8782a4d3757cb3d1b174f81a2ec126e1f22c92"}, + {file = "mkdocs_material-9.6.21.tar.gz", hash = "sha256:b01aa6d2731322438056f360f0e623d3faae981f8f2d8c68b1b973f4f2657870"}, ] [package.dependencies] babel = ">=2.10,<3.0" backrefs = ">=5.7.post1,<6.0" -click = "<8.2.2" colorama = ">=0.4,<1.0" jinja2 = ">=3.1,<4.0" markdown = ">=3.2,<4.0" @@ -1977,7 +1979,7 @@ requests = ">=2.26,<3.0" [package.extras] git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<12.0)"] recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] [[package]] @@ -2042,14 +2044,14 @@ mkdocs = ">=1.0.3" [[package]] name = "mkdocstrings" -version = "0.30.0" +version = "0.30.1" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2"}, - {file = "mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444"}, + {file = "mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82"}, + {file = "mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f"}, ] [package.dependencies] @@ -2085,122 +2087,158 @@ mkdocstrings = ">=0.30" [[package]] name = "multidict" -version = "6.6.4" +version = "6.7.0" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb"}, - {file = "multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0"}, - {file = "multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987"}, - {file = "multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f"}, - {file = "multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f"}, - {file = "multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0"}, - {file = "multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb"}, - {file = "multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50"}, - {file = "multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b"}, - {file = "multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f"}, - {file = "multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2"}, - {file = "multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e"}, - {file = "multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"}, - {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"}, - {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"}, - {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"}, - {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"}, - {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"}, - {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"}, - {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"}, - {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"}, - {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"}, - {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"}, - {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"}, - {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"}, - {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"}, - {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"}, - {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"}, - {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"}, - {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"}, - {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:af7618b591bae552b40dbb6f93f5518328a949dac626ee75927bba1ecdeea9f4"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b6819f83aef06f560cb15482d619d0e623ce9bf155115150a85ab11b8342a665"}, - {file = "multidict-6.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d09384e75788861e046330308e7af54dd306aaf20eb760eb1d0de26b2bea2cb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a59c63061f1a07b861c004e53869eb1211ffd1a4acbca330e3322efa6dd02978"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350f6b0fe1ced61e778037fdc7613f4051c8baf64b1ee19371b42a3acdb016a0"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c5cbac6b55ad69cb6aa17ee9343dfbba903118fd530348c330211dc7aa756d1"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:630f70c32b8066ddfd920350bc236225814ad94dfa493fe1910ee17fe4365cbb"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8d4916a81697faec6cb724a273bd5457e4c6c43d82b29f9dc02c5542fd21fc9"}, - {file = "multidict-6.6.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e42332cf8276bb7645d310cdecca93a16920256a5b01bebf747365f86a1675b"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3be27440f7644ab9a13a6fc86f09cdd90b347c3c5e30c6d6d860de822d7cb53"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:21f216669109e02ef3e2415ede07f4f8987f00de8cdfa0cc0b3440d42534f9f0"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d9890d68c45d1aeac5178ded1d1cccf3bc8d7accf1f976f79bf63099fb16e4bd"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:edfdcae97cdc5d1a89477c436b61f472c4d40971774ac4729c613b4b133163cb"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0b2e886624be5773e69cf32bcb8534aecdeb38943520b240fed3d5596a430f2f"}, - {file = "multidict-6.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:be5bf4b3224948032a845d12ab0f69f208293742df96dc14c4ff9b09e508fc17"}, - {file = "multidict-6.6.4-cp39-cp39-win32.whl", hash = "sha256:10a68a9191f284fe9d501fef4efe93226e74df92ce7a24e301371293bd4918ae"}, - {file = "multidict-6.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee25f82f53262f9ac93bd7e58e47ea1bdcc3393cef815847e397cba17e284210"}, - {file = "multidict-6.6.4-cp39-cp39-win_arm64.whl", hash = "sha256:f9867e55590e0855bcec60d4f9a092b69476db64573c9fe17e92b0c50614c16a"}, - {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"}, - {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, + {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, + {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, + {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, + {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, + {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, + {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, + {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, + {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, + {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, + {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, + {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, + {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, + {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, + {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, + {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, + {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, + {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, + {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, + {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, + {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, + {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, + {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, + {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, + {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, + {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, + {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, + {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, + {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, + {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, + {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, + {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, + {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, + {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, + {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, + {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, + {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, + {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, + {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, + {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, + {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, + {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, + {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, + {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, + {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, + {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, + {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, + {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, + {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, + {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, + {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, ] [[package]] @@ -2222,50 +2260,50 @@ type = ["mypy", "mypy-extensions"] [[package]] name = "mypy" -version = "1.17.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, - {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, - {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, - {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, - {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, - {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, - {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, - {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, - {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, - {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, - {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, - {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, - {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, - {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, - {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, - {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, - {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, - {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, - {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, - {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, - {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, - {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, - {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, - {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, - {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, - {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, - {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -2327,86 +2365,86 @@ files = [ [[package]] name = "numpy" -version = "2.3.2" +version = "2.3.3" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, - {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, - {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, - {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, - {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, - {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, - {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, - {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, - {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, - {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, - {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, - {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, - {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, - {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, - {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, - {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, - {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, - {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, - {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, - {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, - {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, - {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, - {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, - {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, - {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, - {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, - {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, - {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, - {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, - {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, - {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, - {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, - {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, - {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, - {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, - {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, - {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, - {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, - {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f"}, + {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125"}, + {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48"}, + {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6"}, + {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa"}, + {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30"}, + {file = "numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57"}, + {file = "numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa"}, + {file = "numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe"}, + {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b"}, + {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8"}, + {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20"}, + {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea"}, + {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7"}, + {file = "numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf"}, + {file = "numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb"}, + {file = "numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6"}, + {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7"}, + {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c"}, + {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93"}, + {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae"}, + {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86"}, + {file = "numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8"}, + {file = "numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf"}, + {file = "numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b"}, + {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19"}, + {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30"}, + {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e"}, + {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3"}, + {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea"}, + {file = "numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd"}, + {file = "numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d"}, + {file = "numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7"}, + {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a"}, + {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe"}, + {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421"}, + {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021"}, + {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf"}, + {file = "numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0"}, + {file = "numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8"}, + {file = "numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d"}, + {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a"}, + {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54"}, + {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e"}, + {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097"}, + {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970"}, + {file = "numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5"}, + {file = "numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f"}, + {file = "numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db"}, + {file = "numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc"}, + {file = "numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029"}, ] [[package]] @@ -2421,26 +2459,26 @@ develop = false [package.dependencies] backoff = "^2.2.1" -click = "^8.2.1" +click = "^8.3.0" +limits = "^5.6.0" loguru = "^0.7.3" pathvalidate = "^3.3.1" pillow = "^11.3.0" py7zr = "^1.0.0" pydantic = "^2.11.7" python-dateutil = "^2.9.0.post0" -PyYAML = "^6.0.2" -ratelimit = "^2.2.1" -requests = "^2.32.4" +PyYAML = "^6.0.3" +requests = "^2.32.5" tomli = "^2.2.1" tomlkit = "^0.13.3" tqdm = "^4.67.1" -yarl = "^1.20.1" +yarl = "^1.22.0" [package.source] type = "git" url = "https://github.com/pappnu/omnitils.git" reference = "dev" -resolved_reference = "73cd55b7495b348a4a85380396e741214ea13a69" +resolved_reference = "9b87da6cd38983169ccf739e22ebfd9115866640" [[package]] name = "packaging" @@ -2711,14 +2749,14 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.52" +version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, - {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -2726,205 +2764,215 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.3.2" +version = "0.4.0" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, - {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, - {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, - {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, - {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, - {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, - {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, - {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, - {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, - {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, - {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, - {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, - {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, - {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, - {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, - {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, - {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, - {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, - {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, - {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, - {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, - {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, - {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, - {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, - {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, - {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, - {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, - {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, - {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, - {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, - {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, - {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, - {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5"}, + {file = "propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e"}, + {file = "propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3"}, + {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993"}, + {file = "propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57"}, + {file = "propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71"}, + {file = "propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3"}, + {file = "propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35"}, + {file = "propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1"}, + {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0"}, + {file = "propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577"}, + {file = "propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0"}, + {file = "propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3"}, + {file = "propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392"}, + {file = "propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc"}, + {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194"}, + {file = "propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c"}, + {file = "propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907"}, + {file = "propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937"}, + {file = "propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a"}, + {file = "propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7"}, + {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833"}, + {file = "propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc"}, + {file = "propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281"}, + {file = "propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc"}, + {file = "propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4"}, + {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6"}, + {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da"}, + {file = "propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db"}, + {file = "propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d"}, + {file = "propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4"}, + {file = "propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b"}, + {file = "propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90"}, + {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca"}, + {file = "propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93"}, + {file = "propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b"}, + {file = "propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f"}, + {file = "propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a"}, + {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012"}, + {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec"}, + {file = "propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373"}, + {file = "propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28"}, + {file = "propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0cd30341142c68377cf3c4e2d9f0581e6e528694b2d57c62c786be441053d2fc"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c46d37955820dd883cf9156ceb7825b8903e910bdd869902e20a5ac4ecd2c8b"}, + {file = "propcache-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b12df77eb19266efd146627a65b8ad414f9d15672d253699a50c8205661a820"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cdabd60e109506462e6a7b37008e57979e737dc6e7dfbe1437adcfe354d1a0a"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65ff56a31f25925ef030b494fe63289bf07ef0febe6da181b8219146c590e185"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96153e037ae065bb71cae889f23c933190d81ae183f3696a030b47352fd8655d"}, + {file = "propcache-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bf95be277fbb51513895c2cecc81ab12a421cdbd8837f159828a919a0167f96"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8d18d796ffecdc8253742fd53a94ceee2e77ad149eb9ed5960c2856b5f692f71"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4a52c25a51d5894ba60c567b0dbcf73de2f3cd642cf5343679e07ca3a768b085"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e0ce7f3d1faf7ad58652ed758cc9753049af5308b38f89948aa71793282419c5"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:545987971b2aded25ba4698135ea0ae128836e7deb6e18c29a581076aaef44aa"}, + {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da5c4c72ae40fd3ce87213ab057db66df53e55600d0b9e72e2b7f5a470a2cc4"}, + {file = "propcache-0.4.0-cp39-cp39-win32.whl", hash = "sha256:2015218812ee8f13bbaebc9f52b1e424cc130b68d4857bef018e65e3834e1c4d"}, + {file = "propcache-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:39f0f6a3b56e82dc91d84c763b783c5c33720a33c70ee48a1c13ba800ac1fa69"}, + {file = "propcache-0.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:236c8da353ea7c22a8e963ab78cddb1126f700ae9538e2c4c6ef471e5545494b"}, + {file = "propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557"}, + {file = "propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529"}, ] [[package]] name = "psd-tools" -version = "1.10.9" +version = "1.10.13" description = "Python package for working with Adobe Photoshop PSD files" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "psd_tools-1.10.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8519b7849569f3d44eeff9c3c93462be5ef329bc4ce03bb501d69f23844eb9d"}, - {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ad40c36a86182d6dc09ac6f8743df585cc238e50a88db0d2000e703cb59bded"}, - {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe420a47219fd371f7e0b49fdef2c46c87c628900df0c793c01d863fd702f451"}, - {file = "psd_tools-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57b85e8a2ac7ee4b28ed24fd335867c4454731ce6437d9610d0211809a40706"}, - {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7d3c3669ddcc04a6562738e5347169f9c60d4cc57c4561f72e4ee0be6c1122f9"}, - {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e3afbc4d2be2c21c05ae2889088eac80376a8a909ffda6910b0b533f3d1de3f"}, - {file = "psd_tools-1.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2f962f84581f8751d1e6b933affa4e8372c44e8895e2b98c6c05a240c88776e5"}, - {file = "psd_tools-1.10.9-cp310-cp310-win32.whl", hash = "sha256:01cbf330500fed71021dcf7cfb5c536d0215bc50174a6feee25df0f76f756756"}, - {file = "psd_tools-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:9511046b59b01e261339d261eecb8bf43dc0d296400fbebf614b8e599c285774"}, - {file = "psd_tools-1.10.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d26de0e49102a7a2aa8b48021c1f5f00d51a5059df5944a1e71bcb04a75b04d"}, - {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fc042be70363325c68e5ef4326d6318b3cc069f271cd8f21d1def4fa1a941a6"}, - {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16c672ddacba67a41aa1dcc294bbfaeca4f907c609adc6459c9d9568b7c0b95f"}, - {file = "psd_tools-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bca2420d12a8f3847a814a8afe8aeaa33906e36ff43fc606201a1584d001613"}, - {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a9b9d95bd5bd31d4280e6b70ff020933267e99fe9240ad0ac537f0b63217c8a"}, - {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:15900fbd009f2953e971635297a5de8d4e4fc5f58a9b219ff3b8eba0e8f71d0d"}, - {file = "psd_tools-1.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e840c2eb48c0f1a02c7614039980835e71924fdabffda8f53c433e83d10ca0f8"}, - {file = "psd_tools-1.10.9-cp311-cp311-win32.whl", hash = "sha256:e5142d9d2f58f02e547da7b096d0f2cceb1c7a34119e14eed3f2fbd972038220"}, - {file = "psd_tools-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:c6fccb9f6cbf2ca137fc6cfba2592fcb65a30a0b312cbd9200695ea45cc64815"}, - {file = "psd_tools-1.10.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d319ae67e6cf86c9fdc6f27cf598f378fae1e754ecf77fe4e928289b0823a060"}, - {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd8258210a4299e56f0ca5b9fe0145ae7d2034fbb790f43e19918c35be693d37"}, - {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9514641cc50e973847c135075cfa1de2340bc9f7e67cfba52b7f65005a15f0"}, - {file = "psd_tools-1.10.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c8a6cb9e50c5c0f970b354fa01950000f7fd7a03dfae53fd4550fcd8b2211a4"}, - {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:20960fefe641ce1b907287110ea8fb1ebfc47e67874f2855069721ed91e272da"}, - {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1d4eb712a64267fe08f4d590dcb5c08c5ab8216618ff011e002e8b1856f97ee3"}, - {file = "psd_tools-1.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a75b29a55e97193e79ad4696bb4fa0850f3016472a9fa0bcaf175189b9b156e4"}, - {file = "psd_tools-1.10.9-cp312-cp312-win32.whl", hash = "sha256:eecc632efa73d1f6731ea24563b675458b2f70849ace98e3d1835d83b2993a00"}, - {file = "psd_tools-1.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:27e03bc376ec977fe48d54894711c66b2084d08837ed93a29ded77b3ab1036cf"}, - {file = "psd_tools-1.10.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:140d683cbd758239f9bce5b78d35eb5c657c89c3b0825aa09e5c1eaeb5b3c746"}, - {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8726132b535da6364eaaf2da089ebbe399fbfa88c0e3ff571e74d9372eb49a01"}, - {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da063395a6e2d7a1b3979c44ac8e7dc93f21eb179c67e6d0d9fc86aa3b646be"}, - {file = "psd_tools-1.10.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec02bec6a895fe5aa54799717328dfcf57d156bc6dd1f54c00b48c8781380ea9"}, - {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:69f3b8b88af985d961a64029f154ba5593b757d61b7152ad39ea62bd11b6e788"}, - {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aaef0f8eed0bd9c042a3b4bb4be3bc8ebb1b0825fbe74d41574133b252b5c782"}, - {file = "psd_tools-1.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:887f05134315ea338c9bbbaa4e3289b42fb2cf6b3a55524833ca80d83517c058"}, - {file = "psd_tools-1.10.9-cp313-cp313-win32.whl", hash = "sha256:dcd5e3606142c17653706f3619c650ed8717f340a1ef4eb047800d76cc7b7072"}, - {file = "psd_tools-1.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe8e6ac8ece6805d0bba105516eec50079fd600d9e884510fa7edea327a603ae"}, - {file = "psd_tools-1.10.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:43bd172b19fbcaa6fdbc05b8907a4f272066ffae38600d75416b115b21707bf6"}, - {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b33efb9358aeadb3aed554097b1cd4a0afa80156e8292159066a7bcc6b0b977"}, - {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca88c09380cc78847f7592c363c1faabaddbd29f95fdc81472c2917ddf9d4c8"}, - {file = "psd_tools-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82a089d063c68b69aa42e49d6efaaf5c6127f098f9a323cb3cbd23b5f80f0a0b"}, - {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d65c12e05fd85085cc2b8ba269aedb6f99d070701f29fa98daee72f3c050a3d7"}, - {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08b4cd769bacbdadc33f54fd6614934f10ff31219666a2e4be5c236902332b19"}, - {file = "psd_tools-1.10.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e74268102ea68dc4be2326e9792a5ca26196ee6de757d2cbdf6501a901876087"}, - {file = "psd_tools-1.10.9-cp39-cp39-win32.whl", hash = "sha256:f4f839745dab89ad241221d532fb15c3e5c7c35670bc97c0dfff671614f688e7"}, - {file = "psd_tools-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:7e39ad46ddcf1a7857673805d18c62fb65d0fa90525cdfaa8c1e37ca7b7fb912"}, - {file = "psd_tools-1.10.9.tar.gz", hash = "sha256:292a26888cf389f702312417242c8ed0e919bce4ea318feea20a233e9dc11dd5"}, + {file = "psd_tools-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f59337c9787338f1847273b7d5dc3fde0c4d3e5884059ebe08b7e0ce5bdf7061"}, + {file = "psd_tools-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:06ea568b0f96b18df7ce400ab4e5bb917680af8ea010382f779e000bb4ecb0be"}, + {file = "psd_tools-1.10.13-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873736c32f961cfbf2c294812360aa6d0bc04e6538c46b060dfb46b8f50f31de"}, + {file = "psd_tools-1.10.13-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3e4aca47bb2c0c60ed2e660359d2bdfd42270dfaac91c5c4f28c468f5d9079"}, + {file = "psd_tools-1.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:92216ca858b093981a273978910dffed0cb2aa592c3dc3cb32deeac12c4b34b4"}, + {file = "psd_tools-1.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:553c548e91d6051e4781d17eabaef7063c20e12a3eaa98c1a0a06d493a7b4db6"}, + {file = "psd_tools-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:99fe2c05b3088bdcfddcd56d68b9d72ea99923af983e18153fa1d9fcaca7a8c8"}, + {file = "psd_tools-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d88f6f181333b1310a360e30dd7f7716c8df80e2f157837e91d9fee7b6372604"}, + {file = "psd_tools-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f249e7c2cffa70288d1d35b7bf79f18fd572ccc7700bcd924e451383173bc50"}, + {file = "psd_tools-1.10.13-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339e75a9b980c886a7fc7ea78a9dbfc117ad247e33c657ce19b879624ee0cb92"}, + {file = "psd_tools-1.10.13-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eec54dd2352fca42447e54cdd308c43fcd11c5ad5031a93d7639fbd27e668ce"}, + {file = "psd_tools-1.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f87b119e36c9b1dfa1de25ac2d56d3f6671897391c0d867795b0dc1e95d63fa6"}, + {file = "psd_tools-1.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b223b6a2d3ed7e9d006ae0a5c6fc503b9b205c9de673c661f1bd55b941baa07"}, + {file = "psd_tools-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:750073bd0a58800939b26fa766454f5cc9ca63b19c0b61dba84b1429f8e143b5"}, + {file = "psd_tools-1.10.13-cp311-cp311-win_arm64.whl", hash = "sha256:b768a1cbe0b189e700be2f09cfdec5560579b443e8bfedd57f5cd69b7b9cfb18"}, + {file = "psd_tools-1.10.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8bd22dd4b9dda0f2237d6be93b121d72f9754a65efc1222de8ee0f91e56cee63"}, + {file = "psd_tools-1.10.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e386dec96ec52753dd078d1777dd2d3265cccd6334d89f8e6ddea9d4b3b99328"}, + {file = "psd_tools-1.10.13-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ab4811dc5beb8ba110b70b0af191602deec4973e10a1db99f040c1c4d685089"}, + {file = "psd_tools-1.10.13-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d6a2055fa1176fe6b7e134432b71bef8eb7ac18aa785c141ba5c098a039eb68"}, + {file = "psd_tools-1.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afda6cbb96c5c4791955e4c1f4bf2f326139fa148b297f01ef5de0e0e0f14fdc"}, + {file = "psd_tools-1.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95e2017c4eb942524de85816fcd9207c10eb379bb4a78b77324c3ee294ed0e1e"}, + {file = "psd_tools-1.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:8d2347f58c17cdcec5583b0a4dd50ed6f8c68984df27946cce8ca63c88975098"}, + {file = "psd_tools-1.10.13-cp312-cp312-win_arm64.whl", hash = "sha256:b5c5192f8552d1f2b74d1f58454965104a0551aa84e829c105aa25f99e1cdf9b"}, + {file = "psd_tools-1.10.13-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:2a01ce3f67f943239286bfb3aef51f7bbce0fe735299ff0e3ea36746206b9f7f"}, + {file = "psd_tools-1.10.13-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:3b245056f0103b2c52f850317363825fe9d452ed4a2407aa5cea0199423c7fee"}, + {file = "psd_tools-1.10.13-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2dcda60d03bbe9bb7811fbae32fde19e592320405bf1a4005980d379ae3fbfd"}, + {file = "psd_tools-1.10.13-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9198ce1172def62d0b61b6b4392e44b29bf1d3acd3ae962bf47e500f318b461"}, + {file = "psd_tools-1.10.13-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90d7346b4670064ab0c48a740965a75729ce862505f0a5a71eefbe0d4daa1d63"}, + {file = "psd_tools-1.10.13-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a6249c47131b6fb027107c7077fe3caafec380d169f20092744c30da8ef316ca"}, + {file = "psd_tools-1.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:1158b771ead967365ed170c1f4e7ed990f73a86d7a9e502e3c90d92c85a59a2e"}, + {file = "psd_tools-1.10.13-cp313-cp313-win_arm64.whl", hash = "sha256:d27d264c56cd27b01ad970c9e020ed46232e03206feea315f9309a61a367ba2c"}, + {file = "psd_tools-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a5c647c4b33a872e87b5846230135644666481a4e10bce3570402386b66729d7"}, + {file = "psd_tools-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:205ecd744b8f88f33a9f3ee16dc24da77c7081a02b5453e5d6648b8cac18d0da"}, + {file = "psd_tools-1.10.13-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a746f91dc3430d112abaf2839274b825d9282a166a096d0012138d661b46d803"}, + {file = "psd_tools-1.10.13-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f8adc10e1d70508d1982837df8a079acb49aba63212a9db1d7f85dcaef4602a"}, + {file = "psd_tools-1.10.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e527adb03d30ad4d306ae7c3e20842963989e267615ec0470464b5cb2ecc0b01"}, + {file = "psd_tools-1.10.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8f729643723d10d13eed78534d71a02b384bf63969c40a4fec9fb1278ce0927a"}, + {file = "psd_tools-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:8f5ac5a1f02ee3f06620e3112c307058ebcbfaa1ab905cbd74ae71ea1c03f5e4"}, ] [package.dependencies] aggdraw = "*" attrs = ">=23.0.0" -docopt = ">=0.6.0" numpy = "*" Pillow = ">=10.3.0" scikit-image = "*" scipy = "*" -[package.extras] -dev = ["ipython", "pytest", "pytest-cov", "ruff", "types-docopt"] -docs = ["sphinx", "sphinx_rtd_theme"] - [[package]] name = "psutil" -version = "7.0.0" -description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +version = "7.1.0" +description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, - {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, - {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, - {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, - {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, - {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, - {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, + {file = "psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13"}, + {file = "psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d"}, + {file = "psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca"}, + {file = "psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d"}, + {file = "psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07"}, + {file = "psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2"}, ] markers = {main = "sys_platform != \"cygwin\""} [package.extras] -dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] -test = ["pytest", "pytest-xdist", "setuptools"] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] +test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] [[package]] name = "py7zr" @@ -3031,15 +3079,15 @@ files = [ [[package]] name = "pycparser" -version = "2.22" +version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "platform_python_implementation == \"PyPy\"" +markers = "platform_python_implementation == \"PyPy\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] @@ -3095,14 +3143,14 @@ files = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.10" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, + {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, ] [package.dependencies] @@ -3244,24 +3292,24 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyinstaller" -version = "6.15.0" +version = "6.16.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.15,>=3.8" groups = ["dev"] files = [ - {file = "pyinstaller-6.15.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:9f00c71c40148cd1e61695b2c6f1e086693d3bcf9bfa22ab513aa4254c3b966f"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cbcc8eb77320c60722030ac875883b564e00768fe3ff1721c7ba3ad0e0a277e9"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c33e6302bc53db2df1104ed5566bd980b3e0ee7f18416a6e3caa908c12a54542"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:eb902d0fed3bb1f8b7190dc4df5c11f3b59505767e0d56d1ed782b853938bbf3"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b4df862adae7cf1f08eff53c43ace283822447f7f528f72e4f94749062712f15"}, - {file = "pyinstaller-6.15.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b9ebf16ed0f99016ae8ae5746dee4cb244848a12941539e62ce2eea1df5a3f95"}, - {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:22193489e6a22435417103f61e7950363bba600ef36ec3ab1487303668c81092"}, - {file = "pyinstaller-6.15.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:18f743069849dbaee3e10900385f35795a5743eabab55e99dcc42f204e40a0db"}, - {file = "pyinstaller-6.15.0-py3-none-win32.whl", hash = "sha256:60da8f1b5071766b45c0f607d8bc3d7e59ba2c3b262d08f2e4066ba65f3544a2"}, - {file = "pyinstaller-6.15.0-py3-none-win_amd64.whl", hash = "sha256:cbea297e16eeda30b41c300d6ec2fd2abea4dbd8d8a32650eeec36431c94fcd9"}, - {file = "pyinstaller-6.15.0-py3-none-win_arm64.whl", hash = "sha256:f43c035621742cf2d19b84308c60e4e44e72c94786d176b8f6adcde351b5bd98"}, - {file = "pyinstaller-6.15.0.tar.gz", hash = "sha256:a48fc4644ee4aa2aa2a35e7b51f496f8fbd7eecf6a2150646bbf1613ad07bc2d"}, + {file = "pyinstaller-6.16.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fd1c785219a87ca747c21fa92f561b0d2926a7edc06d0a0fe37f3736e00bd7a"}, + {file = "pyinstaller-6.16.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b756ddb9007b8141c5476b553351f9d97559b8af5d07f9460869bfae02be26b0"}, + {file = "pyinstaller-6.16.0-py3-none-manylinux2014_i686.whl", hash = "sha256:0a48f55b85ff60f83169e10050f2759019cf1d06773ad1c4da3a411cd8751058"}, + {file = "pyinstaller-6.16.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:73ba72e04fcece92e32518bbb1e1fb5ac2892677943dfdff38e01a06e8742851"}, + {file = "pyinstaller-6.16.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b1752488248f7899281b17ca3238eefb5410521291371a686a4f5830f29f52b3"}, + {file = "pyinstaller-6.16.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ba618a61627ee674d6d68e5de084ba17c707b59a4f2a856084b3999bdffbd3f0"}, + {file = "pyinstaller-6.16.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c8b7ef536711617e12fef4673806198872033fa06fa92326ad7fd1d84a9fa454"}, + {file = "pyinstaller-6.16.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ebf84d02c51fed19b82a8abb4df536923abd55bb684d694e1356e4ae2a0ce5"}, + {file = "pyinstaller-6.16.0-py3-none-win32.whl", hash = "sha256:6d5f8617f3650ff9ef893e2ab4ddbf3c0d23d0c602ef74b5df8fbef4607840c8"}, + {file = "pyinstaller-6.16.0-py3-none-win_amd64.whl", hash = "sha256:bc10eb1a787f99fea613509f55b902fbd2d8b73ff5f51ff245ea29a481d97d41"}, + {file = "pyinstaller-6.16.0-py3-none-win_arm64.whl", hash = "sha256:d0af8a401de792c233c32c44b16d065ca9ab8262ee0c906835c12bdebc992a64"}, + {file = "pyinstaller-6.16.0.tar.gz", hash = "sha256:53559fe1e041a234f2b4dcc3288ea8bdd57f7cad8a6644e422c27bb407f3edef"}, ] [package.dependencies] @@ -3279,14 +3327,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.8" +version = "2025.9" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2025.8-py3-none-any.whl", hash = "sha256:8d0b8cfa0cb689a619294ae200497374234bd4e3994b3ace2a4442274c899064"}, - {file = "pyinstaller_hooks_contrib-2025.8.tar.gz", hash = "sha256:3402ad41dfe9b5110af134422e37fc5d421ba342c6cb980bd67cb30b7415641c"}, + {file = "pyinstaller_hooks_contrib-2025.9-py3-none-any.whl", hash = "sha256:ccbfaa49399ef6b18486a165810155e5a8d4c59b41f20dc5da81af7482aaf038"}, + {file = "pyinstaller_hooks_contrib-2025.9.tar.gz", hash = "sha256:56e972bdaad4e9af767ed47d132362d162112260cbe488c9da7fee01f228a5a6"}, ] [package.dependencies] @@ -3314,14 +3362,14 @@ extra = ["pygments (>=2.19.1)"] [[package]] name = "pyparsing" -version = "3.2.3" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" +version = "3.2.5" +description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, - {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, + {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, + {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, ] [package.extras] @@ -3418,14 +3466,14 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchm [[package]] name = "pytest" -version = "8.4.1" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] @@ -3498,65 +3546,85 @@ files = [ [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] @@ -3576,106 +3644,106 @@ pyyaml = "*" [[package]] name = "pyzstd" -version = "0.17.0" +version = "0.18.0" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" groups = ["main"] files = [ - {file = "pyzstd-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ac857abb4c4daea71f134e74af7fe16bcfeec40911d13cf9128ddc600d46d92"}, - {file = "pyzstd-0.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d84e8d1cbecd3b661febf5ca8ce12c5e112cfeb8401ceedfb84ab44365298ac"}, - {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f829fa1e7daac2e45b46656bdee13923150f329e53554aeaef75cceec706dd8c"}, - {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994de7a13bb683c190a1b2a0fb99fe0c542126946f0345360582d7d5e8ce8cda"}, - {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3eb213a22823e2155aa252d9093c62ac12d7a9d698a4b37c5613f99cb9de327"}, - {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c451cfa31e70860334cc7dffe46e5178de1756642d972bc3a570fc6768673868"}, - {file = "pyzstd-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d66dc6f15249625e537ea4e5e64c195f50182556c3731f260b13c775b7888d6b"}, - {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:308d4888083913fac2b7b6f4a88f67c0773d66db37e6060971c3f173cfa92d1e"}, - {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a3b636f37af9de52efb7dd2d2f15deaeabdeeacf8e69c29bf3e7e731931e6d66"}, - {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4c07391c67b496d851b18aa29ff552a552438187900965df57f64d5cf2100c40"}, - {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e8bd12a13313ffa27347d7abe20840dcd2092852ab835a8e86008f38f11bd5ac"}, - {file = "pyzstd-0.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e27bfab45f9cdab0c336c747f493a00680a52a018a8bb7a1f787ddde4b29410"}, - {file = "pyzstd-0.17.0-cp310-cp310-win32.whl", hash = "sha256:7370c0978edfcb679419f43ec504c128463858a7ea78cf6d0538c39dfb36fce3"}, - {file = "pyzstd-0.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:564f7aa66cda4acd9b2a8461ff0c6a6e39a977be3e2e7317411a9f7860d7338d"}, - {file = "pyzstd-0.17.0-cp310-cp310-win_arm64.whl", hash = "sha256:fccff3a37fa4c513fe1ebf94cb9dc0369c714da22b5671f78ddcbc7ec8f581cc"}, - {file = "pyzstd-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06d1e7afafe86b90f3d763f83d2f6b6a437a8d75119fe1ff52b955eb9df04eaa"}, - {file = "pyzstd-0.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc827657f644e4510211b49f5dab6b04913216bc316206d98f9a75214361f16e"}, - {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecffadaa2ee516ecea3e432ebf45348fa8c360017f03b88800dd312d62ecb063"}, - {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:596de361948d3aad98a837c98fcee4598e51b608f7e0912e0e725f82e013f00f"}, - {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd3a8d0389c103e93853bf794b9a35ac5d0d11ca3e7e9f87e3305a10f6dfa6b2"}, - {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1356f72c7b8bb99b942d582b61d1a93c5065e66b6df3914dac9f2823136c3228"}, - {file = "pyzstd-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f514c339b013b0b0a2ed8ea6e44684524223bd043267d7644d7c3a70e74a0dd"}, - {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4de16306821021c2d82a45454b612e2a8683d99bfb98cff51a883af9334bea0"}, - {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:aeb9759c04b6a45c1b56be21efb0a738e49b0b75c4d096a38707497a7ff2be82"}, - {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a5b31ddeada0027e67464d99f09167cf08bab5f346c3c628b2d3c84e35e239a"}, - {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8338e4e91c52af839abcf32f1f65f3b21e2597ffe411609bdbdaf10274991bd0"}, - {file = "pyzstd-0.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628e93862feb372b4700085ec4d1d389f1283ac31900af29591ae01019910ff3"}, - {file = "pyzstd-0.17.0-cp311-cp311-win32.whl", hash = "sha256:c27773f9c95ebc891cfcf1ef282584d38cde0a96cb8d64127953ad752592d3d7"}, - {file = "pyzstd-0.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:c043a5766e00a2b7844705c8fa4563b7c195987120afee8f4cf594ecddf7e9ac"}, - {file = "pyzstd-0.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:efd371e41153ef55bf51f97e1ce4c1c0b05ceb59ed1d8972fc9aa1e9b20a790f"}, - {file = "pyzstd-0.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ac330fc4f64f97a411b6f3fc179d2fe3050b86b79140e75a9a6dd9d6d82087f"}, - {file = "pyzstd-0.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:725180c0c4eb2e643b7048ebfb45ddf43585b740535907f70ff6088f5eda5096"}, - {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c20fe0a60019685fa1f7137cb284f09e3f64680a503d9c0d50be4dd0a3dc5ec"}, - {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d97f7aaadc3b6e2f8e51bfa6aa203ead9c579db36d66602382534afaf296d0db"}, - {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42dcb34c5759b59721997036ff2d94210515d3ef47a9de84814f1c51a1e07e8a"}, - {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6bf05e18be6f6c003c7129e2878cffd76fcbebda4e7ebd7774e34ae140426cbf"}, - {file = "pyzstd-0.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f7c3a5144aa4fbccf37c30411f6b1db4c0f2cb6ad4df470b37929bffe6ca0"}, - {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9efd4007f8369fd0890701a4fc77952a0a8c4cb3bd30f362a78a1adfb3c53c12"}, - {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5f8add139b5fd23b95daa844ca13118197f85bd35ce7507e92fcdce66286cc34"}, - {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:259a60e8ce9460367dcb4b34d8b66e44ca3d8c9c30d53ed59ae7037622b3bfc7"}, - {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:86011a93cc3455c5d2e35988feacffbf2fa106812a48e17eb32c2a52d25a95b3"}, - {file = "pyzstd-0.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:425c31bc3de80313054e600398e4f1bd229ee61327896d5d015e2cd0283c9012"}, - {file = "pyzstd-0.17.0-cp312-cp312-win32.whl", hash = "sha256:7c4b88183bb36eb2cebbc0352e6e9fe8e2d594f15859ae1ef13b63ebc58be158"}, - {file = "pyzstd-0.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c31947e0120468342d74e0fa936d43f7e1dad66a2262f939735715aa6c730e8"}, - {file = "pyzstd-0.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1d0346418abcef11507356a31bef5470520f6a5a786d4e2c69109408361b1020"}, - {file = "pyzstd-0.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6cd1a1d37a7abe9c01d180dad699e3ac3889e4f48ac5dcca145cc46b04e9abd2"}, - {file = "pyzstd-0.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a44fd596eda06b6265dc0358d5b309715a93f8e96e8a4b5292c2fe0e14575b3"}, - {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a99b37453f92f0691b2454d0905bbf2f430522612f6f12bbc81133ad947eb97"}, - {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63d864e9f9e624a466070a121ace9d9cbf579eac4ed575dee3b203ab1b3cbeee"}, - {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e58bc02b055f96d1f83c791dd197d8c80253275a56cd84f917a006e9f528420d"}, - {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e62df7c0ba74618481149c849bc3ed7d551b9147e1274b4b3170bbcc0bfcc0a"}, - {file = "pyzstd-0.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42ecdd7136294f1becb8e57441df00eaa6dfd7444a8b0c96a1dfba5c81b066e7"}, - {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:be07a57af75f99fc39b8e2d35f8fb823ecd7ef099cd1f6203829a5094a991ae2"}, - {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0d41e6f7ec2a70dab4982157a099562de35a6735c890945b4cebb12fb7eb0be0"}, - {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f482d906426756e7cc9a43f500fee907e1b3b4e9c04d42d58fb1918c6758759b"}, - {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:827327b35605265e1d05a2f6100244415e8f2728bb75c951736c9288415908d7"}, - {file = "pyzstd-0.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a55008f80e3390e4f37bd9353830f1675f271d13d6368d2f1dc413b7c6022b3"}, - {file = "pyzstd-0.17.0-cp313-cp313-win32.whl", hash = "sha256:a4be186c0df86d4d95091c759a06582654f2b93690503b1c24d77f537d0cf5d0"}, - {file = "pyzstd-0.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:251a0b599bd224ec66f39165ddb2f959d0a523938e3bbfa82d8188dc03a271a2"}, - {file = "pyzstd-0.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:ce6d5fd908fd3ddec32d1c1a5a7a15b9d7737d0ef2ab20fe1e8261da61395017"}, - {file = "pyzstd-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5cb23c3c4ba4105a518cfbe8a566f9482da26f4bc8c1c865fd66e8e266be071"}, - {file = "pyzstd-0.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10b5d9215890a24f22505b68add26beeb2e3858bbe738a7ee339f0db8e29d033"}, - {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db1cff52fd24caf42a2cfb7e5d8dc822b93e9fac5dab505d0bd22e302061e2d2"}, - {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3caad3106e0e80f76acbb19c15e1e834ba6fd44dd4c82719ef8e3374f7fafd3"}, - {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e52e1de31b935e27568742145d8b4d0f204a1605e36f4e1e2846e0d39bed98"}, - {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaa046bc9e751c4083102f3624a52bbb66e20e7aa3e28673543b22e69d9b57cd"}, - {file = "pyzstd-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cc9310bdb7cf2c70098aab40fb6bf68faaf0149110c6ef668996e7957e0147a"}, - {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3619075966456783818904f9d9e213c6fe2e583d5beb545fa1968b1848781e0f"}, - {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3844f8c7d7850580423b1b33601b016b3b913d18deb6fe14a7641b9c2714275c"}, - {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab53f91280b7b639c47bb2048e01182230e7cf3f0f0980bdb405b4241cfb705e"}, - {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:75252ee53e53a819ea7ac4271f66686018bc8b98ef12628269f099c10d881077"}, - {file = "pyzstd-0.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0795afdaa34e1ed7f3d7552100cd57a1cef9d7310b386a893e0890e9a585b427"}, - {file = "pyzstd-0.17.0-cp39-cp39-win32.whl", hash = "sha256:f7316be5a5246b6bbdd807c7a4f10382b6b02c3afc5ae6acd2e266a84f715493"}, - {file = "pyzstd-0.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:121e8fac3e24b881fed59d638100b80c34f6347c02d2f24580f633451939f2d7"}, - {file = "pyzstd-0.17.0-cp39-cp39-win_arm64.whl", hash = "sha256:fe36ccda67f73e909ac305984fe13b7b5a79296706d095a80472ada4413174c2"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c56f99c697130f39702e07ab9fa0bb4c929c7bfe47c0a488dea732bd8a8752a"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:152bae1b2197bcd41fc143f93acd23d474f590162547484ca04ce5874c4847de"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2ddbbd7614922e52018ba3e7bb4cbe6f25b230096831d97916b8b89be8cd0cb"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6f3f152888825f71fd2cf2499f093fac252a5c1fa15ab8747110b3dc095f6b"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d00a2d2bddf51c7bf32c17dc47f0f49f47ebae07c2528b9ee8abf1f318ac193"}, - {file = "pyzstd-0.17.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d79e3eff07217707a92c1a6a9841c2466bfcca4d00fea0bea968f4034c27a256"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3ce6bac0c4c032c5200647992a8efcb9801c918633ebe11cceba946afea152d9"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:a00998144b35be7c485a383f739fe0843a784cd96c3f1f2f53f1a249545ce49a"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8521d7bbd00e0e1c1fd222c1369a7600fba94d24ba380618f9f75ee0c375c277"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da65158c877eac78dcc108861d607c02fb3703195c3a177f2687e0bcdfd519d0"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:226ca0430e2357abae1ade802585231a2959b010ec9865600e416652121ba80b"}, - {file = "pyzstd-0.17.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e3a19e8521c145a0e2cd87ca464bf83604000c5454f7e0746092834fd7de84d1"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:56ed2de4717844ffdebb5c312ec7e7b8eb2b69eb72883bbfe472ba2c872419e6"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc61c47ca631241081c0c99895a1feb56dab4beab37cac7d1f9f18aff06962eb"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd61757a4020590dad6c20fdbf37c054ed9f349591a0d308c3c03c0303ce221"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d6cce91a8ac8ae1aab06684a8bf0dee088405de7f451e1e89776ddc1f40074"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc668b67a13bf6213d0a9c09edc1f4842ed680b92fc3c9361f55a904903bfd1f"}, - {file = "pyzstd-0.17.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a67d7ef18715875b31127eb90075c03ced722fd87902b34bca4b807a2ce1e4d9"}, - {file = "pyzstd-0.17.0.tar.gz", hash = "sha256:d84271f8baa66c419204c1dd115a4dec8b266f8a2921da21b81764fa208c1db6"}, + {file = "pyzstd-0.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79bb84d866bf57ad2c4bc6b8247628b38e965c4f66288f887bf90f546a42ae04"}, + {file = "pyzstd-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0576c48e2f7a2c457538414a6197397c343b1bf5bfe9332b049afd0366c0c92"}, + {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7702484795ee3c16c48a03d990123e833f1e1d6baabbe9a53256238eb04cbc"}, + {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c412ac29a9ebb76c8c40f2df146327b460ce184bbbdaa5bc9257317dce4caa8"}, + {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:36baae4201196c2ec6567faf4a3f19c68211efc2fca30836c885b848ed057f66"}, + {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f6d9c8a535af243c5a19f2d66c3733595ab633e00b97237d877e70e8389edc5"}, + {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a533550740ce8c721aae27b377fb1160df68a9f457f16015ec8e47547a033dfc"}, + {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdd76049c8ccbb98276cfa78d807b4a497ec6bad2603361eceae993c6130e5bf"}, + {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09b73fe07a8d81898ef1575cb3063816168abb3305c1a9f30110383b61a4ee92"}, + {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6baf9fd75d0af4f5d677b6e2d8dd3deb359c4ec2250c8536fe5ea48fd9305199"}, + {file = "pyzstd-0.18.0-cp310-cp310-win32.whl", hash = "sha256:c0634ab42226d2ad96c94d57fd242df2ca9417350c2969eb97c8c61d9574ba69"}, + {file = "pyzstd-0.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:ec99569321a99b9868666c85a5846151f9a16b6a222b59b2570e2ddeefd4d80c"}, + {file = "pyzstd-0.18.0-cp310-cp310-win_arm64.whl", hash = "sha256:85371149cc1d8168461981084438b9f2f139c1699e989fef44562f7504ba0632"}, + {file = "pyzstd-0.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:848914835a8a984d4c5fad2355dc66f0aca979b35ec22753c9e694be8e98403c"}, + {file = "pyzstd-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3938fea87fe83113b5d8ec2925bb265b4c540e374bb0ec73e5528de58d68c393"}, + {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9af4bcde7dde46ca7e82a4c6f5fda1760bcbfd15525dbea36fe625263ef06b5e"}, + {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15d9419d173d26de25342235256aba363190e48e3fd8a8988420a26221b45320"}, + {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b84f75f0494087afad31363e80a3463d1f32a0a6265f1a24660e6422b2b6fa6"}, + {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cfcdf0e46020bda2e98814464ca3ae830da83937c4c61776bf8835c7094214e"}, + {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8551b6bc3690fb76e730967a628b6aab0d9331c38a41f5cddb546be994771191"}, + {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6883b47a4d5d5489890e24e74ef14c1f16dcd68bb326b86911ae0e254e33e4b7"}, + {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929dec930296362ce03fee81877fa93a68ca4de3af75fdfa96ecbe0e366b2ee3"}, + {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:278c80fdeaf857b620295cc815a31f6478fcb217d476ac889985a43b2b67e9bd"}, + {file = "pyzstd-0.18.0-cp311-cp311-win32.whl", hash = "sha256:0d1b678644894e49b5a448f02eebe0ac31bde6f51813168f5ff223d7212e1974"}, + {file = "pyzstd-0.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:8285a464aed201b166bb0d2f4667485b61b607cf89f12943b1f21f7e84cb4550"}, + {file = "pyzstd-0.18.0-cp311-cp311-win_arm64.whl", hash = "sha256:942badf996589e5ab6cbdd0f7dd33f5dc2cd7ed0b65441c96b9a12ffa7700d51"}, + {file = "pyzstd-0.18.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5eef13ee3e230e50c01b288d581664e8758f7b831271f6f32cfc29823a6ab365"}, + {file = "pyzstd-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f78d6ef80d2f355b5bc1a897e9aa58659e85170b3fa268f3211c4979c768264c"}, + {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:394175aeeb4e2255ff5340b32f6db79375b3ffb25514fe4c1439015a7f335ec2"}, + {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3250c551f526d3b966cf4a2199a8d9538dc5c7083b7a26a45f305f8f2ab20a06"}, + {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a99ca80053ca37be21f05f6c4152c70777e0eface72b08277cb4b10b6d286e79"}, + {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5dc4488536e87ff0aac698b9cd65f2913ac87417b3952d80be32463c8e95cc35"}, + {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c12da158f6ec1180be0a3d6f531050dfc1357a25e5d0fd8dd99d4506d2a3f448"}, + {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f9a7d6bff36dfbe87dce1730e4b70d6ab49058a6f8ea22e85b33642491a2d053"}, + {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0f56086bf8019f7c809a406dcc182ce0fb0d3623a9edf351ed80dbb484514613"}, + {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1eb69217ad9b760537e93f2d578c7927b788a9cac0e2104e536855a2797b5b09"}, + {file = "pyzstd-0.18.0-cp312-cp312-win32.whl", hash = "sha256:05ce49412c7aef970e0a6be8e9add4748bc474a7f13533a14555642022f871e9"}, + {file = "pyzstd-0.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:e951c3013b9df479cff758d578b83837b2531d02fb6c3e59166a756795697e19"}, + {file = "pyzstd-0.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:33b54781c66a86e33c93c89ae426811d0aa35a216a23116fc5d5162449284305"}, + {file = "pyzstd-0.18.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:65117997d1e10e9b41336c90c2c4877c8d27533f753272805ff39df15fd5298a"}, + {file = "pyzstd-0.18.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8550efbfb5944343666d0e79d6a3687adcbeb4dbf17aa743146a25e72d12d47f"}, + {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac61854c4a77df66695540549a89f4c67039e4181a9158b8646425f1d56d947a"}, + {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4c453369483f67480f86d67a7b63ef22827db65e7f0d4bec7992bb81751a94b9"}, + {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ef4b757b2df808ac15058fc2aa41e07d93843ee5a95629ff51eb6e8f1950951"}, + {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b42529770febd331e23c5e8a68e9899acb0cc0806ee4c970354806c0ceeec6c7"}, + {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f54d13c269cdc37d2f73c9b3e70c6d2bb168dec768a472d54c2ed830bb19fb9"}, + {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6686460ca4be536dca1b6f2f80055f383a78e92e68e03a14806428572c4fdba"}, + {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8da3978d7de9095cacc5089bd0c435ab84ebd127e0979cd31fa1b216111644af"}, + {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1ebc87e6e50547cff97e07c3fed9999d79b6327c9c4143c3049a7cfeacb2cdba"}, + {file = "pyzstd-0.18.0-cp313-cp313-win32.whl", hash = "sha256:2dd203f2534b16dea2761394fda4e0f3c465a5109ae6450bdaada67e6ac14a45"}, + {file = "pyzstd-0.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:98f43488f88b859291d6bdc51cc7793d1eab17aa9382b17d762944bbb8567c98"}, + {file = "pyzstd-0.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:cff8922e25e19d8fbd95b53f451e637bc80e826ab53c8777a885d4e99d1c0c2d"}, + {file = "pyzstd-0.18.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:67f795ec745cfd6930cdaf5118fcdd8d87ce02b07b254d37efe75afd33ce9917"}, + {file = "pyzstd-0.18.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a8a589673b9b417a084e393f18d09a16b67b87a80f80da6d3b4f84dd983c9b3d"}, + {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdaee8c33f96a6568225e821e6cc33045917628ae0bc7d8d3855332085c1aa7c"}, + {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42bf45d8e835d7c9c0bef98ff703143a5129edf09ef6c3b757037cbf79eabcaa"}, + {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f4dff2a15e2047baea9359d3a547dee80f61887f17e0f23190b4b932fd617e4"}, + {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ed87932d6c534fc8921f7d44a4dadb32881e10ebc68935175a2cba254f5cc83"}, + {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d08a372b2b7fa1fd24217424e13d3d794e01299c43c8bd55f50934ef0785779"}, + {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:e8403108172e24622f51732a336a89fe32bf3842965e0dc677c65df3a562f3ad"}, + {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5604eeb7f00ec308b7e878dae92abfc4eee2e5d238765a62d4fadc0d57bbbff3"}, + {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6b300c5240409f1e7ab9972ab2a880a1949447d8414dbc11d89c10bfcb31aa5"}, + {file = "pyzstd-0.18.0-cp314-cp314-win32.whl", hash = "sha256:83f4fe1409a59c45a5e6fccb4d451e1e3dd03a5fabebd2dd6ba651468f54025e"}, + {file = "pyzstd-0.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:73c3dcd9a16f1669ed6eef0dad1d840b7dd6070ab7d48719171ca691101e7975"}, + {file = "pyzstd-0.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:61333bbb337b9746284624ed14f6238838dfae1e395691ba49f227015374f760"}, + {file = "pyzstd-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bccd16621016b83c2d5d40408806a841bbca2860370dca5ef0e3db005417aca"}, + {file = "pyzstd-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c7ee6747541594a5851bae720d5ab070ba9ef644df779507f35819ea61fd83fd"}, + {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea0d70b4ec72b9d5feae4ec665ef8a4cd48f442921f2100117229c900a5a713"}, + {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d581aeeba9a3ed13e304b0efc27efdf310b58c1e69ebb99a08e0eeea3a392310"}, + {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d582d2fab7cc3e7606c2b09093f914e6e8b942ec52aa992a3a25d9d3ed7ba295"}, + {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a25a72afa7d66d47a881e475ffe88d9961b36052bf6a512af3b84de22b20d41f"}, + {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5b4feed895f32b314f2b3aa3ba6a4e0ce903c6764f31ad78e68b6c3fa31415ac"}, + {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:20d9524adbc4efc8a1680e59cc325bc73ff56bf70bb54d233c3540efcb7bf476"}, + {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:72c25d14217854883b571f101253d39443ea2f226f85cf3223b4d4a4d644618d"}, + {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c335605ac7d018ca2d4d68cc0bac10e3c4ccf8e9686972dfc569a4df53f7a8d3"}, + {file = "pyzstd-0.18.0-cp39-cp39-win32.whl", hash = "sha256:64ebf9bd8065388d778c4ab6d9c4e913c00633abcfbf55236202dd0398520cc0"}, + {file = "pyzstd-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a32751ac634eb685bec42935b0f6e494f018843da09596da3f2a0072ae8273b"}, + {file = "pyzstd-0.18.0-cp39-cp39-win_arm64.whl", hash = "sha256:6b64efb254fdc3c90ed4c74185beee62c24e517288aacfb3abd95c127e6f8f52"}, + {file = "pyzstd-0.18.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:35934369fcdfde6fb932f88fa441337c8ddaf4b08e7b0b12952010f0ba2082f7"}, + {file = "pyzstd-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:55b8e12c9657359a697440e88a8535d1a771025e5d8f1c3087ad69ba11bee6d2"}, + {file = "pyzstd-0.18.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134d33d3e56b5083c8f827b63254c2abf85d6ace2b323e69d28e3954b5b71883"}, + {file = "pyzstd-0.18.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6c4bffa0157ef9e5cfa32413a5a79448e5affadece4982df274f1b5aae3a680"}, + {file = "pyzstd-0.18.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8c36824d94cf77997a899b60886cc2be3ac969083f1d74eb4dd4127234ba50a4"}, + {file = "pyzstd-0.18.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:788e0889db436cd6d16a3b490006ab80a913d8ce6f46db127f1888066ff4560b"}, + {file = "pyzstd-0.18.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e70b7c36a40d7f946bf6391a206374b057299735d366fad6524d3b9f392441f"}, + {file = "pyzstd-0.18.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:571c5f71622943387370f76de8cc0de3d5c6217ab0f38386cb127665e4e09275"}, + {file = "pyzstd-0.18.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de0b730f374b583894d58b79cff76569540baf1e84bc493be191d3128b58e559"}, + {file = "pyzstd-0.18.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b32184013f33dba2fabcdda89f2a83289f5b717a0c2477cda764e53fdafec7ee"}, + {file = "pyzstd-0.18.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:27c281abfc2f13f19df92793f66e12cd0a19038ccbc02684af2a14bce664fdc4"}, + {file = "pyzstd-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7313f3a9bd2cb11158e5eaab3d5d2cd6b4582702e383a08ebb8273d0d45c3e49"}, + {file = "pyzstd-0.18.0-pp39-pypy39_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec4ae014abf835bd9995ee1b318fdf4e955ffb8439838373bdc19c80d51a541"}, + {file = "pyzstd-0.18.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94c2f15f0e67acf89bec97ea276f7a5ad4e6d0267f62f12424bf044a0de280a0"}, + {file = "pyzstd-0.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:898e41170fde5aa73105a0262572c286bafc5f24c7b4cf131168d9b198e4c586"}, + {file = "pyzstd-0.18.0.tar.gz", hash = "sha256:81b6851ab1ca2e5f2c709e896a1362e3065a64f271f43db77fb7d5e4a78e9861"}, ] [package.dependencies] @@ -3696,17 +3764,6 @@ files = [ [package.dependencies] prompt_toolkit = ">=2.0,<4.0" -[[package]] -name = "ratelimit" -version = "2.2.1" -description = "API rate limit decorator" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "ratelimit-2.2.1.tar.gz", hash = "sha256:af8a9b64b821529aca09ebaf6d8d279100d766f19e90b5059ac6a718ca6dee42"}, -] - [[package]] name = "requests" version = "2.32.5" @@ -3769,59 +3826,68 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" -version = "0.2.12" +version = "0.2.14" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.9" groups = ["main"] markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\"" files = [ - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, - {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win32.whl", hash = "sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182"}, + {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win_amd64.whl", hash = "sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a"}, + {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54"}, + {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2"}, + {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78"}, + {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f"}, + {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83"}, + {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27"}, + {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win32.whl", hash = "sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352"}, + {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6"}, + {file = "ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e"}, ] [[package]] @@ -3876,67 +3942,73 @@ test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=8)", "pytest-co [[package]] name = "scipy" -version = "1.16.1" +version = "1.16.2" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77"}, - {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe"}, - {file = "scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b"}, - {file = "scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7"}, - {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958"}, - {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39"}, - {file = "scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"}, - {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"}, - {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"}, - {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"}, - {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"}, - {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"}, - {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"}, - {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"}, - {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"}, - {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"}, - {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"}, - {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"}, - {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"}, - {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"}, - {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"}, - {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"}, - {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"}, - {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"}, - {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"}, - {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"}, - {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"}, - {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"}, - {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"}, - {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"}, - {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"}, - {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"}, - {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"}, - {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"}, - {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"}, - {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"}, - {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"}, - {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"}, + {file = "scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92"}, + {file = "scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e"}, + {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173"}, + {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d"}, + {file = "scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2"}, + {file = "scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9"}, + {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3"}, + {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88"}, + {file = "scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa"}, + {file = "scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c"}, + {file = "scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d"}, + {file = "scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371"}, + {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0"}, + {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232"}, + {file = "scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1"}, + {file = "scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f"}, + {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef"}, + {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1"}, + {file = "scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e"}, + {file = "scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851"}, + {file = "scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70"}, + {file = "scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9"}, + {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5"}, + {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925"}, + {file = "scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9"}, + {file = "scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7"}, + {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb"}, + {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e"}, + {file = "scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c"}, + {file = "scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104"}, + {file = "scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1"}, + {file = "scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a"}, + {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f"}, + {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4"}, + {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21"}, + {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7"}, + {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8"}, + {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472"}, + {file = "scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351"}, + {file = "scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d"}, + {file = "scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77"}, + {file = "scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70"}, + {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88"}, + {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f"}, + {file = "scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb"}, + {file = "scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7"}, + {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548"}, + {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936"}, + {file = "scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff"}, + {file = "scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d"}, + {file = "scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8"}, + {file = "scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4"}, + {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831"}, + {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3"}, + {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac"}, + {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374"}, + {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6"}, + {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c"}, + {file = "scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9"}, + {file = "scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779"}, + {file = "scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b"}, ] [package.dependencies] @@ -3945,7 +4017,7 @@ numpy = ">=1.25.2,<2.6" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest (>=8.0.0)", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" @@ -4033,26 +4105,26 @@ files = [ [[package]] name = "tifffile" -version = "2025.8.28" +version = "2025.10.4" description = "Read and write TIFF files" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "tifffile-2025.8.28-py3-none-any.whl", hash = "sha256:b274a6d9eeba65177cf7320af25ef38ecf910b3369ac6bc494a94a3f6bd99c78"}, - {file = "tifffile-2025.8.28.tar.gz", hash = "sha256:82929343c70f6f776983f6a817f0b92e913a1bbb3dc3f436af44419b872bb467"}, + {file = "tifffile-2025.10.4-py3-none-any.whl", hash = "sha256:7687d691e49026053181470cec70fa9250e3a586b2041041297e38b10bbd34e1"}, + {file = "tifffile-2025.10.4.tar.gz", hash = "sha256:2e437c16ab211be5bcdc79f71b4907359115f1f83b5d919e7c297c29725d3e38"}, ] [package.dependencies] numpy = "*" [package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2024.12.30)", "kerchunk", "lxml", "matplotlib", "zarr (>=3)"] +all = ["defusedxml", "fsspec", "imagecodecs (>=2024.12.30)", "kerchunk", "lxml", "matplotlib", "zarr (>=3.1.3)"] codecs = ["imagecodecs (>=2024.12.30)"] plot = ["matplotlib"] -test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "kerchunk", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "requests", "roifile", "xarray", "zarr (>=3)"] +test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "kerchunk", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "requests", "roifile", "xarray", "zarr (>=3.1.3)"] xml = ["defusedxml", "lxml"] -zarr = ["fsspec", "kerchunk", "zarr (>=3)"] +zarr = ["fsspec", "kerchunk", "zarr (>=3.1.3)"] [[package]] name = "tomli" @@ -4132,26 +4204,26 @@ telegram = ["requests"] [[package]] name = "types-pywin32" -version = "311.0.0.20250822" +version = "311.0.0.20250915" description = "Typing stubs for pywin32" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_pywin32-311.0.0.20250822-py3-none-any.whl", hash = "sha256:cbcfe0d247b599efcb30e4a4db60af9473a6f5a6b4aaf6c622e962ac420da6ea"}, - {file = "types_pywin32-311.0.0.20250822.tar.gz", hash = "sha256:ea1082e23a1ebc3f069be1d7eec911d247bf54b3f8feeef61385134b36c42c7c"}, + {file = "types_pywin32-311.0.0.20250915-py3-none-any.whl", hash = "sha256:9605b20fd3b05189dbb9333098c19b5a519fc64bb807009a48ba75953b681c39"}, + {file = "types_pywin32-311.0.0.20250915.tar.gz", hash = "sha256:f0edbffd510539b83da225a89a8bd328ee729145c62582f5b5ba9267a66fe6bd"}, ] [[package]] name = "types-requests" -version = "2.32.4.20250809" +version = "2.32.4.20250913" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_requests-2.32.4.20250809-py3-none-any.whl", hash = "sha256:f73d1832fb519ece02c85b1f09d5f0dd3108938e7d47e7f94bbfa18a6782b163"}, - {file = "types_requests-2.32.4.20250809.tar.gz", hash = "sha256:d8060de1c8ee599311f56ff58010fb4902f462a1470802cf9f6ed27bc46c4df3"}, + {file = "types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}, + {file = "types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}, ] [package.dependencies] @@ -4171,14 +4243,14 @@ files = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -4283,14 +4355,14 @@ bracex = ">=2.1.1" [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = "*" +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, + {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, + {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, ] [[package]] @@ -4330,7 +4402,7 @@ version = "1.17.3" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, @@ -4417,116 +4489,142 @@ files = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, - {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, - {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, - {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, - {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, - {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, - {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, - {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, - {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, - {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, - {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, - {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, - {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, - {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, - {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, - {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, - {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, - {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, - {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, - {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, - {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, - {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, - {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, - {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, - {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, - {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, - {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, - {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, - {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, - {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, - {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, - {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, - {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, + {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, + {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, + {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, + {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, + {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, + {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, + {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, + {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, + {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, + {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, + {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, + {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, + {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, + {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, + {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, + {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, + {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, + {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, + {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, + {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, + {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, + {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, + {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, + {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, + {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, + {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, + {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, + {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, + {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, + {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, + {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, + {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, + {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, + {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, + {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, + {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, + {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, + {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, + {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, + {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, + {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, + {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, + {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, + {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, + {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, + {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, + {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, + {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, + {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, + {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, ] [package.dependencies] @@ -4534,27 +4632,7 @@ idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.1" -[[package]] -name = "zipp" -version = "3.23.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [metadata] lock-version = "2.1" -python-versions = ">=3.11,<3.15" -content-hash = "f1231aff33158110e08dc924a6c96d2115e784d3a2675c3f5ad3810be48dddc3" +python-versions = ">=3.12,<3.15" +content-hash = "0fd211805847621f49b8ab76955031aa6c0214f7efd9f3298dbb0f85ef03381c" diff --git a/pyproject.toml b/pyproject.toml index 27c68ee4..6b36f920 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,11 +35,11 @@ requests = "^2.32.5" asynckivy = "^0.9.0" Pillow = "^11.3.0" kivy = "^2.3.1" -typing-extensions = "^4.14.1" +typing-extensions = "^4.15.0" limits = "^5.5.0" backoff = "^2.2.1" pathvalidate = "^3.3.1" -fonttools = "^4.59.1" +fonttools = "^4.59.2" pyyaml = "^6.0.2" tqdm = "^4.67.1" click = "^8.2.1" @@ -53,21 +53,21 @@ rich = "^14.1.0" pywin32 = "^311.0.0" [tool.poetry.group.dev.dependencies] -pytest = "^8.4.1" -mypy = "^1.17.1" -commitizen = "^4.8.3" +pytest = "^8.4.2" +mypy = "^1.18.1" +commitizen = "^4.9.1" setuptools = "^80.9.0" -matplotlib = "^3.10.5" +matplotlib = "^3.10.6" psd-tools = "^1.10.9" pyclean = "^3.1.0" pyinstaller = "^6.15.0" pre-commit = "^4.3.0" mkdocs = "^1.6.1" -mkdocs-material = "^9.6.18" -mkdocs-include-markdown-plugin = "^7.1.6" +mkdocs-material = "^9.6.19" +mkdocs-include-markdown-plugin = "^7.1.7" mkdocs-pymdownx-material-extras = "^2.8.0" mkdocs-minify-plugin = "^0.8.0" -mkdocstrings-python = "^1.17.0" +mkdocstrings-python = "^1.18.2" mkdocs-gen-files = "^0.5.0" mkdocs-autolinks-plugin = "^0.7.1" mkdocs-same-dir = "^0.1.3" From f872c44f04835d828baf67fafdfe57dd7f755c8e Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 7 Oct 2025 14:39:54 +0300 Subject: [PATCH 054/190] refactor(rate_limit): Import rate_limit decorator from omnitils --- src/utils/hexapi.py | 8 ++------ src/utils/rate_limit.py | 43 ----------------------------------------- src/utils/scryfall.py | 4 ++-- 3 files changed, 4 insertions(+), 51 deletions(-) delete mode 100644 src/utils/rate_limit.py diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 2c72b651..4cff5111 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -1,7 +1,6 @@ """ * Handles Requests to the hexproof.io API """ -# Standard Library Imports from collections.abc import Callable from contextlib import suppress from functools import cache @@ -19,15 +18,12 @@ from omnitils.fetch import download_file from omnitils.files import dump_data_file from omnitils.files.archive import unpack_zip - -# Third Party Imports +from omnitils.rate_limit import rate_limit from pydantic import BaseModel from requests import RequestException -# Local Imports from src import CON, CONSOLE, PATH from src.utils.download import HEADERS -from src.utils.rate_limit import rate_limit """ * Types @@ -77,7 +73,7 @@ def hexproof_request_wrapper(fallback: T, logr: ExceptionLogger | None = None) - def decorator(func: Callable[P,T]): @return_on_exception(fallback) @log_on_exception(logr) - @rate_limit(strategy=_hexproof_rate_limit, limit=_rate_limit) + @rate_limit(limiter=_hexproof_rate_limit, limit=_rate_limit) @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) diff --git a/src/utils/rate_limit.py b/src/utils/rate_limit.py deleted file mode 100644 index bde49d59..00000000 --- a/src/utils/rate_limit.py +++ /dev/null @@ -1,43 +0,0 @@ -from collections.abc import Callable, Iterable -from time import sleep, time -from typing import ParamSpec, TypeVar - -from limits import RateLimitItem -from limits.strategies import RateLimiter - -T = TypeVar("T") -P = ParamSpec("P") - - -class RateLimitError(Exception): - pass - - -def rate_limit( - strategy: RateLimiter, - limit: RateLimitItem, - reschedule: bool = True, - identifiers: Iterable[str] = tuple(), -) -> Callable[[Callable[P, T]], Callable[P, T]]: - """Decorator for rate_limiting function calls. - - Args: - strategy: Limiting strategy. - limit: The limit, e.g, 10 calls per second. - reschedule: How long to sleep in seconds before reattempting. Pass 0 to raise `RateLimitError` instead when limit is exceeded. - identifier: Identifiers that can be used to separate this limit from oters. - """ - - def decorator(func: Callable[P, T]) -> Callable[P, T]: - def wrapper(*args: P.args, **kwargs: P.kwargs): - if reschedule: - while not strategy.hit(limit, *identifiers): - window = strategy.get_window_stats(limit, *identifiers) - sleep(window.reset_time - time()) - elif not strategy.hit(limit, *identifiers): - raise RateLimitError(f"Limit exceeded for '{identifiers}' '{limit}'") - return func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 9a6ed95f..7a90cabb 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -26,13 +26,13 @@ from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from omnitils.rate_limit import rate_limit from pydantic import BaseModel, HttpUrl, ValidationError from requests.exceptions import RequestException from src import CONSOLE, PATH from src.console import get_bullet_points from src.utils.download import HEADERS -from src.utils.rate_limit import rate_limit """ * Types @@ -449,7 +449,7 @@ def decorator(func: Callable[P, T]): @on_exception( expo, requests.exceptions.RequestException, max_tries=2, max_time=1 ) - @rate_limit(strategy=_scryfall_rate_limit, limit=_rate_limit) + @rate_limit(limiter=_scryfall_rate_limit, limit=_rate_limit) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) From d78235d8159395b2f2f69ec1ba6be3ae3a9a0a1c Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 25 Oct 2025 07:46:43 +0300 Subject: [PATCH 055/190] fix(ScryfallCard): Don't validate the non-official `front` field --- src/utils/scryfall.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 7a90cabb..a739d178 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -27,7 +27,7 @@ from limits.strategies import MovingWindowRateLimiter from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception from omnitils.rate_limit import rate_limit -from pydantic import BaseModel, HttpUrl, ValidationError +from pydantic import BaseModel, Field, HttpUrl, ValidationError from requests.exceptions import RequestException from src import CONSOLE, PATH @@ -211,9 +211,6 @@ class ScryfallCardFace(BaseModel): class ScryfallCard(BaseModel): - class Config: - fields = {"front": {"exclude": True}} - # Core Card Fields arena_id: int | None = None id: UUID @@ -306,7 +303,7 @@ class Config: preview: ScryfallPreview | None = None # Non-official extensions - front: bool = True + front: bool = Field(default=True, exclude=True) class ScryfallList(BaseModel, Generic[T]): From 563252279f570e46c8d66fc752458a2d8477434c Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 25 Oct 2025 17:48:35 +0300 Subject: [PATCH 056/190] feat: Allow fetching symbols and checking app updates from user defined repositories. Use Pydantic for validating configs --- poetry.lock | 1372 ++++++++++++++++++++------------------ pyproject.toml | 43 +- src/__init__.py | 45 +- src/_loader.py | 315 +++++---- src/_state.py | 72 +- src/data/env.default.yml | 8 + src/data/kv/console.kv | 3 +- src/gui/app.py | 18 +- src/layouts.py | 28 +- src/utils/github.py | 88 +++ src/utils/hexapi.py | 159 +++-- 11 files changed, 1219 insertions(+), 932 deletions(-) create mode 100644 src/utils/github.py diff --git a/poetry.lock b/poetry.lock index ed29926e..206d267d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aggdraw" @@ -62,14 +62,14 @@ files = [ [[package]] name = "argcomplete" -version = "3.6.2" +version = "3.6.3" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, - {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, + {file = "argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce"}, + {file = "argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c"}, ] [package.extras] @@ -512,91 +512,125 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.3" +version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, - {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, - {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, - {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, - {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, - {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, - {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, - {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, - {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, - {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] [[package]] @@ -655,14 +689,14 @@ tomlkit = ">=0.5.3,<1.0.0" [[package]] name = "comtypes" -version = "1.4.12" +version = "1.4.13" description = "Pure Python COM package" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "comtypes-1.4.12-py3-none-any.whl", hash = "sha256:e0fa9cc19c489fa7feea4c1710f4575c717e2673edef5b99bf99efd507908e44"}, - {file = "comtypes-1.4.12.zip", hash = "sha256:3ff06c442c2de8a2b25785407f244eb5b6f809d21cf068a855071ba80a76876f"}, + {file = "comtypes-1.4.13-py3-none-any.whl", hash = "sha256:21546210748ba52e839e52112124b16ffab7d7fb68096493165fbc249e9023ad"}, + {file = "comtypes-1.4.13.zip", hash = "sha256:fc573997ae32b374891cfa8d79ebb8289809ed3a4a3f789d5348371099c7788a"}, ] [[package]] @@ -840,14 +874,14 @@ files = [ [[package]] name = "dynaconf" -version = "3.2.11" +version = "3.2.12" description = "The dynamic configurator for your Python Project" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "dynaconf-3.2.11-py2.py3-none-any.whl", hash = "sha256:660de90879d4da236f79195692a7d197957224d7acf922bcc6899187dc7b4a27"}, - {file = "dynaconf-3.2.11.tar.gz", hash = "sha256:4cfc6a730c533bf1a1d0bf266ae202133a22236bb3227d23eff4b8542d4034a5"}, + {file = "dynaconf-3.2.12-py2.py3-none-any.whl", hash = "sha256:eb2a11865917dff8810c6098cd736b8f4d2f4e39ad914500e2dfbe064b82c499"}, + {file = "dynaconf-3.2.12.tar.gz", hash = "sha256:29cea583b007d890e6031fa89c0ac489b631c73dbee83bcd5e6f97602c26354e"}, ] [package.dependencies] @@ -865,14 +899,14 @@ yaml = ["ruamel.yaml"] [[package]] name = "filelock" -version = "3.19.1" +version = "3.20.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, - {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, + {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, + {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, ] [[package]] @@ -1050,17 +1084,17 @@ bs4 = "^0.0.2" limits = "^5.6.0" loguru = "^0.7.3" omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} -pydantic = ">=2.11.10" +pydantic = ">=2.12.3" PyYAML = ">=6.0.3" requests = "^2.32.5" -tomli = ">=2.2.1" +tomli = ">=2.3.0" tomlkit = ">=0.13.3" [package.source] type = "git" url = "https://github.com/pappnu/hexproof.git" reference = "dev" -resolved_reference = "34958ae70d675e09a30f8c82cda2877a0e453e8d" +resolved_reference = "fc281f1d767651322ff271d2ccb72251bd70e19c" [[package]] name = "htmlmin2" @@ -1090,14 +1124,14 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.10" +version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] @@ -1195,14 +1229,14 @@ test = ["pytest"] [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] @@ -1706,67 +1740,67 @@ files = [ [[package]] name = "matplotlib" -version = "3.10.6" +version = "3.10.7" description = "Python plotting package" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "matplotlib-3.10.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bc7316c306d97463a9866b89d5cc217824e799fa0de346c8f68f4f3d27c8693d"}, - {file = "matplotlib-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d00932b0d160ef03f59f9c0e16d1e3ac89646f7785165ce6ad40c842db16cc2e"}, - {file = "matplotlib-3.10.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fa4c43d6bfdbfec09c733bca8667de11bfa4970e8324c471f3a3632a0301c15"}, - {file = "matplotlib-3.10.6-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea117a9c1627acaa04dbf36265691921b999cbf515a015298e54e1a12c3af837"}, - {file = "matplotlib-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08fc803293b4e1694ee325896030de97f74c141ccff0be886bb5915269247676"}, - {file = "matplotlib-3.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:2adf92d9b7527fbfb8818e050260f0ebaa460f79d61546374ce73506c9421d09"}, - {file = "matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f"}, - {file = "matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76"}, - {file = "matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6"}, - {file = "matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f"}, - {file = "matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce"}, - {file = "matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e"}, - {file = "matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951"}, - {file = "matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347"}, - {file = "matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75"}, - {file = "matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95"}, - {file = "matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb"}, - {file = "matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07"}, - {file = "matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b"}, - {file = "matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa"}, - {file = "matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a"}, - {file = "matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf"}, - {file = "matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a"}, - {file = "matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110"}, - {file = "matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2"}, - {file = "matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18"}, - {file = "matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6"}, - {file = "matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f"}, - {file = "matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27"}, - {file = "matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833"}, - {file = "matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa"}, - {file = "matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706"}, - {file = "matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e"}, - {file = "matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5"}, - {file = "matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899"}, - {file = "matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c"}, - {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438"}, - {file = "matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453"}, - {file = "matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47"}, - {file = "matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98"}, - {file = "matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a"}, - {file = "matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b"}, - {file = "matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c"}, - {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3"}, - {file = "matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf"}, - {file = "matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a"}, - {file = "matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3"}, - {file = "matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7"}, - {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:13fcd07ccf17e354398358e0307a1f53f5325dca22982556ddb9c52837b5af41"}, - {file = "matplotlib-3.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:470fc846d59d1406e34fa4c32ba371039cd12c2fe86801159a965956f2575bd1"}, - {file = "matplotlib-3.10.6-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7173f8551b88f4ef810a94adae3128c2530e0d07529f7141be7f8d8c365f051"}, - {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488"}, - {file = "matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf"}, - {file = "matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb"}, - {file = "matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c"}, + {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, + {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297"}, + {file = "matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42"}, + {file = "matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7"}, + {file = "matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a"}, + {file = "matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a"}, + {file = "matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1"}, + {file = "matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e"}, + {file = "matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748"}, + {file = "matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0"}, + {file = "matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695"}, + {file = "matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee"}, + {file = "matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f"}, + {file = "matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1"}, + {file = "matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632"}, + {file = "matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815"}, + {file = "matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355"}, + {file = "matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67"}, + {file = "matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2"}, + {file = "matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100"}, + {file = "matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715"}, + {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1"}, + {file = "matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866"}, + {file = "matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1"}, + {file = "matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318"}, + {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca"}, + {file = "matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8"}, + {file = "matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657"}, + {file = "matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68"}, + {file = "matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91"}, + {file = "matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7"}, ] [package.dependencies] @@ -1777,7 +1811,7 @@ kiwisolver = ">=1.3.1" numpy = ">=1.23" packaging = ">=20.0" pillow = ">=8" -pyparsing = ">=2.3.1" +pyparsing = ">=3" python-dateutil = ">=2.7" [package.extras] @@ -1954,14 +1988,14 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.6.21" +version = "9.6.22" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.6.21-py3-none-any.whl", hash = "sha256:aa6a5ab6fb4f6d381588ac51da8782a4d3757cb3d1b174f81a2ec126e1f22c92"}, - {file = "mkdocs_material-9.6.21.tar.gz", hash = "sha256:b01aa6d2731322438056f360f0e623d3faae981f8f2d8c68b1b973f4f2657870"}, + {file = "mkdocs_material-9.6.22-py3-none-any.whl", hash = "sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84"}, + {file = "mkdocs_material-9.6.22.tar.gz", hash = "sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c"}, ] [package.dependencies] @@ -2365,86 +2399,86 @@ files = [ [[package]] name = "numpy" -version = "2.3.3" +version = "2.3.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30"}, - {file = "numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57"}, - {file = "numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa"}, - {file = "numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7"}, - {file = "numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf"}, - {file = "numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb"}, - {file = "numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86"}, - {file = "numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8"}, - {file = "numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf"}, - {file = "numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea"}, - {file = "numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd"}, - {file = "numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d"}, - {file = "numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf"}, - {file = "numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0"}, - {file = "numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8"}, - {file = "numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970"}, - {file = "numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5"}, - {file = "numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f"}, - {file = "numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc"}, - {file = "numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029"}, + {file = "numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb"}, + {file = "numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f"}, + {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36"}, + {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032"}, + {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7"}, + {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda"}, + {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0"}, + {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a"}, + {file = "numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1"}, + {file = "numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996"}, + {file = "numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c"}, + {file = "numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11"}, + {file = "numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9"}, + {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667"}, + {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef"}, + {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e"}, + {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a"}, + {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16"}, + {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786"}, + {file = "numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc"}, + {file = "numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32"}, + {file = "numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db"}, + {file = "numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966"}, + {file = "numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3"}, + {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197"}, + {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e"}, + {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7"}, + {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953"}, + {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37"}, + {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd"}, + {file = "numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646"}, + {file = "numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d"}, + {file = "numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc"}, + {file = "numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879"}, + {file = "numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562"}, + {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a"}, + {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6"}, + {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7"}, + {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0"}, + {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f"}, + {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64"}, + {file = "numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb"}, + {file = "numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c"}, + {file = "numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40"}, + {file = "numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e"}, + {file = "numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff"}, + {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f"}, + {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b"}, + {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7"}, + {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2"}, + {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52"}, + {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26"}, + {file = "numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc"}, + {file = "numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9"}, + {file = "numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868"}, + {file = "numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec"}, + {file = "numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3"}, + {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365"}, + {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252"}, + {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e"}, + {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0"}, + {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0"}, + {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f"}, + {file = "numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d"}, + {file = "numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6"}, + {file = "numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d"}, + {file = "numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f"}, + {file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, ] [[package]] @@ -2463,13 +2497,13 @@ click = "^8.3.0" limits = "^5.6.0" loguru = "^0.7.3" pathvalidate = "^3.3.1" -pillow = "^11.3.0" +pillow = "^12.0.0" py7zr = "^1.0.0" -pydantic = "^2.11.7" +pydantic = "^2.12.3" python-dateutil = "^2.9.0.post0" PyYAML = "^6.0.3" requests = "^2.32.5" -tomli = "^2.2.1" +tomli = "^2.3.0" tomlkit = "^0.13.3" tqdm = "^4.67.1" yarl = "^1.22.0" @@ -2478,7 +2512,7 @@ yarl = "^1.22.0" type = "git" url = "https://github.com/pappnu/omnitils.git" reference = "dev" -resolved_reference = "9b87da6cd38983169ccf739e22ebfd9115866640" +resolved_reference = "3c9acacc479067b092354c8ac345f24d24ec33d1" [[package]] name = "packaging" @@ -2572,145 +2606,129 @@ resolved_reference = "9f8496ef88c96918a8ba4434f5878968e034fcaa" [[package]] name = "pillow" -version = "11.3.0" -description = "Python Imaging Library (Fork)" +version = "12.0.0" +description = "Python Imaging Library (fork)" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}, + {file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}, + {file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}, + {file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}, + {file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}, + {file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}, + {file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}, + {file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}, + {file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}, + {file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}, + {file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}, + {file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}, + {file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}, + {file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}, + {file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}, + {file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}, + {file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}, + {file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}, + {file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}, + {file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}, + {file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}, + {file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}, + {file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, + {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, + {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] +docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] +type = ["mypy (>=1.18.2)"] [[package]] name = "pluggy" @@ -2764,134 +2782,134 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.4.0" +version = "0.4.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80"}, - {file = "propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5"}, - {file = "propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034"}, - {file = "propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335"}, - {file = "propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7"}, - {file = "propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e"}, - {file = "propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f"}, - {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454"}, - {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528"}, - {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d"}, - {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3"}, - {file = "propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993"}, - {file = "propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57"}, - {file = "propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71"}, - {file = "propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641"}, - {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549"}, - {file = "propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3"}, - {file = "propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234"}, - {file = "propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9"}, - {file = "propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1"}, - {file = "propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35"}, - {file = "propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8"}, - {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2"}, - {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13"}, - {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710"}, - {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1"}, - {file = "propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0"}, - {file = "propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577"}, - {file = "propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0"}, - {file = "propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64"}, - {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9"}, - {file = "propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3"}, - {file = "propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555"}, - {file = "propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289"}, - {file = "propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7"}, - {file = "propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392"}, - {file = "propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932"}, - {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2"}, - {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e"}, - {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b"}, - {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc"}, - {file = "propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194"}, - {file = "propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c"}, - {file = "propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907"}, - {file = "propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc"}, - {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d"}, - {file = "propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937"}, - {file = "propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6"}, - {file = "propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85"}, - {file = "propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c"}, - {file = "propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a"}, - {file = "propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218"}, - {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1"}, - {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16"}, - {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6"}, - {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7"}, - {file = "propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833"}, - {file = "propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc"}, - {file = "propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281"}, - {file = "propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92"}, - {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4"}, - {file = "propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc"}, - {file = "propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f"}, - {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1"}, - {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d"}, - {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4"}, - {file = "propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d"}, - {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be"}, - {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44"}, - {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0"}, - {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6"}, - {file = "propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da"}, - {file = "propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db"}, - {file = "propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d"}, - {file = "propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9"}, - {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79"}, - {file = "propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4"}, - {file = "propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e"}, - {file = "propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5"}, - {file = "propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e"}, - {file = "propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b"}, - {file = "propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb"}, - {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137"}, - {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738"}, - {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f"}, - {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90"}, - {file = "propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca"}, - {file = "propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93"}, - {file = "propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b"}, - {file = "propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79"}, - {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9"}, - {file = "propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f"}, - {file = "propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321"}, - {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac"}, - {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0"}, - {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a"}, - {file = "propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2"}, - {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3"}, - {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db"}, - {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4"}, - {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012"}, - {file = "propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec"}, - {file = "propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373"}, - {file = "propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28"}, - {file = "propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4"}, - {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0cd30341142c68377cf3c4e2d9f0581e6e528694b2d57c62c786be441053d2fc"}, - {file = "propcache-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c46d37955820dd883cf9156ceb7825b8903e910bdd869902e20a5ac4ecd2c8b"}, - {file = "propcache-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b12df77eb19266efd146627a65b8ad414f9d15672d253699a50c8205661a820"}, - {file = "propcache-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cdabd60e109506462e6a7b37008e57979e737dc6e7dfbe1437adcfe354d1a0a"}, - {file = "propcache-0.4.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65ff56a31f25925ef030b494fe63289bf07ef0febe6da181b8219146c590e185"}, - {file = "propcache-0.4.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96153e037ae065bb71cae889f23c933190d81ae183f3696a030b47352fd8655d"}, - {file = "propcache-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bf95be277fbb51513895c2cecc81ab12a421cdbd8837f159828a919a0167f96"}, - {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8d18d796ffecdc8253742fd53a94ceee2e77ad149eb9ed5960c2856b5f692f71"}, - {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4a52c25a51d5894ba60c567b0dbcf73de2f3cd642cf5343679e07ca3a768b085"}, - {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e0ce7f3d1faf7ad58652ed758cc9753049af5308b38f89948aa71793282419c5"}, - {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:545987971b2aded25ba4698135ea0ae128836e7deb6e18c29a581076aaef44aa"}, - {file = "propcache-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da5c4c72ae40fd3ce87213ab057db66df53e55600d0b9e72e2b7f5a470a2cc4"}, - {file = "propcache-0.4.0-cp39-cp39-win32.whl", hash = "sha256:2015218812ee8f13bbaebc9f52b1e424cc130b68d4857bef018e65e3834e1c4d"}, - {file = "propcache-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:39f0f6a3b56e82dc91d84c763b783c5c33720a33c70ee48a1c13ba800ac1fa69"}, - {file = "propcache-0.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:236c8da353ea7c22a8e963ab78cddb1126f700ae9538e2c4c6ef471e5545494b"}, - {file = "propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557"}, - {file = "propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, + {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, + {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, + {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, + {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, + {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, + {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, + {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, + {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, + {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, + {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, + {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, + {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, + {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, + {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, + {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, + {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, + {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, + {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, + {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, + {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, + {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, + {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, + {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, + {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, + {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, + {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, + {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, + {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, + {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, + {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, + {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, + {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, + {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, + {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, + {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, + {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, + {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, + {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, + {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, + {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, + {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, + {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, + {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, + {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, ] [[package]] @@ -2952,26 +2970,36 @@ scipy = "*" [[package]] name = "psutil" -version = "7.1.0" +version = "7.1.2" description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13"}, - {file = "psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3"}, - {file = "psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d"}, - {file = "psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca"}, - {file = "psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d"}, - {file = "psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07"}, - {file = "psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2"}, + {file = "psutil-7.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0cc5c6889b9871f231ed5455a9a02149e388fffcb30b607fb7a8896a6d95f22e"}, + {file = "psutil-7.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8e9e77a977208d84aa363a4a12e0f72189d58bbf4e46b49aae29a2c6e93ef206"}, + {file = "psutil-7.1.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d9623a5e4164d2220ecceb071f4b333b3c78866141e8887c072129185f41278"}, + {file = "psutil-7.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:364b1c10fe4ed59c89ec49e5f1a70da353b27986fa8233b4b999df4742a5ee2f"}, + {file = "psutil-7.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f101ef84de7e05d41310e3ccbdd65a6dd1d9eed85e8aaf0758405d022308e204"}, + {file = "psutil-7.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:20c00824048a95de67f00afedc7b08b282aa08638585b0206a9fb51f28f1a165"}, + {file = "psutil-7.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e09cfe92aa8e22b1ec5e2d394820cf86c5dff6367ac3242366485dfa874d43bc"}, + {file = "psutil-7.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa6342cf859c48b19df3e4aa170e4cfb64aadc50b11e06bb569c6c777b089c9e"}, + {file = "psutil-7.1.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:625977443498ee7d6c1e63e93bacca893fd759a66c5f635d05e05811d23fb5ee"}, + {file = "psutil-7.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a24bcd7b7f2918d934af0fb91859f621b873d6aa81267575e3655cd387572a7"}, + {file = "psutil-7.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:329f05610da6380982e6078b9d0881d9ab1e9a7eb7c02d833bfb7340aa634e31"}, + {file = "psutil-7.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7b04c29e3c0c888e83ed4762b70f31e65c42673ea956cefa8ced0e31e185f582"}, + {file = "psutil-7.1.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c9ba5c19f2d46203ee8c152c7b01df6eec87d883cfd8ee1af2ef2727f6b0f814"}, + {file = "psutil-7.1.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:2a486030d2fe81bec023f703d3d155f4823a10a47c36784c84f1cc7f8d39bedb"}, + {file = "psutil-7.1.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3efd8fc791492e7808a51cb2b94889db7578bfaea22df931424f874468e389e3"}, + {file = "psutil-7.1.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2aeb9b64f481b8eabfc633bd39e0016d4d8bbcd590d984af764d80bf0851b8a"}, + {file = "psutil-7.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:8e17852114c4e7996fe9da4745c2bdef001ebbf2f260dec406290e66628bdb91"}, + {file = "psutil-7.1.2-cp37-abi3-win_arm64.whl", hash = "sha256:3e988455e61c240cc879cb62a008c2699231bf3e3d061d7fce4234463fd2abb4"}, + {file = "psutil-7.1.2.tar.gz", hash = "sha256:aa225cdde1335ff9684708ee8c72650f6598d5ed2114b9a7c5802030b1785018"}, ] markers = {main = "sys_platform != \"cygwin\""} [package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] [[package]] @@ -3067,14 +3095,14 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pyclean" -version = "3.1.0" +version = "3.2.0" description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyclean-3.1.0-py3-none-any.whl", hash = "sha256:46652f7416816924cab565cd045d0c341237aced0368a3727d66b5192c16728c"}, - {file = "pyclean-3.1.0.tar.gz", hash = "sha256:d23fa900558696e08c79401c79f242fe6f7f38fd58ccd035f875a01c5bc7a6fc"}, + {file = "pyclean-3.2.0-py3-none-any.whl", hash = "sha256:6622535af7aa6132b80373f779e2dd9b6e8cfe7f67457cb6c566b820bd479a6a"}, + {file = "pyclean-3.2.0.tar.gz", hash = "sha256:256cf317cc58ca64fd94694dd6dfe211366266e3095e0449baa87421d0e2ab39"}, ] [[package]] @@ -3143,21 +3171,21 @@ files = [ [[package]] name = "pydantic" -version = "2.11.10" +version = "2.12.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a"}, - {file = "pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423"}, + {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, + {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" +pydantic-core = "2.41.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -3165,115 +3193,133 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, + {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, + {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, + {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, + {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, + {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, + {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, + {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, + {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, + {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, + {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, + {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, + {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, + {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, + {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, + {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, + {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, + {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, + {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, + {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, + {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, + {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, + {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, + {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, + {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, + {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, + {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, + {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, + {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, + {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, + {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, + {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pygments" @@ -3788,14 +3834,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "14.1.0" +version = "14.2.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, - {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, + {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, + {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, ] [package.dependencies] @@ -3807,14 +3853,14 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruamel-yaml" -version = "0.18.15" +version = "0.18.16" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701"}, - {file = "ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700"}, + {file = "ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba"}, + {file = "ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a"}, ] [package.dependencies] @@ -4105,14 +4151,14 @@ files = [ [[package]] name = "tifffile" -version = "2025.10.4" +version = "2025.10.16" description = "Read and write TIFF files" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "tifffile-2025.10.4-py3-none-any.whl", hash = "sha256:7687d691e49026053181470cec70fa9250e3a586b2041041297e38b10bbd34e1"}, - {file = "tifffile-2025.10.4.tar.gz", hash = "sha256:2e437c16ab211be5bcdc79f71b4907359115f1f83b5d919e7c297c29725d3e38"}, + {file = "tifffile-2025.10.16-py3-none-any.whl", hash = "sha256:41463d979c1c262b0a5cdef2a7f95f0388a072ad82d899458b154a48609d759c"}, + {file = "tifffile-2025.10.16.tar.gz", hash = "sha256:425179ec7837ac0e07bc95d2ea5bea9b179ce854967c12ba07fc3f093e58efc1"}, ] [package.dependencies] @@ -4128,44 +4174,54 @@ zarr = ["fsspec", "kerchunk", "zarr (>=3.1.3)"] [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] @@ -4204,14 +4260,14 @@ telegram = ["requests"] [[package]] name = "types-pywin32" -version = "311.0.0.20250915" +version = "311.0.0.20251008" description = "Typing stubs for pywin32" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_pywin32-311.0.0.20250915-py3-none-any.whl", hash = "sha256:9605b20fd3b05189dbb9333098c19b5a519fc64bb807009a48ba75953b681c39"}, - {file = "types_pywin32-311.0.0.20250915.tar.gz", hash = "sha256:f0edbffd510539b83da225a89a8bd328ee729145c62582f5b5ba9267a66fe6bd"}, + {file = "types_pywin32-311.0.0.20251008-py3-none-any.whl", hash = "sha256:775e1046e0bad6d29ca47501301cce67002f6661b9cebbeca93f9c388c53fab4"}, + {file = "types_pywin32-311.0.0.20251008.tar.gz", hash = "sha256:d6d4faf8e0d7fdc0e0a1ff297b80be07d6d18510f102d793bf54e9e3e86f6d06"}, ] [[package]] @@ -4276,14 +4332,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.34.0" +version = "20.35.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, - {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, + {file = "virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}, + {file = "virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}, ] [package.dependencies] @@ -4635,4 +4691,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.15" -content-hash = "0fd211805847621f49b8ab76955031aa6c0214f7efd9f3298dbb0f85ef03381c" +content-hash = "254606293f604a14159ae8c50a212a749786f1da4a607bc1a2e2fdb8a6283eb4" diff --git a/pyproject.toml b/pyproject.toml index 6b36f920..7ad43302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ keywords = [ "magic the gathering", "playtest", ] -requires-python = ">=3.11,<3.15" +requires-python = ">=3.12,<3.15" [tool.poetry.urls] Changelog = "https://github.com/Investigamer/Proxyshop/blob/main/CHANGELOG.md" @@ -33,38 +33,38 @@ include = 'src/../' photoshop-python-api = { git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation" } requests = "^2.32.5" asynckivy = "^0.9.0" -Pillow = "^11.3.0" +Pillow = "^12.0.0" kivy = "^2.3.1" typing-extensions = "^4.15.0" -limits = "^5.5.0" +limits = "^5.6.0" backoff = "^2.2.1" pathvalidate = "^3.3.1" -fonttools = "^4.59.2" -pyyaml = "^6.0.2" +fonttools = "^4.60.1" +pyyaml = "^6.0.3" tqdm = "^4.67.1" -click = "^8.2.1" -tomli = "^2.2.1" -yarl = "^1.20.1" -pydantic = "^2.11.7" +click = "^8.3.0" +tomli = "^2.3.0" +yarl = "^1.22.0" +pydantic = "^2.12.3" omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } -dynaconf = { extras = ["yaml"], version = "^3.2.11" } +dynaconf = { extras = ["yaml"], version = "^3.2.12" } hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } -rich = "^14.1.0" +rich = "^14.2.0" pywin32 = "^311.0.0" [tool.poetry.group.dev.dependencies] pytest = "^8.4.2" -mypy = "^1.18.1" +mypy = "^1.18.2" commitizen = "^4.9.1" setuptools = "^80.9.0" -matplotlib = "^3.10.6" -psd-tools = "^1.10.9" -pyclean = "^3.1.0" -pyinstaller = "^6.15.0" +matplotlib = "^3.10.7" +psd-tools = "^1.10.13" +pyclean = "^3.2.0" +pyinstaller = "^6.16.0" pre-commit = "^4.3.0" mkdocs = "^1.6.1" -mkdocs-material = "^9.6.19" -mkdocs-include-markdown-plugin = "^7.1.7" +mkdocs-material = "^9.6.22" +mkdocs-include-markdown-plugin = "^7.2.0" mkdocs-pymdownx-material-extras = "^2.8.0" mkdocs-minify-plugin = "^0.8.0" mkdocstrings-python = "^1.18.2" @@ -72,10 +72,10 @@ mkdocs-gen-files = "^0.5.0" mkdocs-autolinks-plugin = "^0.7.1" mkdocs-same-dir = "^0.1.3" mkdocs-git-revision-date-plugin = "^0.3.2" -mkdocstrings = { extras = ["python"], version = "^0.30.0" } +mkdocstrings = { extras = ["python"], version = "^0.30.1" } memory-profiler = "^0.61.0" -types-pywin32 = "^311.0.0.20250809" -types-requests = "^2.32.4.20250809" +types-pywin32 = "^311.0.0.20251008" +types-requests = "^2.32.4.20250913" [build-system] requires = ["poetry-core"] @@ -93,6 +93,7 @@ proxyshop = 'main:launch_cli' [tool.ruff.lint] extend-select = [ + "I", # isort "UP", # pyupgrade ] ignore = ["F403", "UP015"] diff --git a/src/__init__.py b/src/__init__.py index e6312b46..e2ad4fb8 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,48 +1,55 @@ """ * Load Global Application State """ -# Standard Library Imports -from pathlib import Path + import sys +from pathlib import Path -# Third Party Imports from dynaconf import Validator from omnitils.files import get_project_version +from src.utils.adobe import PhotoshopHandler from src.utils.threading import ThreadInitializedInstance -# Local Imports from ._config import AppConfig -from ._loader import get_all_plugins, get_all_templates, get_template_map, get_template_map_defaults -from ._state import AppConstants, AppEnvironment, PATH -from src.utils.adobe import PhotoshopHandler +from ._loader import ( + get_all_plugins, + get_all_templates, + get_template_map, + get_template_map_defaults, +) +from ._state import PATH, AppConstants, AppEnvironment """ * Globally Loaded Objects """ + def _get_proj_version(path: Path) -> str: try: return get_project_version(path) except Exception: return "0.0.0" + # Global environment object (dynaconf) ENV = AppEnvironment( - envvar_prefix='PROXYSHOP', + envvar_prefix="PROXYSHOP", settings_files=[PATH.SRC_DATA_ENV_DEFAULT, PATH.SRC_DATA_ENV], validators=[ - Validator('API_GOOGLE', cast=str, default=''), - Validator('API_AMAZON', cast=str, default=''), - Validator('PS_ERROR_DIALOG', cast=bool, default=False), - Validator('PS_VERSION', cast=AppEnvironment.string_or_none, default=None), - Validator('HEADLESS', cast=bool, default=False), - Validator('DEV_MODE', cast=bool, default=bool(not hasattr(sys, '_MEIPASS'))), - Validator('TEST_MODE', cast=bool, default=False), - Validator('VERSION', cast=str, default=_get_proj_version(PATH.PROJECT_FILE)), - Validator('FORCE_RELOAD', cast=bool, default=False) + Validator("API_GOOGLE", cast=str, default=""), + Validator("API_AMAZON", cast=str, default=""), + Validator("PS_ERROR_DIALOG", cast=bool, default=False), + Validator("PS_VERSION", cast=AppEnvironment.string_or_none, default=None), + Validator("HEADLESS", cast=bool, default=False), + Validator("DEV_MODE", cast=bool, default=bool(not hasattr(sys, "_MEIPASS"))), + Validator("TEST_MODE", cast=bool, default=False), + Validator("APP_UPDATES_REPO", cast=str, default=""), + Validator("SYMBOL_UPDATES_REPO", cast=str, default=""), + Validator("VERSION", cast=str, default=_get_proj_version(PATH.PROJECT_FILE)), + Validator("FORCE_RELOAD", cast=bool, default=False), ], - apply_default_on_none=True + apply_default_on_none=True, ) # Global constants object @@ -68,4 +75,4 @@ def _get_proj_version(path: Path) -> str: TEMPLATE_DEFAULTS = get_template_map_defaults(TEMPLATE_MAP) # Export objects -__all__ = ['APP', 'CFG', 'CON', 'CONSOLE', 'ENV', 'PATH'] +__all__ = ["APP", "CFG", "CON", "CONSOLE", "ENV", "PATH"] diff --git a/src/_loader.py b/src/_loader.py index 42228211..0ed1e99b 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -3,36 +3,35 @@ * Only import enums and utils """ -# Standard Library Imports -import json +import os import shutil -from concurrent.futures import ThreadPoolExecutor, Future +import tomllib +from collections.abc import Callable +from concurrent.futures import Future, ThreadPoolExecutor from configparser import ConfigParser from contextlib import suppress from functools import cached_property -import os from pathlib import Path from traceback import format_exc, print_tb from types import ModuleType -from typing import Optional, TypedDict, NotRequired, Any, overload -from collections.abc import Callable +from typing import Any, Literal, NotRequired, Optional, Self, TypedDict -# Third Party Imports -from py7zr import SevenZipFile +import yaml import yarl -from omnitils.api.gdrive import gdrive_get_metadata, gdrive_download_file -from omnitils.files import load_data_file, ensure_file, mkdir_full_perms -from omnitils.modules import get_local_module, import_package, import_module_from_path +from omnitils.api.gdrive import gdrive_download_file, gdrive_get_metadata +from omnitils.files import ensure_file, load_data_file, mkdir_full_perms +from omnitils.modules import get_local_module, import_module_from_path, import_package from omnitils.strings import normalize_ver +from py7zr import SevenZipFile +from pydantic import BaseModel, ConfigDict, Field, RootModel, model_validator -# Local Imports -from src._state import AppConstants, AppEnvironment, PATH +from src._state import PATH, AppConstants, AppEnvironment from src.enums.mtg import ( - layout_map_types_display, layout_map_category, - layout_map_types, - layout_map_display_condition_dual, layout_map_display_condition, + layout_map_display_condition_dual, + layout_map_types, + layout_map_types_display, ) from src.utils.download import download_cloudfront @@ -138,6 +137,137 @@ class IniConfig(TypedDict): value: str | int | float +class BaseConfig(BaseModel): + prefix: str + + +class BaseSection(BaseModel): + title: str + + +class SectionTitle(BaseSection): + type: Literal["title"] = "title" + + +class BaseSetting(BaseSection): + desc: str = "" + key: str = "" + section: str = "" + + +class BoolSetting(BaseSetting): + type: Literal["bool"] + # Kivy doesn't support default fields in it's config, + # so they should not be serialized. + default: bool = Field(default=False, exclude=True) + + +class StringSetting(BaseSetting): + type: Literal["string"] + default: str = Field(default="", exclude=True) + + +class NumericSetting(BaseSetting): + type: Literal["numeric"] + default: int | float = Field(default=0, exclude=True) + + +class OptionsSetting(BaseSetting): + type: Literal["options"] + options: list[str] = [] + default: str = Field(exclude=True) + + +SettingsSection = BoolSetting | StringSetting | NumericSetting | OptionsSetting + +_SomeSetting = RootModel[SettingsSection] + + +class ConfigSection(BaseSection): + settings: dict[str, SettingsSection] = Field(default={}, exclude=True) + + @model_validator(mode="before") + @classmethod + def extra_validator(cls, data: Any) -> Any: + if isinstance(data, dict): + for key, value in data.items(): # pyright: ignore[reportUnknownVariableType] + if not isinstance(key, str) or key in cls.model_fields: + continue + data[key] = _SomeSetting.model_validate(value).root + return data # pyright: ignore[reportUnknownVariableType] + + @model_validator(mode="after") + def post_validate(self) -> Self: + if self.model_extra: + self.settings = self.model_extra + return self + + model_config = ConfigDict(extra="allow") + + +class FormattedKivyConfig(RootModel[list[SectionTitle | SettingsSection]]): + root: list[SectionTitle | SettingsSection] = [] + + +class KivyConfig(BaseModel): + config: BaseConfig | None = Field(default=None, alias="__CONFIG__") + sections: FormattedKivyConfig = Field(default=FormattedKivyConfig(), exclude=True) + + @model_validator(mode="before") + @classmethod + def extra_validator(cls, data: Any) -> Any: + if isinstance(data, dict): + exclude_keys = [*cls.model_fields.keys(), "__CONFIG__"] + for key, value in data.items(): # pyright: ignore[reportUnknownVariableType] + if not isinstance(key, str) or key in exclude_keys: + continue + data[key] = ConfigSection.model_validate(value) + return data # pyright: ignore[reportUnknownVariableType] + + @model_validator(mode="after") + def post_validate(self) -> Self: + if self.model_extra: + sections: dict[str, ConfigSection] = self.model_extra + self.sections = FormattedKivyConfig() + prefix = self.config.prefix if self.config else None + for section, data in sections.items(): + self.sections.root.append(SectionTitle(title=data.title)) + for key, setting in data.settings.items(): + setting.key = key + setting.section = f"{prefix}.{section}" if prefix else section + self.sections.root.append(setting) + return self + + model_config = ConfigDict(extra="allow") + + +SymbolRarity = Literal["80", "B", "C", "H", "M", "R", "S", "T", "U", "WM"] + + +class SymbolsMeta(BaseModel): + date: str + version: str + uri: str + + +class SymbolsSet(BaseModel): + aliases: dict[str, str] + routes: dict[str, str] + rarities: dict[SymbolRarity, str] + symbols: dict[str, list[SymbolRarity]] + + +class SymbolsWatermark(BaseModel): + routes: dict[str, str] | None = None + symbols: list[str] + + +class SymbolsManifest(BaseModel): + meta: SymbolsMeta + set: SymbolsSet + watermark: SymbolsWatermark + + """ * Config File Utils """ @@ -148,6 +278,32 @@ def optionxform(self, optionstr: str) -> str: return optionstr +def parse_kivy_config(data_path: Path) -> FormattedKivyConfig: + """ + Tries to parse the Kivy config from a file at data_path. + + Raises: + OSError: If reading of the file at data_path fails. + ValidationError: If the data in file at data_path is invalid. + """ + if data_path.suffix == ".json": + return KivyConfig.model_validate_json(data_path.read_bytes()).sections + else: + if data_path.suffix == ".toml": + with open(data_path, "rb") as f: + data = tomllib.load(f) + return KivyConfig.model_validate(data).sections + if data_path.suffix in (".yaml", ".yml"): + with open(data_path, "rb") as f: + data = yaml.safe_load(f) + return KivyConfig.model_validate(data).sections + raise NotImplementedError( + f"Kivy config can be parsed only from .json, .toml, .yaml and .yml files. Got file: { + data_path + }" + ) + + def verify_config_fields(ini_file: Path, data_file: Path) -> None: """Validate that all settings fields present in a given json data are present in config file. If any are missing, add them and return. @@ -161,24 +317,23 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: changed = False # Data file doesn't exist or is unsupported data type - if not data_file.is_file() or data_file.suffix not in [".toml", ".json"]: + if not data_file.is_file() or data_file.suffix not in (".toml", ".json"): return # Load data from JSON or TOML file - raw = load_data_file(data_file) - raw = parse_kivy_config_toml(raw) if data_file.suffix == ".toml" else raw + settings_config = parse_kivy_config(data_file) # Ensure INI file exists and load ConfigParser ensure_file(ini_file) config = get_config_object(ini_file) # Build a dictionary of the necessary values - for row in raw: + for section in settings_config.root: # Add row if it's not a title - if row.get("type", "title") == "title": + if isinstance(section, SectionTitle): continue - data.setdefault(row.get("section", "BROKEN"), []).append( - {"key": row.get("key", ""), "value": row.get("default", 0)} + data.setdefault(section.section, []).append( + {"key": section.key, "value": section.default} ) # Add the data to ini where missing @@ -199,98 +354,6 @@ def verify_config_fields(ini_file: Path, data_file: Path) -> None: config.write(f) # noqa -@overload -def parse_kivy_config_json( - raw: list[KivyConfigTitle | KivyConfigSection], -) -> list[KivyConfigTitle | KivyConfigSection]: ... - - -@overload -def parse_kivy_config_json( - raw: list[dict[str, str | list[str]]], -) -> list[dict[str, str | list[str]]]: ... - - -def parse_kivy_config_json( - raw: list[dict[str, str | list[str]]] | list[KivyConfigTitle | KivyConfigSection], -) -> list[dict[str, str | list[str]]] | list[KivyConfigTitle | KivyConfigSection]: - """Parse config JSON data for use with Kivy settings panel. - - Args: - raw: Raw loaded JSON data. - - Returns: - Properly parsed data safe for use with Kivy. - """ - # Remove unsupported keys - for row in raw: - if "default" in row: - row.pop("default") - return raw - - -def parse_kivy_config_toml( - raw: dict[str, Any], -) -> list[KivyConfigTitle | KivyConfigSection]: - """Parse config TOML data for use with Kivy settings panel. - - Args: - raw: Raw loaded TOML data. - Something along the lines of (not yet possible to express in Python): - - ``` - class Config(TypedDict): - prefix: str - - class Section(TypedDict): - title: str - [str]: dict[str,str|list[str]] - - class RawConf(TypedDict): - __CONFIG__: Config - [str]: Section - ``` - - Returns: - Properly parsed data safe for use with Kivy. - """ - - # Process __CONFIG__ header if present - cfg_header = raw.pop("__CONFIG__", {}) - prefix = cfg_header.get("prefix", "") - - # Process data - data: list[KivyConfigTitle | KivyConfigSection] = [] - for section, settings in raw.items(): - # Add section title if it exists - if title := settings.pop("title", None): - data.append({"type": "title", "title": title}) - - # Add each setting within this section - for key, field in settings.items(): - # Establish data type and default value - data_type = field.get("type", "bool") - display_default = default = field.get("default", 0) - if data_type == "bool": - display_default = "True" if default else "False" - elif data_type in ["string", "options", "path"]: - display_default = f"'{default}'" - setting: KivyConfigSection = { - "type": data_type, - "title": f"[b]{field.get('title', 'Broken Setting')}[/b]", - "desc": f"{field.get('desc', '')}\n[b](Default: {display_default})[/b]", - "section": f"{prefix}.{section}" if prefix else section, - "key": key, - "default": default, - } - if options := field.get("options"): - setting["options"] = options - data.append(setting) - - # Return parsed data - return data - - def get_kivy_config_from_schema(config: Path) -> str: """Return valid JSON data for use with Kivy settings panel. @@ -300,13 +363,7 @@ def get_kivy_config_from_schema(config: Path) -> str: Returns: Json string dump of validated data. """ - # Need to load data as JSON - raw = load_data_file(config) - - # Use correct parser - if config.suffix == ".toml": - raw = parse_kivy_config_toml(raw) - return json.dumps(parse_kivy_config_json(raw)) + return parse_kivy_config(config).model_dump_json() def copy_config_or_verify(path_from: Path, path_to: Path, data_file: Path) -> None: @@ -1457,3 +1514,17 @@ def check_for_updates(templates: list[AppTemplate]) -> list[AppTemplate]: # Return templates needing updates return sorted(updates, key=lambda x: x.name) + + +# region Symbols + + +def get_symbols_manifest() -> SymbolsManifest | None: + if PATH.SRC_IMG_SYMBOLS_MANIFEST.exists(): + return SymbolsManifest.model_validate_json( + PATH.SRC_IMG_SYMBOLS_MANIFEST.read_bytes() + ) + return None + + +# endregion Symbols diff --git a/src/_state.py b/src/_state.py index 36fef6f8..42064b8a 100644 --- a/src/_state.py +++ b/src/_state.py @@ -2,33 +2,41 @@ * Manage Global State (non-GUI) * Only local imports should be `enums`, `utils`, or `cards`. """ -# Standard Library Imports import os +import sys from contextlib import suppress from functools import cached_property from os import environ from pathlib import Path -import sys from threading import Lock from typing import TYPE_CHECKING, Any -# Third Party Imports +from dynaconf import Dynaconf from hexproof.hexapi.schema.meta import Meta from omnitils.exceptions import return_on_exception -from omnitils.files import load_data_file, dump_data_file +from omnitils.files import dump_data_file, load_data_file from omnitils.metaclass import Singleton from omnitils.properties import tracked_prop -from dynaconf import Dynaconf +from pydantic import BaseModel, RootModel -# Local Imports from src.enums.layers import LAYERS -from src.enums.mtg import ( - mana_symbol_map, - CardFonts) +from src.enums.mtg import CardFonts, mana_symbol_map from src.schema.colors import ColorObject, SymbolColorMap from src.utils.mtg import get_symbol_colors +class HexproofSet(BaseModel): + """Cached 'Set' object data from Hexproof.io.""" + code_symbol: str = "default" + code_parent: str | None = None + count_cards: int + count_tokens: int + count_printed: int | None = None + +HexproofSets = RootModel[dict[str, HexproofSet]] + +HexproofMetas = RootModel[dict[str, Meta]] + """ * App Paths """ @@ -109,6 +117,7 @@ class PATH(DefinedPaths): # Image Level Files SRC_IMG_SYMBOLS_PACKAGE = (SRC_IMG_SYMBOLS / 'package').with_suffix('.zip') + SRC_IMG_SYMBOLS_MANIFEST = SRC_IMG_SYMBOLS / "manifest.json" SRC_IMG_OVERLAY = (SRC_IMG / 'overlay').with_suffix('.jpg') SRC_IMG_NOTFOUND = (SRC_IMG / 'notfound').with_suffix('.jpg') @@ -155,6 +164,10 @@ def DEV_MODE(self) -> bool: ... @cached_property def TEST_MODE(self) -> bool: ... @cached_property + def APP_UPDATES_REPO(self) -> str: ... + @cached_property + def SYMBOL_UPDATES_REPO(self) -> str: ... + @cached_property def FORCE_RELOAD(self) -> bool: ... @cached_property def VERSION(self) -> str: ... @@ -225,6 +238,18 @@ def DEV_MODE(self) -> bool: def TEST_MODE(self) -> bool: """bool: Whether the app is running in testing mode.""" return super().TEST_MODE + + """ + * Update checks + """ + + @cached_property + def APP_UPDATES_REPO(self) -> str: + return super().APP_UPDATES_REPO + + @cached_property + def SYMBOL_UPDATES_REPO(self) -> str: + return super().SYMBOL_UPDATES_REPO """ * Experimental @@ -421,13 +446,13 @@ def update_version_tracker(self): """ @tracked_prop - def set_data(self) -> dict[str, dict[str,dict[str,Any]]]: - """dict[str, dict]: Returns set data pulled from Hexproof.io mapped to set codes.""" + def set_data(self) -> dict[str, HexproofSet]: + """Returns set data pulled from Hexproof.io mapped to set codes.""" return self.get_set_data() @tracked_prop def metadata(self) -> dict[str, Meta]: - """dict[str, dict]: Returns data pulled from Hexproof.io mapped to resources.""" + """Returns data pulled from Hexproof.io mapped to resources.""" return self.get_meta_data() """ @@ -467,21 +492,22 @@ def get_user_data(self): """ @return_on_exception({}) - def get_set_data(self) -> dict[str, dict[str,dict[str,Any]]]: - """dict: Loaded data from the 'set' data file.""" + def get_set_data(self) -> dict[str, HexproofSet]: + """Loaded data from the 'set' data file.""" if not PATH.SRC_DATA_HEXPROOF_SET.is_file(): - dump_data_file({}, PATH.SRC_DATA_HEXPROOF_SET) + return {} - # Import watermark library - return load_data_file(PATH.SRC_DATA_HEXPROOF_SET) + # Read set data + return HexproofSets.model_validate_json( + PATH.SRC_DATA_HEXPROOF_SET.read_bytes() + ).root @return_on_exception({}) def get_meta_data(self) -> dict[str, Meta]: - """dict: Loaded data from the 'meta' data file.""" + """Loaded data from the 'meta' data file.""" if not PATH.SRC_DATA_HEXPROOF_META.is_file(): - dump_data_file({}, PATH.SRC_DATA_HEXPROOF_META) + return {} - # Import watermark library - return { - k: Meta(**v) for k, v in - load_data_file(PATH.SRC_DATA_HEXPROOF_META)} + return HexproofMetas.model_validate_json( + PATH.SRC_DATA_HEXPROOF_META.read_bytes() + ).root diff --git a/src/data/env.default.yml b/src/data/env.default.yml index 98e8a071..b79cb415 100644 --- a/src/data/env.default.yml +++ b/src/data/env.default.yml @@ -38,6 +38,14 @@ DEV_MODE: False # Force the app into testing mode TEST_MODE: False +### +# * Update Checks +### + +# Specify using format "/" +APP_UPDATES_REPO: Investigamer/Proxyshop +SYMBOL_UPDATES_REPO: "" + ### # * Experimental ### diff --git a/src/data/kv/console.kv b/src/data/kv/console.kv index 169e923e..20382cae 100644 --- a/src/data/kv/console.kv +++ b/src/data/kv/console.kv @@ -4,6 +4,7 @@ #:import ScrollView kivy.uix.scrollview.ScrollView #:import webbrowser webbrowser #:import ak asynckivy +#:import ENV src.ENV : id: console @@ -65,7 +66,7 @@ text: "🌍" options: [] font_name: get_font("seguiemj.ttf") - on_release: webbrowser.open('https://github.com/MrTeferi/Proxyshop') + on_release: webbrowser.open(f'https://github.com/{ENV.APP_UPDATES_REPO}') HoverButton: text: "❔" options: [] diff --git a/src/gui/app.py b/src/gui/app.py index d1d28b0d..69013c25 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -23,7 +23,7 @@ from PIL import Image as PImage from photoshop.api._document import Document from photoshop.api import SaveOptions, PurgeTarget -from packaging.version import parse +from packaging.version import parse, InvalidVersion # Kivy Imports from kivy.lang import Builder @@ -65,6 +65,7 @@ NormalLayout) from src.templates import BaseTemplate from src.utils.adobe import get_photoshop_error_message, PhotoshopHandler, PS_EXCEPTIONS +from src.utils.github import get_github_releases from src.utils.hexapi import update_hexproof_cache, get_api_key from src.utils.fonts import check_app_fonts from src.utils.threading import ThreadInitializedInstance @@ -1132,10 +1133,13 @@ def check_app_version(self) -> bool: Returns: Return True if up to date, otherwise False. """ - with suppress(requests.RequestException, json.JSONDecodeError): - response = requests.get( - "https://api.github.com/repos/MrTeferi/Proxyshop/releases/latest", - timeout=(3, 3)) - latest = response.json().get("tag_name", self.env.VERSION) - return bool(parse(self.env.VERSION.lstrip('v')) >= parse(latest.lstrip('v'))) + if self.env.APP_UPDATES_REPO: + with suppress(requests.RequestException, json.JSONDecodeError): + releases = get_github_releases(self.env.APP_UPDATES_REPO, per_page=1) + if (len(releases) > 0): + latest = releases[0].tag_name + try: + return bool(parse(self.env.VERSION.lstrip('v')) >= parse(latest.lstrip('v'))) + except InvalidVersion: + return self.env.VERSION >= latest return True diff --git a/src/layouts.py b/src/layouts.py index b4660cc2..70c4bb7f 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -14,6 +14,7 @@ from omnitils.strings import get_line, get_lines, normalize_str, strip_lines from src import CFG, CON, CONSOLE, ENV, PATH +from src._state import HexproofSet from src.cards import ( CardDetails, FrameDetails, @@ -197,10 +198,6 @@ def __init__(self, scryfall: ScryfallCard, file: CardDetails): self._file = file self._scryfall = scryfall - # Cache set data and frame data - _ = self.set_data - _ = self.frame - def __str__(self): """String representation of the card layout object.""" return (f"{self.name}" @@ -251,9 +248,9 @@ def set(self) -> str: return self.scryfall.set.upper() @cached_property - def set_data(self) -> dict[str,Any]: + def set_data(self) -> HexproofSet | None: """Set data from the current hexproof.io data file.""" - return CON.set_data.get(self.scryfall.set.lower(), {}) + return CON.set_data.get(self.scryfall.set.lower(), None) @cached_property def set_type(self) -> str: @@ -454,7 +451,9 @@ def symbol_code(self) -> str: """Code used to match a symbol to this card's set. Provided by hexproof.io.""" if CFG.symbol_force_default: return CFG.symbol_default.upper() - return self.set_data.get('code_symbol', 'DEFAULT').upper() + if self.set_data: + return self.set_data.code_symbol.upper() + return "DEFAULT" @cached_property def lang(self) -> str: @@ -516,12 +515,12 @@ def card_count(self) -> int | None: return # Prefer printed count, fallback to card count, skip if count isn't found - count = self.set_data.get('count_printed', self.set_data.get('count_cards')) - if count is None: + if not self.set_data: return + count = self.set_data.count_printed or self.set_data.count_cards # Skip if count is smaller than collector number - return count if int(count) >= self.collector_number else None + return count if count >= self.collector_number else None @cached_property def collector_data(self) -> str: @@ -1753,7 +1752,9 @@ def collector_data(self) -> str: @cached_property def set(self) -> str: """str: Use parent set code if provided.""" - return self.set_data.get('code_parent', super().set).upper() + if self.set_data and self.set_data.code_parent: + return self.set_data.code_parent.upper() + return super().set @cached_property def card_count(self) -> int | None: @@ -1763,8 +1764,9 @@ def card_count(self) -> int | None: if CFG.collector_mode != CollectorMode.Normal or not self.collector_number_raw: return - # Prefer printed count, fallback to card count, skip if count isn't found - return self.set_data.get('count_tokens', None) + if self.set_data: + return self.set_data.count_tokens + return class StationDetails(TypedDict): diff --git a/src/utils/github.py b/src/utils/github.py new file mode 100644 index 00000000..c3cc5a2f --- /dev/null +++ b/src/utils/github.py @@ -0,0 +1,88 @@ +from collections.abc import Callable +from typing import Literal + +from backoff import expo, on_exception +from limits import RateLimitItemPerHour +from limits.storage import MemoryStorage +from limits.strategies import MovingWindowRateLimiter +from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from omnitils.rate_limit import rate_limit +from pydantic import BaseModel, RootModel +from requests import RequestException, get + +from src import CONSOLE + +# Rate limiter to safely limit GitHub requests +_rate_limit_storage = MemoryStorage() +_rate_limiter = MovingWindowRateLimiter(_rate_limit_storage) +_rate_limit = RateLimitItemPerHour(60) + +_headers = {"accept": "application/vnd.github+json"} + +GITHUB_API_BASE_URL = "https://api.github.com/" +GITHUB_API_RELEASES_URL = ( + GITHUB_API_BASE_URL + "repos/{repo}/releases?per_page={per_page}&page={page}" +) + + +# These definitions are incomplete since we don't need most of the fields. +class GitHubReleaseAsset(BaseModel): + id: int + name: str + content_type: str + state: Literal["uploaded", "open"] + size: int + """Bytes""" + created_at: str + updated_at: str + browser_download_url: str + + +class GitHubRelease(BaseModel): + id: int + tag_name: str + name: str + draft: bool + prerelease: bool + created_at: str + updated_at: str + published_at: str + assets: list[GitHubReleaseAsset] + + +GitHubReleases = RootModel[list[GitHubRelease]] + + +def github_request_wrapper[T, **P]( + fallback: T, logr: ExceptionLogger | None = None +) -> Callable[[Callable[P, T]], Callable[P, T]]: + logr = logr or CONSOLE + + def decorator(func: Callable[P, T]): + @return_on_exception(fallback) + @log_on_exception(logr) + @rate_limit(limiter=_rate_limiter, limit=_rate_limit) + @on_exception(expo, RequestException, max_tries=2, max_time=1) + def wrapper(*args: P.args, **kwargs: P.kwargs): + return func(*args, **kwargs) + + return wrapper + + return decorator + + +_default_releases: list[GitHubRelease] = [] + + +@github_request_wrapper(_default_releases) +def get_github_releases( + repository: str, per_page: int = 5, page: int = 1 +) -> list[GitHubRelease]: + response = get( + GITHUB_API_RELEASES_URL.format(repo=repository, per_page=per_page, page=page), + headers=_headers, + timeout=(10, 10), + ) + if response.status_code == 200: + return GitHubReleases.model_validate_json(response.content).root + raise RequestException(response=response) diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 4cff5111..5528f15f 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -3,9 +3,11 @@ """ from collections.abc import Callable from contextlib import suppress +from datetime import datetime from functools import cache +from io import BytesIO from pathlib import Path -from typing import Any, ParamSpec, TypeVar +from zipfile import ZipFile import requests from backoff import expo, on_exception @@ -15,32 +17,15 @@ from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception -from omnitils.fetch import download_file from omnitils.files import dump_data_file -from omnitils.files.archive import unpack_zip from omnitils.rate_limit import rate_limit -from pydantic import BaseModel -from requests import RequestException +from requests import RequestException, get -from src import CON, CONSOLE, PATH +from src import CON, CONSOLE, ENV, PATH +from src._loader import SymbolsManifest, get_symbols_manifest +from src._state import HexproofSet, HexproofSets from src.utils.download import HEADERS - -""" -* Types -""" - -T = TypeVar("T") -P = ParamSpec("P") - - -class HexproofSet(BaseModel): - """Cached 'Set' object data from Hexproof.io.""" - code_symbol: str - code_parent: str | None = None - count_cards: int - count_tokens: int - count_printed: int | None = None - +from src.utils.github import GitHubReleaseAsset, get_github_releases """ * Hexproof.io Objects @@ -59,7 +44,7 @@ class HexproofSet(BaseModel): """ -def hexproof_request_wrapper(fallback: T, logr: ExceptionLogger | None = None) -> Callable[[Callable[P,T]], Callable[P, T]]: +def hexproof_request_wrapper[T, **P](fallback: T, logr: ExceptionLogger | None = None) -> Callable[[Callable[P,T]], Callable[P, T]]: """Wrapper for a Hexproof.io request function to handle retries, rate limits, and a final exception catch. Args: @@ -127,7 +112,7 @@ def get_metadata() -> dict[str, Meta]: @hexproof_request_wrapper({}) -def get_sets() -> dict[str,dict[str,Any]]: +def get_sets() -> dict[str,HexproofSet]: """Retrieve the current 'Set' data manifest from https://api.hexproof.io. Returns: @@ -138,41 +123,24 @@ def get_sets() -> dict[str,dict[str,Any]]: """ res = requests.get(str(HexURL.API.Sets.All), headers=hexproof_http_header, timeout=(5, 5)) if res.status_code == 200: - return res.json() + return HexproofSets.model_validate_json(res.content).root raise RequestException( res.json().get('details', 'Failed to get set data!'), response=res) -""" -* Data Post-Processing -""" - - -def process_data_sets(data: dict[str,dict[str,Any]]) -> dict[str, HexproofSet]: - """Process bulk 'Set' data retrieved from the Hexproof API into a smaller dataset. - - Args: - data: Raw data pulled from the `/sets/` Hexproof API endpoint. - - Returns: - Dictionary of smaller 'HexproofSet' data entries. - """ - return { - code: HexproofSet( - code_symbol=d.get('code_symbol', 'DEFAULT'), - count_cards=d.get('count_cards', 0), - count_tokens=d.get('count_tokens', 0), - code_parent=d.get('code_parent'), - count_printed=d.get('count_printed'), - ) for code, d in data.items() - } - - """ * Data Caching """ +def _download_symbols(url: str) -> SymbolsManifest | None: + response = get(url) + if response.status_code == 200: + with ZipFile(BytesIO(response.content), "r") as zf: + zf.extractall(PATH.SRC_IMG_SYMBOLS) + return get_symbols_manifest() + return None + def update_hexproof_cache() -> tuple[bool, str | None]: """Check for a hexproof.io data update. @@ -186,36 +154,92 @@ def update_hexproof_cache() -> tuple[bool, str | None]: meta: dict[str, Meta] = get_metadata() # Check against current metadata + new_set_data: dict[str,HexproofSet] | None = None _current, _next = CON.metadata.get('sets'), meta.get('sets') if not _current or not _next or _current.version != _next.version: try: # Download updated 'Set' data - data = get_sets() - data = process_data_sets(data) - dump_data_file( - obj={k: v.model_dump(exclude_none=True) for k, v in data.items()}, - path=PATH.SRC_DATA_HEXPROOF_SET) + new_set_data = get_sets() updated = True except (RequestException, ValueError, OSError): return False, "Unable to update 'Set' data from hexproof.io!" # Check against current symbol data - _current, _next = CON.metadata.get('symbols'), meta.get('symbols') - if not _current or not _next or _current.version != _next.version: + new_symbols: SymbolsManifest | None = None + symbols_dl_url: str | None = None + if ENV.SYMBOL_UPDATES_REPO: + symbols_releases = get_github_releases(ENV.SYMBOL_UPDATES_REPO, per_page=1) + if len(symbols_releases) > 0: + latest_release = symbols_releases[0] + if len(latest_release.assets) > 0: + chosen_asset: GitHubReleaseAsset | None = None + for asset in latest_release.assets: + if "optimized" in asset.name: + chosen_asset = asset + break + if not chosen_asset: + chosen_asset = latest_release.assets[0] + asset_timestamp: datetime + try: + asset_timestamp = datetime.fromisoformat(latest_release.tag_name) + except ValueError: + asset_timestamp = datetime.fromisoformat(chosen_asset.updated_at) + if not ( + current_symbols_manifest := get_symbols_manifest() + ) or asset_timestamp > datetime.fromisoformat( + current_symbols_manifest.meta.date + ): + symbols_dl_url = chosen_asset.browser_download_url + else: + _current, _next = CON.metadata.get("symbols"), meta.get("symbols") + if not _current or not _next or _current.version != _next.version: + symbols_dl_url = str(HexURL.API.Symbols.All / "package") + + if symbols_dl_url: try: # Download and unpack updated 'Symbols' assets - download_file( - url=HexURL.API.Symbols.All / 'package', - path=PATH.SRC_IMG_SYMBOLS_PACKAGE) - unpack_zip(PATH.SRC_IMG_SYMBOLS_PACKAGE) - updated = True + new_symbols = _download_symbols(symbols_dl_url) + updated = updated or bool(new_symbols) except (RequestException, FileNotFoundError): - return False, 'Unable to download symbols package!' + return False, "Unable to download symbols package!" # Update metadata try: if not updated: return updated, None + + # Ensure that all symbols are present in set data + if new_symbols: + symbs = new_symbols.set.symbols + set_data = new_set_data or CON.set_data + for card_set, data in set_data.items(): + if data.code_symbol == "default": + if card_set.upper() in symbs: + data.code_symbol = card_set + # elif card_set in symbs: + # data.code_symbol = card_set + else: + curr_key = card_set + curr_data = data + while curr_data.code_parent: + curr_key = curr_data.code_parent + curr_data = set_data.get(curr_key, None) + if not curr_data: + break + if curr_key.upper() in symbs: + data.code_symbol = curr_key + break + # elif curr_key in symbs: + # data.code_symbol = curr_key + # break + for card_set in symbs: + if (card_set_low := card_set.lower()) not in set_data: + set_data[card_set_low] = HexproofSet( + code_symbol=card_set_low, count_cards=0, count_tokens=0 + ) + with open(PATH.SRC_DATA_HEXPROOF_SET, "w") as f: + f.write(HexproofSets(set_data).model_dump_json(indent=2)) + dump_data_file( obj={k: v.model_dump() for k, v in meta.items()}, path=PATH.SRC_DATA_HEXPROOF_META) @@ -230,7 +254,7 @@ def update_hexproof_cache() -> tuple[bool, str | None]: @cache -def get_set_data(code: str) -> dict[str,Any] | None: +def get_set_data(code: str) -> HexproofSet | None: """Returns a specific 'Set' object by set code. Args: @@ -258,12 +282,11 @@ def get_watermark_svg_from_set(code: str) -> Path | None: return # Check if this set has a provided symbol code - symbol = set_obj.get('code_symbol') - if not symbol: + if not set_obj.code_symbol: return # Check if this symbol code matches a supported watermark - p = PATH.SRC_IMG_SYMBOLS / 'set' / symbol.upper() / 'WM.svg' + p = PATH.SRC_IMG_SYMBOLS / 'set' / set_obj.code_symbol.upper() / 'WM.svg' return p if p.is_file() else None From ee142823fd2ad898eba32e83dbbd9218a4168f33 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 25 Oct 2025 17:49:15 +0300 Subject: [PATCH 057/190] refactor(scryfall.py): Do not use the `type` keyword for defining Literal Scryfall types --- src/utils/scryfall.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index a739d178..a5030f69 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -41,7 +41,7 @@ T = TypeVar("T") P = ParamSpec("P") -type LayoutType = Literal[ +LayoutType = Literal[ "normal", "split", "flip", @@ -73,15 +73,15 @@ "station", ] -type MagicColor = Literal["W", "U", "B", "R", "G"] +MagicColor = Literal["W", "U", "B", "R", "G"] -type Legality = Literal["legal", "not_legal", "restricted", "banned"] +Legality = Literal["legal", "not_legal", "restricted", "banned"] -type BorderColor = Literal["black", "white", "borderless", "yellow", "silver", "gold"] +BorderColor = Literal["black", "white", "borderless", "yellow", "silver", "gold"] -type Finish = Literal["foil", "nonfoil", "etched"] +Finish = Literal["foil", "nonfoil", "etched"] -type FrameEffect = Literal[ +FrameEffect = Literal[ "legendary", "miracle", "enchantment", @@ -110,13 +110,13 @@ "meld", ] -type ScryfallGame = Literal["paper", "arena", "mtgo"] +ScryfallGame = Literal["paper", "arena", "mtgo"] -type ScryfallImageStatus = Literal["missing", "placeholder", "lowres", "highres_scan"] +ScryfallImageStatus = Literal["missing", "placeholder", "lowres", "highres_scan"] -type Rarity = Literal["common", "uncommon", "rare", "special", "mythic", "bonus"] +Rarity = Literal["common", "uncommon", "rare", "special", "mythic", "bonus"] -type SetType = Literal[ +SetType = Literal[ "core", "expansion", "masters", From aef6e2df74729c9e898c9e5cbaee3d751f0f0478 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 26 Oct 2025 05:24:54 +0200 Subject: [PATCH 058/190] feat: Add a setting for forcing a symbol of a specified rarity to be used --- src/_config.py | 3 +++ src/data/config/base.toml | 9 +++++++++ src/layouts.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/src/_config.py b/src/_config.py index 8a7f3e9c..1fb9f1b1 100644 --- a/src/_config.py +++ b/src/_config.py @@ -122,6 +122,9 @@ def update_definitions(self): self.symbol_force_default = self.file.getboolean( "BASE.SYMBOLS", "Force.Default.Symbol", fallback=False ) + self.symbol_force_rarity = self.file.get( + "BASE.SYMBOLS", "Force.Rarity", fallback="" + ) # BASE - WATERMARKS self.watermark_mode = self.get_option( diff --git a/src/data/config/base.toml b/src/data/config/base.toml index 602e845d..c7d1edc0 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -69,6 +69,15 @@ desc = """Forces Proxyshop to always used the Default Expansion Symbol chosen ab type = "bool" default = 0 +[SYMBOLS."Force.Rarity"] +title = "Force Use of Specific Rarity" +desc = """Forces Proxyshop to try to use a symbol of a specified rarity. The card's real rarity will be used as a fallback if the specified rarity can't be found. +Possible rarities include: [b]80[/b], [b]B[/b], [b]C[/b], [b]H[/b], [b]M[/b], [b]R[/b], [b]S[/b], [b]T[/b], [b]U[/b], [b]WM[/b] +You may also specify a code for any extra symbol variants that you might have copied to your installation. Extra variants are matched case sensitively by their filename without the suffix (.svg). +Empty string disables this feature.""" +type = "string" +default = "" + ### # * Watermark Settings ### diff --git a/src/layouts.py b/src/layouts.py index 70c4bb7f..4302c86b 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -543,6 +543,10 @@ def creator(self) -> str: @cached_property def symbol_svg(self) -> Path | None: """SVG path definition for card's expansion symbol.""" + if CFG.symbol_force_rarity: + path = (PATH.SRC_IMG_SYMBOLS / 'set' / self.set / CFG.symbol_force_rarity).with_suffix('.svg') + if path.is_file(): + return path # If code is default, perform replacement and check if we have a local asset first if self.symbol_code == 'DEFAULT': From f54baffeeb773a0170bb0ff4bf1ed7340b0569e5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 26 Oct 2025 07:53:31 +0200 Subject: [PATCH 059/190] refactor(ENV): Use pydantic-settings instead of dynaconf for app environment management pydantic-settings allows validating environment variables in less verbose and more type safe manner than dynaconf BREAKING CHANGE: None isn't interpreted as a default anymore for some of the env variables, so env variables that should use default values should not be defined at all, e.g., in the env files. --- poetry.lock | 159 ++++++++++-------------------------- pyproject.toml | 2 +- src/__init__.py | 34 +------- src/_state.py | 172 +++++++++++---------------------------- src/data/env.default.yml | 20 ++--- 5 files changed, 104 insertions(+), 283 deletions(-) diff --git a/poetry.lock b/poetry.lock index 206d267d..334fdadb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -872,31 +872,6 @@ files = [ {file = "docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d"}, ] -[[package]] -name = "dynaconf" -version = "3.2.12" -description = "The dynamic configurator for your Python Project" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "dynaconf-3.2.12-py2.py3-none-any.whl", hash = "sha256:eb2a11865917dff8810c6098cd736b8f4d2f4e39ad914500e2dfbe064b82c499"}, - {file = "dynaconf-3.2.12.tar.gz", hash = "sha256:29cea583b007d890e6031fa89c0ac489b631c73dbee83bcd5e6f97602c26354e"}, -] - -[package.dependencies] -"ruamel.yaml" = {version = "*", optional = true, markers = "extra == \"yaml\""} - -[package.extras] -all = ["configobj", "hvac", "redis", "ruamel.yaml"] -configobj = ["configobj"] -ini = ["configobj"] -redis = ["redis"] -test = ["configobj", "django", "flask (>=0.12)", "hvac (>=1.1.0)", "pytest", "pytest-cov", "pytest-mock", "pytest-xdist", "python-dotenv", "radon", "redis", "toml"] -toml = ["toml"] -vault = ["hvac"] -yaml = ["ruamel.yaml"] - [[package]] name = "filelock" version = "3.20.0" @@ -3321,6 +3296,30 @@ files = [ [package.dependencies] typing-extensions = ">=4.14.1" +[[package]] +name = "pydantic-settings" +version = "2.11.0" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, + {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" + +[package.extras] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + [[package]] name = "pygments" version = "2.19.2" @@ -3547,6 +3546,21 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pywin32" version = "311" @@ -3851,91 +3865,6 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] -[[package]] -name = "ruamel-yaml" -version = "0.18.16" -description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba"}, - {file = "ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a"}, -] - -[package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""} - -[package.extras] -docs = ["mercurial (>5.7)", "ryd"] -jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.14" -description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\"" -files = [ - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win32.whl", hash = "sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182"}, - {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win_amd64.whl", hash = "sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a"}, - {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54"}, - {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2"}, - {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78"}, - {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f"}, - {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83"}, - {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27"}, - {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win32.whl", hash = "sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352"}, - {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6"}, - {file = "ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e"}, -] - [[package]] name = "scikit-image" version = "0.25.2" @@ -4124,14 +4053,14 @@ files = [ [[package]] name = "termcolor" -version = "3.1.0" +version = "3.2.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"}, - {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"}, + {file = "termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6"}, + {file = "termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58"}, ] [package.extras] @@ -4691,4 +4620,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.15" -content-hash = "254606293f604a14159ae8c50a212a749786f1da4a607bc1a2e2fdb8a6283eb4" +content-hash = "66a8ac543cfd4352d4e1573499856c835c1d1a62f5482ef629bd865e4856a428" diff --git a/pyproject.toml b/pyproject.toml index 7ad43302..f017601e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,8 @@ click = "^8.3.0" tomli = "^2.3.0" yarl = "^1.22.0" pydantic = "^2.12.3" +pydantic-settings = "^2.11.0" omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } -dynaconf = { extras = ["yaml"], version = "^3.2.12" } hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } rich = "^14.2.0" pywin32 = "^311.0.0" diff --git a/src/__init__.py b/src/__init__.py index e2ad4fb8..2950bebb 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,12 +2,6 @@ * Load Global Application State """ -import sys -from pathlib import Path - -from dynaconf import Validator -from omnitils.files import get_project_version - from src.utils.adobe import PhotoshopHandler from src.utils.threading import ThreadInitializedInstance @@ -25,32 +19,8 @@ """ -def _get_proj_version(path: Path) -> str: - try: - return get_project_version(path) - except Exception: - return "0.0.0" - - -# Global environment object (dynaconf) -ENV = AppEnvironment( - envvar_prefix="PROXYSHOP", - settings_files=[PATH.SRC_DATA_ENV_DEFAULT, PATH.SRC_DATA_ENV], - validators=[ - Validator("API_GOOGLE", cast=str, default=""), - Validator("API_AMAZON", cast=str, default=""), - Validator("PS_ERROR_DIALOG", cast=bool, default=False), - Validator("PS_VERSION", cast=AppEnvironment.string_or_none, default=None), - Validator("HEADLESS", cast=bool, default=False), - Validator("DEV_MODE", cast=bool, default=bool(not hasattr(sys, "_MEIPASS"))), - Validator("TEST_MODE", cast=bool, default=False), - Validator("APP_UPDATES_REPO", cast=str, default=""), - Validator("SYMBOL_UPDATES_REPO", cast=str, default=""), - Validator("VERSION", cast=str, default=_get_proj_version(PATH.PROJECT_FILE)), - Validator("FORCE_RELOAD", cast=bool, default=False), - ], - apply_default_on_none=True, -) +# Global environment object +ENV = AppEnvironment() # Global constants object CON = AppConstants() diff --git a/src/_state.py b/src/_state.py index 42064b8a..cd29a475 100644 --- a/src/_state.py +++ b/src/_state.py @@ -5,19 +5,23 @@ import os import sys from contextlib import suppress -from functools import cached_property from os import environ from pathlib import Path from threading import Lock -from typing import TYPE_CHECKING, Any +from typing import Any -from dynaconf import Dynaconf from hexproof.hexapi.schema.meta import Meta from omnitils.exceptions import return_on_exception -from omnitils.files import dump_data_file, load_data_file +from omnitils.files import dump_data_file, get_project_version, load_data_file from omnitils.metaclass import Singleton from omnitils.properties import tracked_prop from pydantic import BaseModel, RootModel +from pydantic_settings import ( + BaseSettings, + PydanticBaseSettingsSource, + SettingsConfigDict, + YamlConfigSettingsSource, +) from src.enums.layers import LAYERS from src.enums.mtg import CardFonts, mana_symbol_map @@ -146,128 +150,46 @@ class PATH(DefinedPaths): environ.setdefault('KIVY_LOG_MODE', 'PYTHON') environ.setdefault('KIVY_NO_FILELOG', '1') +def _get_proj_version(path: Path) -> str: + if hasattr(sys, '_MEIPASS'): + # Build with Pyinstaller generated module + import __VERSION__ + return str(__VERSION__.version) + try: + return get_project_version(path) + except Exception: + return "0.0.0" + +class AppEnvironment(BaseSettings): + API_GOOGLE: str = "" + API_AMAZON: str = "" + PS_ERROR_DIALOG: bool = False + PS_VERSION: str | None = None + HEADLESS: bool = False + DEV_MODE: bool = bool(not hasattr(sys, "_MEIPASS")) + TEST_MODE: bool = False + APP_UPDATES_REPO: str = "" + SYMBOL_UPDATES_REPO: str = "" + FORCE_RELOAD: bool = False + VERSION: str = _get_proj_version(PATH.PROJECT_FILE) + + model_config = SettingsConfigDict(env_prefix="PROXYSHOP_") -class CustomDynaconf(Dynaconf): - if TYPE_CHECKING: - @cached_property - def API_GOOGLE(self) -> str: ... - @cached_property - def API_AMAZON(self) -> str: ... - @cached_property - def PS_ERROR_DIALOG(self) -> bool: ... - @cached_property - def PS_VERSION(self) -> str | None: ... - @cached_property - def HEADLESS(self) -> bool: ... - @cached_property - def DEV_MODE(self) -> bool: ... - @cached_property - def TEST_MODE(self) -> bool: ... - @cached_property - def APP_UPDATES_REPO(self) -> str: ... - @cached_property - def SYMBOL_UPDATES_REPO(self) -> str: ... - @cached_property - def FORCE_RELOAD(self) -> bool: ... - @cached_property - def VERSION(self) -> str: ... - - -class AppEnvironment(CustomDynaconf): - """Tracking and modifying global environment behavior.""" - - @staticmethod - def string_or_none(value: Any) -> str | None: - """Casts a value as either a string OR None. - - Args: - value: Value to be cast. - - Returns: - A string or None. - """ - if value in (None, 'null', 'None', ''): - return None - return str(value) - - """ - * File Hosting - """ - - @cached_property - def API_GOOGLE(self) -> str: - """str: Google Drive API key.""" - return super().API_GOOGLE - - @cached_property - def API_AMAZON(self) -> str: - """str: Amazon S3 cloudfront URL.""" - return super().API_AMAZON - - """ - * Photoshop - """ - - @cached_property - def PS_ERROR_DIALOG(self) -> bool: - """bool: Whether Photoshop error dialogues are enabled.""" - return super().PS_ERROR_DIALOG - - @cached_property - def PS_VERSION(self) -> str | None: - """str: Photoshop version to try and load, for use when multiple Photoshop versions are installed.""" - if (ver := (super().PS_VERSION)) not in ['', None]: - return ver - return None - - """ - * Testing - """ - - @cached_property - def HEADLESS(self) -> bool: - """bool: Whether the app is running in headless mode.""" - return super().HEADLESS - - @cached_property - def DEV_MODE(self) -> bool: - """bool: Whether the app is running in developer mode.""" - return super().DEV_MODE - - @cached_property - def TEST_MODE(self) -> bool: - """bool: Whether the app is running in testing mode.""" - return super().TEST_MODE - - """ - * Update checks - """ - - @cached_property - def APP_UPDATES_REPO(self) -> str: - return super().APP_UPDATES_REPO - - @cached_property - def SYMBOL_UPDATES_REPO(self) -> str: - return super().SYMBOL_UPDATES_REPO - - """ - * Experimental - """ - - @cached_property - def FORCE_RELOAD(self) -> bool: - """bool: Whether to force plugin template modules to be reloaded on each new render sequence.""" - return super().FORCE_RELOAD - - @cached_property - def VERSION(self) -> str: - """str: Current app version.""" - if hasattr(sys, '_MEIPASS'): - # Build with Pyinstaller generated module - import __VERSION__ # noqa - return str(__VERSION__.version) - return super().VERSION + @classmethod + def settings_customise_sources( + cls, + settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource, + ) -> tuple[PydanticBaseSettingsSource, ...]: + # First entry has highest priority + return ( + env_settings, + YamlConfigSettingsSource(settings_cls, PATH.SRC_DATA_ENV), + YamlConfigSettingsSource(settings_cls, PATH.SRC_DATA_ENV_DEFAULT), + ) """ diff --git a/src/data/env.default.yml b/src/data/env.default.yml index b79cb415..75ffd575 100644 --- a/src/data/env.default.yml +++ b/src/data/env.default.yml @@ -10,33 +10,33 @@ ### # Google API Key - ONLY FOR ADVANCED USERS -API_GOOGLE: null +# API_GOOGLE: "" # Cloudfront URL - ONLY FOR ADVANCED USERS -API_AMAZON: null +# API_AMAZON: "" ### # * Photoshop Settings ### # Enable Photoshop error dialogs when executing actions -PS_ERROR_DIALOG: False +# PS_ERROR_DIALOG: False # Optionally specify Photoshop version to look for (EXPERIMENTAL) -PS_VERSION: null +# PS_VERSION: null ### # * App Testing ### # Force the app to use headless console (ADVANCED) -HEADLESS: False +# HEADLESS: False # Force the app into development mode -DEV_MODE: False +# DEV_MODE: False # Force the app into testing mode -TEST_MODE: False +# TEST_MODE: False ### # * Update Checks @@ -44,14 +44,14 @@ TEST_MODE: False # Specify using format "/" APP_UPDATES_REPO: Investigamer/Proxyshop -SYMBOL_UPDATES_REPO: "" +# SYMBOL_UPDATES_REPO: "" ### # * Experimental ### # Force plugin template modules to be reloaded on each new render -FORCE_RELOAD: False +# FORCE_RELOAD: False # Give Proxyshop an alternative version string -VERSION: null +# VERSION: "" From faf79ac20838dfae767f45f8bcd5f8949f463ee5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 17 Feb 2026 16:40:16 +0200 Subject: [PATCH 060/190] feat: Replace the Kivy GUI with a PySide6 GUI BREAKING CHANGE: Only the most specific plugin template definition format is supported by the new validator as supporting many different formats makes the code more complicated and makes it harder for plugin developers to understands the manifest file. --- .gitignore | 18 +- main.py | 178 +- plugins/Investigamer/manifest.yml | 75 +- .../Investigamer/py/actions/pencilsketch.py | 14 +- plugins/Investigamer/py/templates.py | 18 +- plugins/SilvanMTG/manifest.yml | 31 +- poetry.lock | 3413 ++++++++--------- pyproject.toml | 73 +- src/__init__.py | 33 +- src/_config.py | 145 +- src/_loader.py | 1690 ++++---- src/_state.py | 293 +- src/cards.py | 142 +- src/commands/render.py | 9 +- src/commands/test/__init__.py | 13 +- src/commands/test/compression.py | 27 +- src/commands/test/frame_logic.py | 62 +- src/commands/test/text_logic.py | 35 +- src/console.py | 507 +-- src/data/build/Proxyshop-console.spec | 58 - src/data/build/Proxyshop.spec | 58 - src/data/config/app.toml | 8 +- src/data/config/base.toml | 12 +- src/data/kv/app.kv | 32 - src/data/kv/console.kv | 127 - src/data/kv/creator.kv | 350 -- src/data/kv/main.kv | 135 - src/data/kv/test.kv | 46 - src/data/kv/tools.kv | 102 - src/data/kv/updater.kv | 106 - src/data/manifest.yml | 285 +- src/data/tests/template_renders.toml | 15 +- src/enums/adobe.py | 9 +- src/enums/layers.py | 6 +- src/enums/mtg.py | 90 +- src/enums/settings.py | 71 +- src/gui/__init__.py | 8 - src/gui/_state.py | 155 - src/gui/app.py | 1145 ------ src/gui/console.py | 374 -- src/gui/popup/settings.py | 271 -- src/gui/popup/updater.py | 135 - src/gui/qml/App.qml | 608 +++ src/gui/qml/components/Badge.qml | 36 + src/gui/qml/components/CustomButton.qml | 23 + src/gui/qml/components/CustomCheckBox.qml | 32 + src/gui/qml/components/CustomComboBox.qml | 41 + src/gui/qml/components/CustomItemDelegate.qml | 26 + src/gui/qml/components/CustomMenu.qml | 20 + src/gui/qml/components/CustomMenuItem.qml | 16 + src/gui/qml/components/CustomSpinBox.qml | 6 + src/gui/qml/components/CustomTabButton.qml | 27 + src/gui/qml/components/CustomTextField.qml | 9 + src/gui/qml/components/SelectableText.qml | 8 + src/gui/qml/components/qmldir | 12 + src/gui/qml/dialogs/CustomFileDialog.qml | 32 + src/gui/qml/dialogs/CustomMessageDialog.qml | 38 + src/gui/qml/dialogs/qmldir | 3 + src/gui/qml/models/__init__.py | 0 src/gui/qml/models/base_dialog_model.py | 34 + src/gui/qml/models/batch_rendering_model.py | 292 ++ src/gui/qml/models/console_model.py | 71 + src/gui/qml/models/file_dialog_model.py | 150 + src/gui/qml/models/file_path_model.py | 31 + src/gui/qml/models/image_transform_model.py | 124 + .../models/message_dialog_content_model.py | 132 + src/gui/qml/models/pydantic_q_list_model.py | 240 ++ src/gui/qml/models/render_operations_model.py | 205 + src/gui/qml/models/settings_model.py | 207 + src/gui/qml/models/settings_tree_model.py | 220 ++ src/gui/qml/models/template_list_model.py | 286 ++ src/gui/qml/models/template_updater_model.py | 133 + src/gui/qml/models/test_renders_model.py | 122 + src/gui/qml/qmldir | 2 + src/gui/qml/tools/FontViewer.qml | 34 + src/gui/qml/tools/ImageTransformWindow.qml | 155 + src/gui/qml/tools/SystemPaletteViewer.qml | 197 + src/gui/qml/tools/qmldir | 3 + src/gui/qml/views/BatchRenderView.qml | 89 + src/gui/qml/views/Console.qml | 96 + src/gui/qml/views/RenderQueue.qml | 323 ++ src/gui/qml/views/SettingsWindow.qml | 383 ++ src/gui/qml/views/TemplateDetails.qml | 147 + src/gui/qml/views/TemplateList.qml | 111 + src/gui/qml/views/TemplateUpdater.qml | 316 ++ src/gui/qml/views/qmldir | 8 + src/gui/tabs/creator.py | 213 - src/gui/tabs/main.py | 294 -- src/gui/tabs/tools.py | 172 - src/gui/test.py | 91 - src/gui/utils/__init__.py | 8 - src/gui/utils/behaviors.py | 69 - src/gui/utils/fonts.py | 42 - src/gui/utils/forms.py | 194 - src/gui/utils/layouts.py | 224 -- src/helpers/design.py | 53 +- src/helpers/document.py | 39 +- src/helpers/effects.py | 30 +- src/helpers/layers.py | 69 +- src/helpers/text.py | 53 +- src/layouts.py | 718 ++-- src/render/__init__.py | 0 src/render/render_queue.py | 112 + src/render/setup.py | 264 ++ src/schema/adobe.py | 14 +- src/schema/colors.py | 11 +- src/startup.py | 129 + src/templates/_core.py | 254 +- src/templates/_cosmetic.py | 11 +- src/templates/mutate.py | 12 +- src/templates/normal.py | 99 +- src/templates/planar.py | 11 +- src/templates/planeswalker.py | 22 +- src/templates/prototype.py | 13 +- src/templates/split.py | 19 +- src/templates/token.py | 14 +- src/templates/transform.py | 36 +- src/text_layers.py | 62 +- src/utils/adobe.py | 36 +- src/utils/asynchronic.py | 22 + src/utils/build.py | 28 +- src/utils/data_structures.py | 42 + src/utils/event.py | 16 +- src/utils/fonts.py | 54 +- src/utils/github.py | 15 +- src/utils/hexapi.py | 113 +- src/utils/images.py | 94 + src/utils/lazy.py | 14 + src/utils/logging.py | 24 + src/utils/mtg.py | 4 +- src/utils/scryfall.py | 167 +- src/utils/tests.py | 34 + src/utils/threading.py | 9 +- src/utils/windows.py | 6 +- 134 files changed, 10235 insertions(+), 9217 deletions(-) delete mode 100644 src/data/build/Proxyshop-console.spec delete mode 100644 src/data/build/Proxyshop.spec delete mode 100644 src/data/kv/app.kv delete mode 100644 src/data/kv/console.kv delete mode 100644 src/data/kv/creator.kv delete mode 100644 src/data/kv/main.kv delete mode 100644 src/data/kv/test.kv delete mode 100644 src/data/kv/tools.kv delete mode 100644 src/data/kv/updater.kv delete mode 100644 src/gui/__init__.py delete mode 100644 src/gui/_state.py delete mode 100644 src/gui/app.py delete mode 100644 src/gui/console.py delete mode 100644 src/gui/popup/settings.py delete mode 100644 src/gui/popup/updater.py create mode 100644 src/gui/qml/App.qml create mode 100644 src/gui/qml/components/Badge.qml create mode 100644 src/gui/qml/components/CustomButton.qml create mode 100644 src/gui/qml/components/CustomCheckBox.qml create mode 100644 src/gui/qml/components/CustomComboBox.qml create mode 100644 src/gui/qml/components/CustomItemDelegate.qml create mode 100644 src/gui/qml/components/CustomMenu.qml create mode 100644 src/gui/qml/components/CustomMenuItem.qml create mode 100644 src/gui/qml/components/CustomSpinBox.qml create mode 100644 src/gui/qml/components/CustomTabButton.qml create mode 100644 src/gui/qml/components/CustomTextField.qml create mode 100644 src/gui/qml/components/SelectableText.qml create mode 100644 src/gui/qml/components/qmldir create mode 100644 src/gui/qml/dialogs/CustomFileDialog.qml create mode 100644 src/gui/qml/dialogs/CustomMessageDialog.qml create mode 100644 src/gui/qml/dialogs/qmldir create mode 100644 src/gui/qml/models/__init__.py create mode 100644 src/gui/qml/models/base_dialog_model.py create mode 100644 src/gui/qml/models/batch_rendering_model.py create mode 100644 src/gui/qml/models/console_model.py create mode 100644 src/gui/qml/models/file_dialog_model.py create mode 100644 src/gui/qml/models/file_path_model.py create mode 100644 src/gui/qml/models/image_transform_model.py create mode 100644 src/gui/qml/models/message_dialog_content_model.py create mode 100644 src/gui/qml/models/pydantic_q_list_model.py create mode 100644 src/gui/qml/models/render_operations_model.py create mode 100644 src/gui/qml/models/settings_model.py create mode 100644 src/gui/qml/models/settings_tree_model.py create mode 100644 src/gui/qml/models/template_list_model.py create mode 100644 src/gui/qml/models/template_updater_model.py create mode 100644 src/gui/qml/models/test_renders_model.py create mode 100644 src/gui/qml/qmldir create mode 100644 src/gui/qml/tools/FontViewer.qml create mode 100644 src/gui/qml/tools/ImageTransformWindow.qml create mode 100644 src/gui/qml/tools/SystemPaletteViewer.qml create mode 100644 src/gui/qml/tools/qmldir create mode 100644 src/gui/qml/views/BatchRenderView.qml create mode 100644 src/gui/qml/views/Console.qml create mode 100644 src/gui/qml/views/RenderQueue.qml create mode 100644 src/gui/qml/views/SettingsWindow.qml create mode 100644 src/gui/qml/views/TemplateDetails.qml create mode 100644 src/gui/qml/views/TemplateList.qml create mode 100644 src/gui/qml/views/TemplateUpdater.qml create mode 100644 src/gui/qml/views/qmldir delete mode 100644 src/gui/tabs/creator.py delete mode 100644 src/gui/tabs/main.py delete mode 100644 src/gui/tabs/tools.py delete mode 100644 src/gui/test.py delete mode 100644 src/gui/utils/__init__.py delete mode 100644 src/gui/utils/behaviors.py delete mode 100644 src/gui/utils/fonts.py delete mode 100644 src/gui/utils/forms.py delete mode 100644 src/gui/utils/layouts.py create mode 100644 src/render/__init__.py create mode 100644 src/render/render_queue.py create mode 100644 src/render/setup.py create mode 100644 src/startup.py create mode 100644 src/utils/asynchronic.py create mode 100644 src/utils/data_structures.py create mode 100644 src/utils/images.py create mode 100644 src/utils/lazy.py create mode 100644 src/utils/logging.py create mode 100644 src/utils/tests.py diff --git a/.gitignore b/.gitignore index 18ae9ba8..e3d046f7 100644 --- a/.gitignore +++ b/.gitignore @@ -151,7 +151,11 @@ cython_debug/ # VSCode .vscode/ -# SPECIFIC TO THIS PROJECT +# Node modules +node_modules/ + +## SPECIFIC TO THIS PROJECT + # Art input folder /art # Render output folder @@ -166,6 +170,8 @@ cython_debug/ /src/data/versions.yml # User defined environment /src/data/env.yml +# User preferences +/src/data/preferences # Generated new release /release # Environmental variables @@ -175,6 +181,7 @@ cython_debug/ !/plugins/Investigamer !/plugins/SilvanMTG /plugins/*/config_ini +/plugins/*/__init__.py # PSD templates directories /templates/* !/templates/tools @@ -191,3 +198,12 @@ cython_debug/ /src/data/hexproof # Allow the internal build tools !src/data/build/ +# Backups +/backup +# Built executable +proxyshop.exe +# Autogenerated Qt files +src/gui/qml/models/*.json +src/gui/qml/models/*.cpp +src/gui/qml/models/*.qmltypes +src/gui/qml/models/qmldir diff --git a/main.py b/main.py index bd57ccf4..e781c371 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,31 @@ """ * Proxyshop Application Launcher """ -# Standard Library Imports + import os import sys +from pathlib import Path -""" -* Launcher Funcs -""" +from PySide6.QtGui import QIcon + +from src import APP, CFG, CON, ENV +from src._loader import ( + AppPlugin, + TemplateLibrary, + get_all_plugins, + get_template_file_versions, +) +from src.startup import run_startup_checks def launch_cli(): """Launch the app in CLI mode.""" # Enable headless mode - os.environ['PROXYSHOP_HEADLESS'] = '1' + os.environ["PROXYSHOP_HEADLESS"] = "1" # Remove cli marker - if 'cli' in sys.argv: - sys.argv.remove('cli') + if "cli" in sys.argv: + sys.argv.remove("cli") # Local Imports from src.commands import ProxyshopCLI @@ -26,51 +34,117 @@ def launch_cli(): ProxyshopCLI.main() -def launch_gui(): +def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]): """Launch the app in GUI mode.""" - # Disable headless mode - os.environ['PROXYSHOP_HEADLESS'] = '0' - - # Local Imports - from src import ( - APP, CFG, CON, CONSOLE, ENV, - PLUGINS, TEMPLATES, TEMPLATE_MAP, TEMPLATE_DEFAULTS) - from src.gui._state import register_kv_classes, load_kv_config - from src.gui.app import ProxyshopGUIApp - from src.gui.console import GUIConsole - - if isinstance(CONSOLE, GUIConsole): - # Start Photoshop in the background - APP.initialize() - - # Kivy Imported Last - from kivy.resources import resource_add_path - - # Kivy packaging for PyInstaller - if hasattr(sys, '_MEIPASS'): - resource_add_path(os.path.join(sys._MEIPASS)) - - # Load KV files and utilities - load_kv_config() - register_kv_classes() - - # Run the GUI application - ProxyshopGUIApp( - app=APP, - con=CON, - cfg=CFG, - env=ENV, - console=CONSOLE, - plugins=PLUGINS, - templates=TEMPLATES, - template_map=TEMPLATE_MAP, - templates_default=TEMPLATE_DEFAULTS, - ).run() - - -if __name__ == '__main__': - """Route to a qualified launcher.""" - if 'cli' in sys.argv: + from PySide6 import QtAsyncio + from PySide6.QtGui import QGuiApplication + from PySide6.QtQml import QQmlApplicationEngine + from PySide6.QtQuickControls2 import QQuickStyle + + from src.gui.qml.models.batch_rendering_model import BatchRenderingModel + from src.gui.qml.models.console_model import ConsoleModel + from src.gui.qml.models.file_dialog_model import FileDialogModel + from src.gui.qml.models.file_path_model import FilePathModel + from src.gui.qml.models.image_transform_model import ImageTransformModel + from src.gui.qml.models.message_dialog_content_model import ( + MessageDialogContentModel, + ) + from src.gui.qml.models.render_operations_model import RenderOperationsModel + from src.gui.qml.models.settings_model import SettingsModel + from src.gui.qml.models.settings_tree_model import SettingsTreeModel + from src.gui.qml.models.template_list_model import TemplateListModel + from src.gui.qml.models.template_updater_model import TemplateUpdaterModel + from src.gui.qml.models.test_renders_model import TestRendersModel + from src.render.render_queue import RenderQueue + + app = QGuiApplication(applicationDisplayName="Proxyshop") + app.setWindowIcon(QIcon("src/img/favicon.ico")) + engine = QQmlApplicationEngine() + + file_path_model = FilePathModel() + render_queue = RenderQueue() + file_dialog_model = FileDialogModel() + console_model = ConsoleModel() + render_message_dialog_content_model = MessageDialogContentModel() + render_operations_model = RenderOperationsModel( + render_queue, render_message_dialog_content_model + ) + template_updater_model = TemplateUpdaterModel( + app_env=ENV, template_library=template_library + ) + test_renders_model = TestRendersModel( + render_queue, + template_library, + file_dialog_model, + render_message_dialog_content_model, + ) + template_list_model = TemplateListModel( + render_queue, + file_dialog_model, + render_message_dialog_content_model, + template_library, + test_renders_model, + ) + batch_render_model = BatchRenderingModel( + file_dialog_model, + render_message_dialog_content_model, + render_queue, + plugins, + template_library, + test_renders_model, + ) + settings_tree_model = SettingsTreeModel( + app_config=CFG, template_library=template_library + ) + settings_model = SettingsModel(settings_tree_model) + image_transform_model = ImageTransformModel(file_dialog_model=file_dialog_model) + + root_context = engine.rootContext() + root_context.setContextProperty("filePathModel", file_path_model) + root_context.setContextProperty("fileDialogModel", file_dialog_model) + root_context.setContextProperty("consoleModel", console_model) + root_context.setContextProperty( + "renderMessageDialogContentModel", render_message_dialog_content_model + ) + root_context.setContextProperty("renderOperationsModel", render_operations_model) + root_context.setContextProperty("testRendersModel", test_renders_model) + root_context.setContextProperty("templateUpdaterModel", template_updater_model) + root_context.setContextProperty("templateListModel", template_list_model) + root_context.setContextProperty("batchRenderModel", batch_render_model) + root_context.setContextProperty("settingsTreeModel", settings_tree_model) + root_context.setContextProperty("settingsModel", settings_model) + root_context.setContextProperty("imageTransformModel", image_transform_model) + root_context.setContextProperty( + "github_url", f"https://github.com/{ENV.APP_UPDATES_REPO}" + ) + + # This points to the extracted bundle directory in a PyInstaller build + engine.addImportPath(Path(__file__).parent / "src" / "gui") + QQuickStyle.setStyle("Fusion") + QQuickStyle.setFallbackStyle("Basic") + engine.loadFromModule("qml", "App") + + # Ensure that the root context is available at QML destruction + app.aboutToQuit.connect(engine.deleteLater) + + run_startup_checks(APP) + + QtAsyncio.run(handle_sigint=True) + + +if __name__ == "__main__": + initial_versions = get_template_file_versions() + versions = initial_versions.model_copy(deep=True) + plugins = get_all_plugins(con=CON, env=ENV, template_file_versions=versions.root) + template_library = TemplateLibrary( + con=CON, + env=ENV, + plugins=plugins, + initial_template_file_versions=initial_versions, + template_file_versions=versions, + ) + + if "cli" in sys.argv: sys.exit(launch_cli()) - launch_gui() + launch_gui(template_library, plugins) diff --git a/plugins/Investigamer/manifest.yml b/plugins/Investigamer/manifest.yml index 8a78b38f..5b97199c 100644 --- a/plugins/Investigamer/manifest.yml +++ b/plugins/Investigamer/manifest.yml @@ -3,54 +3,69 @@ ### PLUGIN: - name: 'Investigamer' - author: 'Investigamer' + name: "Investigamer" + author: "Investigamer" desc: "Investigamer's official Proxyshop plugin." - license: 'MPL-2.0' - requires: '^1.13.0' - version: '1.0.0' + license: "MPL-2.0" + requires: "^1.13.0" + version: "1.0.0" ### # * Templates ### sketch.psd: - name: 'Sketch Showcase' - id: '1uFfDl5VUYiu77ct6rsDPBRJK0-9ap8Zb' - desc: 'Based on the Sketch showcase frame introduced in Modern Horizons.' - templates: { Sketch: SketchTemplate } + name: "Sketch Showcase" + id: "1uFfDl5VUYiu77ct6rsDPBRJK0-9ap8Zb" + desc: "Based on the Sketch showcase frame introduced in Modern Horizons." + templates: + Sketch: + SketchTemplate: + - normal kaldheim.psd: - name: 'Kaldheim Showcase' - id: '16XtBeZT_teePRDvythLu8JvHKjSND5TW' - desc: 'Based on the Legendary Viking showcase frame introduced in Kaldheim.' - templates: { Kaldheim: KaldheimTemplate } + name: "Kaldheim Showcase" + id: "16XtBeZT_teePRDvythLu8JvHKjSND5TW" + desc: "Based on the Legendary Viking showcase frame introduced in Kaldheim." + templates: + Kaldheim: + KaldheimTemplate: + - normal phyrexian.psd: - name: 'Phyrexian Showcase' - id: '1mEb4BXZI1v0dqwGOwgmrHFOaytVoJlkQ' - desc: 'Based on the Phyrexian showcase Secret Lair.' - templates: { Phyrexian: PhyrexianTemplate } + name: "Phyrexian Showcase" + id: "1mEb4BXZI1v0dqwGOwgmrHFOaytVoJlkQ" + desc: "Based on the Phyrexian showcase Secret Lair." + templates: + Phyrexian: + PhyrexianTemplate: + - normal colorshifted.psd: - name: 'Colorshifted' - id: '14-DkMK2PEoU4aNHWPxsoau_t_xAKhgb7' - desc: 'Based on the Colorshifted frame introduced in Planar Chaos.' - templates: { Colorshifted: ColorshiftedTemplate } + name: "Colorshifted" + id: "14-DkMK2PEoU4aNHWPxsoau_t_xAKhgb7" + desc: "Based on the Colorshifted frame introduced in Planar Chaos." + templates: + Colorshifted: + ColorshiftedTemplate: + - normal double-feature.psd: - name: 'Innistrad: Double Feature Showcase' - id: '1D88pzyudnGnUglWL7H-K5AlWjLPcaHfE' + name: "Innistrad: Double Feature Showcase" + id: "1D88pzyudnGnUglWL7H-K5AlWjLPcaHfE" desc: 'Based on the showcase frame used for the "Double Feature" promo set.' - templates: { Double Feature: DoubleFeatureTemplate } + templates: + Double Feature: + DoubleFeatureTemplate: + - normal crimson-fang.psd: - name: 'Crimson Vow Showcase' - id: '1scPZCVzKezjrjfvGso3IejJgFBYDMvfn' - desc: 'Based on the Crimson Fang showcase frame introduced in Crimson Vow.' + name: "Crimson Vow Showcase" + id: "1scPZCVzKezjrjfvGso3IejJgFBYDMvfn" + desc: "Based on the Crimson Fang showcase frame introduced in Crimson Vow." templates: Crimson Fang: CrimsonFangTemplate: - - transform_front - - transform_back - - normal + - transform_front + - transform_back + - normal diff --git a/plugins/Investigamer/py/actions/pencilsketch.py b/plugins/Investigamer/py/actions/pencilsketch.py index 179d3f89..810c9b36 100644 --- a/plugins/Investigamer/py/actions/pencilsketch.py +++ b/plugins/Investigamer/py/actions/pencilsketch.py @@ -2,17 +2,13 @@ Pencil Sketchify Action Module """ -# Standard Library Imports -from threading import Event from collections.abc import Iterable -# Third Party Imports import photoshop.api as ps -# Local Imports -from src import APP, CONSOLE +from src import APP +from src.render.setup import RenderOperation -# QOL Definitions dialog_mode = ps.DialogModes.DisplayNoDialogs """ @@ -243,7 +239,7 @@ def blend_color(): def run( - thr: Event, + render_operation: RenderOperation, draft_sketch: bool = False, rough_sketch: bool = False, black_and_white: bool = True, @@ -1392,5 +1388,7 @@ def step343(): select_bg() # Flatten if manual_editing: - CONSOLE.await_choice(thr, "Sketch Action complete, hit continue when ready!") + render_operation.pause_sync( + "Sketch Action complete." + ) APP.instance.executeAction(APP.instance.cID("FltI"), None, dialog_mode) diff --git a/plugins/Investigamer/py/templates.py b/plugins/Investigamer/py/templates.py index a0677768..87d387fd 100644 --- a/plugins/Investigamer/py/templates.py +++ b/plugins/Investigamer/py/templates.py @@ -2,20 +2,16 @@ * Plugin: Investigamer """ -# Standard Library from collections.abc import Callable from functools import cached_property -# Third Party from photoshop.api._artlayer import ArtLayer -# Local Imports import src.helpers as psd -from src import CFG, ENV +from src import ENV from src.enums.layers import LAYERS from src.templates import ExtendedMod, NormalTemplate, TransformMod -# Plugin Imports from .actions import pencilsketch, sketch """ @@ -40,7 +36,7 @@ def art_action(self) -> Callable[[], None] | None: # Skip action if in test mode if ENV.TEST_MODE: return - action = CFG.get_setting( + action = self.config.get_setting( section="ACTION", key="Sketch.Action", default="Advanced Sketch", @@ -48,17 +44,17 @@ def art_action(self) -> Callable[[], None] | None: ) if action == "Advanced Sketch": return lambda: pencilsketch.run( - self.event, - draft_sketch=CFG.get_bool_setting( + self.render_operation, + draft_sketch=self.config.get_bool_setting( section="ACTION", key="Draft.Sketch.Lines", default=False ), - rough_sketch=CFG.get_bool_setting( + rough_sketch=self.config.get_bool_setting( section="ACTION", key="Rough.Sketch.Lines", default=False ), - black_and_white=CFG.get_bool_setting( + black_and_white=self.config.get_bool_setting( section="ACTION", key="Black.And.White", default=False ), - manual_editing=CFG.get_bool_setting( + manual_editing=self.config.get_bool_setting( section="ACTION", key="Sketch.Manual.Editing", default=False ), ) diff --git a/plugins/SilvanMTG/manifest.yml b/plugins/SilvanMTG/manifest.yml index b7a6ebcc..45abeee7 100644 --- a/plugins/SilvanMTG/manifest.yml +++ b/plugins/SilvanMTG/manifest.yml @@ -3,37 +3,40 @@ ### PLUGIN: - name: 'SilvanMTG' - author: 'SilvanMTG' + name: "SilvanMTG" + author: "SilvanMTG" desc: "A plugin for SilvanMTG's legendary extended templates." - license: 'MPL-2.0' - requires: '^1.13.0' - version: '1.0.0' + license: "MPL-2.0" + requires: "^1.13.0" + version: "1.0.0" ### # * Templates ### extended.psd: - name: 'Silvan Extended' - id: '1RloUNFCkOJE1QUwJ0YDtfysCDBYlY9kn' + name: "Silvan Extended" + id: "1RloUNFCkOJE1QUwJ0YDtfysCDBYlY9kn" desc: "Silvan's original Extended template." - templates: { Silvan Extended: SilvanExtendedTemplate } + templates: + Silvan Extended: + SilvanExtendedTemplate: + - normal extended-mdfc-front.psd: - name: 'Silvan Extended (MDFC Front)' - id: '17ONVdUMGAv-lj0od_iNjgWT7mmW9725M' + name: "Silvan Extended (MDFC Front)" + id: "17ONVdUMGAv-lj0od_iNjgWT7mmW9725M" desc: "Silvan's original Extended template, in MDFC (Front) treatment." templates: Silvan Extended: SilvanMDFCTemplate: - - mdfc_front + - mdfc_front extended-mdfc-back.psd: - name: 'Silvan Extended (MDFC Back)' - id: '10sx3CLO7det1oQl3gfuBoD016bHkPQEa' + name: "Silvan Extended (MDFC Back)" + id: "10sx3CLO7det1oQl3gfuBoD016bHkPQEa" desc: "Silvan's original Extended template, in MDFC (Back) treatment." templates: Silvan Extended: SilvanMDFCTemplate: - - mdfc_back + - mdfc_back diff --git a/poetry.lock b/poetry.lock index 334fdadb..42c271a9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,51 +1,15 @@ # This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. -[[package]] -name = "aggdraw" -version = "1.3.19" -description = "High quality drawing interface for PIL." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "aggdraw-1.3.19-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:cfbb072ee0a6de6cb6a782724df6e8ab9648d9566fb033118dcebbe9dc06326e"}, - {file = "aggdraw-1.3.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:789fc905d0bad232fe4d783ee115ed34a1c0904957dc384fc5c45a40df517975"}, - {file = "aggdraw-1.3.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30d783a7bfb2ff2ac43ce080ff18fb5df8b96ad4c0988c88324037855310e665"}, - {file = "aggdraw-1.3.19-cp310-cp310-win_amd64.whl", hash = "sha256:4483393bc69392287d1c8159a0c475c51ce2f687b1099d686a3c5b2028e25b0f"}, - {file = "aggdraw-1.3.19-cp310-cp310-win_arm64.whl", hash = "sha256:67f28103a619ebf7068421dfee7bd3d6cf2528887bfb5d16ecf952da10fd6f93"}, - {file = "aggdraw-1.3.19-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a9b6be919955e9ace24a22ca1172786902254db22f6789a0bb8ce08bfb86603f"}, - {file = "aggdraw-1.3.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c350f9403749e2fa33525bf9116a154ea5c7fddbb9fe045448b7700b62d882d"}, - {file = "aggdraw-1.3.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5c47a5a4c9d1e0902b90b8b0032db55c8dc041dfc5005ff02749d8e9c0aa622"}, - {file = "aggdraw-1.3.19-cp311-cp311-win_amd64.whl", hash = "sha256:b3bace3619f78891dbbeb1273409ea35d104880e5c22b0435808508c4c2f6a0e"}, - {file = "aggdraw-1.3.19-cp311-cp311-win_arm64.whl", hash = "sha256:178784eb812dacfe3069fcbe4f4ef11877130803141b8f41b89535fbec0d0759"}, - {file = "aggdraw-1.3.19-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e68afe82d0bbd111b2d6d7f41ac8cf13a010cd65ad095117c2e9fd8252710a73"}, - {file = "aggdraw-1.3.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f3ff2595490b3760658a28afdca5111ed787bd1fccdab5c81d0ea57e0fe5a8c"}, - {file = "aggdraw-1.3.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56bbcc8d4f64e60ed815419837b9d2ba579169d92c4fe19e4caa3679aaedb6aa"}, - {file = "aggdraw-1.3.19-cp312-cp312-win_amd64.whl", hash = "sha256:6a39bdf2f1043ca4148a926e35feaa1d4d592c25c8b225d8d0ad945415bab7cc"}, - {file = "aggdraw-1.3.19-cp312-cp312-win_arm64.whl", hash = "sha256:21f951129b3014b3cf678b250ba5e4345d93243602b4062804e6111319d0eaf3"}, - {file = "aggdraw-1.3.19-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f7cb8141620ec4cb3be55601115126d9a1b084ef5bdea0bb510007efa42e8cfe"}, - {file = "aggdraw-1.3.19-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aecacc4f6d13b8343ac07624576bab4dadd4528f0c47f54b2fae0656fc913670"}, - {file = "aggdraw-1.3.19-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:981210dbcbb649d80763a8ae32b845ff517103718c6bb0a95bae5b9d5d1c8cd2"}, - {file = "aggdraw-1.3.19-cp313-cp313-win_amd64.whl", hash = "sha256:41e09faf469cae11339f5d1b0680dba7ec02502efebc10086f1a142a8a39afb8"}, - {file = "aggdraw-1.3.19-cp313-cp313-win_arm64.whl", hash = "sha256:e5552ac136225692a774d481a9baa0736c68739f64b7a7ec9f4cc5f69c8a789a"}, - {file = "aggdraw-1.3.19-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:e49048918017c62ba7069d781b351d4cbe8b188c76fe9104a923e57ddc95d5d0"}, - {file = "aggdraw-1.3.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a91cb1fdf32f7e5611212987901400297098c6aa403fd59bb3221ce4608663"}, - {file = "aggdraw-1.3.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f79e9504e5fead1881d0bc3b763b76fb17f649ef263cfc1d63a10a068e904f3"}, - {file = "aggdraw-1.3.19-cp39-cp39-win_amd64.whl", hash = "sha256:868c10d59daf9c6f625c4a206f384c637fdafe527c56c081bb0e567fcef17b55"}, - {file = "aggdraw-1.3.19-cp39-cp39-win_arm64.whl", hash = "sha256:f0a4fd88d737719071cbfb1eba7520ca6e64c118b71b72525944b8d7bb20327c"}, - {file = "aggdraw-1.3.19.tar.gz", hash = "sha256:e78a23b29fb66a079832bae5604f082bfa4ff9d5d469c77506a67253d7fee7db"}, -] - [[package]] name = "altgraph" -version = "0.17.4" +version = "0.17.5" description = "Python graph (network) package" optional = false python-versions = "*" groups = ["dev"] files = [ - {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, - {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, + {file = "altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597"}, + {file = "altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7"}, ] [[package]] @@ -75,33 +39,6 @@ files = [ [package.extras] test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] -[[package]] -name = "asyncgui" -version = "0.9.3" -description = "A minimalistic async library that focuses on fast responsiveness" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "asyncgui-0.9.3-py3-none-any.whl", hash = "sha256:ed73fc00c68d7e0d804eb239f2ad9c319dce57a5d31342c19aadc18f44270103"}, - {file = "asyncgui-0.9.3.tar.gz", hash = "sha256:46b283946a1814f600b502ee48082be9681097a72d8d011305e453d683a9064b"}, -] - -[[package]] -name = "asynckivy" -version = "0.9.0" -description = "Async library for Kivy" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "asynckivy-0.9.0-py3-none-any.whl", hash = "sha256:cfc9e3f606dfb95fcd9611ee7be69e1da3ce6974679fbb61a6250e3a43ae3d0c"}, - {file = "asynckivy-0.9.0.tar.gz", hash = "sha256:6358dbe49bffa3be8507395a256789e41e234cda8df5baa39747d66aae1f92e6"}, -] - -[package.dependencies] -asyncgui = ">=0.9.3,<0.10" - [[package]] name = "attrs" version = "25.4.0" @@ -116,14 +53,14 @@ files = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, + {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, + {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, ] [package.extras] @@ -143,19 +80,19 @@ files = [ [[package]] name = "backrefs" -version = "5.9" +version = "6.2" description = "A wrapper around re and regex that adds additional back references." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, - {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, - {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, - {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, - {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, - {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, - {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, + {file = "backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8"}, + {file = "backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be"}, + {file = "backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90"}, + {file = "backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b"}, + {file = "backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7"}, + {file = "backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7"}, + {file = "backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49"}, ] [package.extras] @@ -163,18 +100,18 @@ extras = ["regex"] [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, + {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, + {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, ] [package.dependencies] -soupsieve = ">1.2" +soupsieve = ">=1.6.1" typing-extensions = ">=4.0.0" [package.extras] @@ -198,180 +135,138 @@ files = [ [[package]] name = "brotli" -version = "1.1.0" +version = "1.2.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" groups = ["main"] markers = "platform_python_implementation == \"CPython\"" files = [ - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"}, - {file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, - {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, - {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, - {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, - {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, - {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, - {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, - {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, - {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, - {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, - {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, - {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, - {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, - {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, - {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, - {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, - {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, - {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, + {file = "brotli-1.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:99cfa69813d79492f0e5d52a20fd18395bc82e671d5d40bd5a91d13e75e468e8"}, + {file = "brotli-1.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3ebe801e0f4e56d17cd386ca6600573e3706ce1845376307f5d2cbd32149b69a"}, + {file = "brotli-1.2.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a387225a67f619bf16bd504c37655930f910eb03675730fc2ad69d3d8b5e7e92"}, + {file = "brotli-1.2.0-cp27-cp27m-win32.whl", hash = "sha256:b908d1a7b28bc72dfb743be0d4d3f8931f8309f810af66c906ae6cd4127c93cb"}, + {file = "brotli-1.2.0-cp27-cp27m-win_amd64.whl", hash = "sha256:d206a36b4140fbb5373bf1eb73fb9de589bb06afd0d22376de23c5e91d0ab35f"}, + {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7e9053f5fb4e0dfab89243079b3e217f2aea4085e4d58c5c06115fc34823707f"}, + {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4735a10f738cb5516905a121f32b24ce196ab82cfc1e4ba2e3ad1b371085fd46"}, + {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e"}, + {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1"}, + {file = "brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997"}, + {file = "brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196"}, + {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744"}, + {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae"}, + {file = "brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03"}, + {file = "brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24"}, + {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84"}, + {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036"}, + {file = "brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161"}, + {file = "brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44"}, + {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab"}, + {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5"}, + {file = "brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a"}, + {file = "brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8"}, + {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21"}, + {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888"}, + {file = "brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d"}, + {file = "brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3"}, + {file = "brotli-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82676c2781ecf0ab23833796062786db04648b7aae8be139f6b8065e5e7b1518"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c16ab1ef7bb55651f5836e8e62db1f711d55b82ea08c3b8083ff037157171a69"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e85190da223337a6b7431d92c799fca3e2982abd44e7b8dec69938dcc81c8e9e"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d8c05b1dfb61af28ef37624385b0029df902ca896a639881f594060b30ffc9a7"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:465a0d012b3d3e4f1d6146ea019b5c11e3e87f03d1676da1cc3833462e672fb0"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:96fbe82a58cdb2f872fa5d87dedc8477a12993626c446de794ea025bbda625ea"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:1b71754d5b6eda54d16fbbed7fce2d8bc6c052a1b91a35c320247946ee103502"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:66c02c187ad250513c2f4fce973ef402d22f80e0adce734ee4e4efd657b6cb64"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:ba76177fd318ab7b3b9bf6522be5e84c2ae798754b6cc028665490f6e66b5533"}, + {file = "brotli-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:c1702888c9f3383cc2f09eb3e88b8babf5965a54afb79649458ec7c3c7a63e96"}, + {file = "brotli-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f8d635cafbbb0c61327f942df2e3f474dde1cff16c3cd0580564774eaba1ee13"}, + {file = "brotli-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e80a28f2b150774844c8b454dd288be90d76ba6109670fe33d7ff54d96eb5cb8"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b1b799f45da91292ffaa21a473ab3a3054fa78560e8ff67082a185274431c8"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b7e6716ee4ea0c59e3b241f682204105f7da084d6254ec61886508efeb43bc"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640fe199048f24c474ec6f3eae67c48d286de12911110437a36a87d7c89573a6"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:92edab1e2fd6cd5ca605f57d4545b6599ced5dea0fd90b2bcdf8b247a12bd190"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7274942e69b17f9cef76691bcf38f2b2d4c8a5f5dba6ec10958363dcb3308a0a"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:a56ef534b66a749759ebd091c19c03ef81eb8cd96f0d1d16b59127eaf1b97a12"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5732eff8973dd995549a18ecbd8acd692ac611c5c0bb3f59fa3541ae27b33be3"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:598e88c736f63a0efec8363f9eb34e5b5536b7b6b1821e401afcb501d881f59a"}, + {file = "brotli-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:7ad8cec81f34edf44a1c6a7edf28e7b7806dfb8886e371d95dcf789ccd4e4982"}, + {file = "brotli-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:865cedc7c7c303df5fad14a57bc5db1d4f4f9b2b4d0a7523ddd206f00c121a16"}, + {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ac27a70bda257ae3f380ec8310b0a06680236bea547756c277b5dfe55a2452a8"}, + {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e813da3d2d865e9793ef681d3a6b66fa4b7c19244a45b817d0cceda67e615990"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fe11467c42c133f38d42289d0861b6b4f9da31e8087ca2c0d7ebb4543625526"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c0d6770111d1879881432f81c369de5cde6e9467be7c682a983747ec800544e2"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:eda5a6d042c698e28bda2507a89b16555b9aa954ef1d750e1c20473481aff675"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3173e1e57cebb6d1de186e46b5680afbd82fd4301d7b2465beebe83ed317066d"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:71a66c1c9be66595d628467401d5976158c97888c2c9379c034e1e2312c5b4f5"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1e68cdf321ad05797ee41d1d09169e09d40fdf51a725bb148bff892ce04583d7"}, + {file = "brotli-1.2.0-cp38-cp38-win32.whl", hash = "sha256:f16dace5e4d3596eaeb8af334b4d2c820d34b8278da633ce4a00020b2eac981c"}, + {file = "brotli-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:14ef29fc5f310d34fc7696426071067462c9292ed98b5ff5a27ac70a200e5470"}, + {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8d4f47f284bdd28629481c97b5f29ad67544fa258d9091a6ed1fda47c7347cd1"}, + {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2881416badd2a88a7a14d981c103a52a23a276a553a8aacc1346c2ff47c8dc17"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d39b54b968f4b49b5e845758e202b1035f948b0561ff5e6385e855c96625971"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95db242754c21a88a79e01504912e537808504465974ebb92931cfca2510469e"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bba6e7e6cfe1e6cb6eb0b7c2736a6059461de1fa2c0ad26cf845de6c078d16c8"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:88ef7d55b7bcf3331572634c3fd0ed327d237ceb9be6066810d39020a3ebac7a"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7fa18d65a213abcfbb2f6cafbb4c58863a8bd6f2103d65203c520ac117d1944b"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09ac247501d1909e9ee47d309be760c89c990defbb2e0240845c892ea5ff0de4"}, + {file = "brotli-1.2.0-cp39-cp39-win32.whl", hash = "sha256:c25332657dee6052ca470626f18349fc1fe8855a56218e19bd7a8c6ad4952c49"}, + {file = "brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937"}, + {file = "brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a"}, ] [[package]] name = "brotlicffi" -version = "1.1.0.0" +version = "1.2.0.0" description = "Python CFFI bindings to the Brotli library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation == \"PyPy\"" files = [ - {file = "brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb"}, - {file = "brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35"}, - {file = "brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:246f1d1a90279bb6069de3de8d75a8856e073b8ff0b09dcca18ccc14cec85979"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4bc5d82bc56ebd8b514fb8350cfac4627d6b0743382e46d033976a5f80fab6"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c26ecb14386a44b118ce36e546ce307f4810bc9598a6e6cb4f7fca725ae7e6"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca72968ae4eaf6470498d5c2887073f7efe3b1e7d7ec8be11a06a79cc810e990"}, - {file = "brotlicffi-1.1.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:add0de5b9ad9e9aa293c3aa4e9deb2b61e99ad6c1634e01d01d98c03e6a354cc"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9b6068e0f3769992d6b622a1cd2e7835eae3cf8d9da123d7f51ca9c1e9c333e5"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8557a8559509b61e65083f8782329188a250102372576093c88930c875a69838"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a7ae37e5d79c5bdfb5b4b99f2715a6035e6c5bf538c3746abc8e26694f92f33"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391151ec86bb1c683835980f4816272a87eaddc46bb91cbf44f62228b84d8cca"}, - {file = "brotlicffi-1.1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f3711be9290f0453de8eed5275d93d286abe26b08ab4a35d7452caa1fef532f"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a807d760763e398bbf2c6394ae9da5815901aa93ee0a37bca5efe78d4ee3171"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa8ca0623b26c94fccc3a1fdd895be1743b838f3917300506d04aa3346fd2a14"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de0cf28a53a3238b252aca9fed1593e9d36c1d116748013339f0949bfc84112"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6be5ec0e88a4925c91f3dea2bb0013b3a2accda6f77238f76a34a1ea532a1cb0"}, - {file = "brotlicffi-1.1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d9eb71bb1085d996244439154387266fd23d6ad37161f6f52f1cd41dd95a3808"}, - {file = "brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13"}, + {file = "brotlicffi-1.2.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9458d08a7ccde8e3c0afedbf2c70a8263227a68dea5ab13590593f4c0a4fd5f4"}, + {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e3d0020cf1bd8b8131f4a07819edee9f283721566fe044a20ec792ca8fd8b7"}, + {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33cfb408d0cff64cd50bef268c0fed397c46fbb53944aa37264148614a62e990"}, + {file = "brotlicffi-1.2.0.0-cp38-abi3-win32.whl", hash = "sha256:23e5c912fdc6fd37143203820230374d24babd078fc054e18070a647118158f6"}, + {file = "brotlicffi-1.2.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:f139a7cdfe4ae7859513067b736eb44d19fae1186f9e99370092f6915216451b"}, + {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fa102a60e50ddbd08de86a63431a722ea216d9bc903b000bf544149cc9b823dc"}, + {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d3c4332fc808a94e8c1035950a10d04b681b03ab585ce897ae2a360d479037c"}, + {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb4eb5830026b79a93bf503ad32b2c5257315e9ffc49e76b2715cffd07c8e3db"}, + {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3832c66e00d6d82087f20a972b2fc03e21cd99ef22705225a6f8f418a9158ecc"}, + {file = "brotlicffi-1.2.0.0.tar.gz", hash = "sha256:34345d8d1f9d534fcac2249e57a4c3c8801a33c9942ff9f8574f67a175e17adb"}, ] [package.dependencies] -cffi = ">=1.0.0" +cffi = {version = ">=1.17.0", markers = "python_version >= \"3.13\""} [[package]] name = "bs4" @@ -390,14 +285,14 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2025.10.5" +version = "2026.1.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, + {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, ] [[package]] @@ -500,14 +395,14 @@ pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, + {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, + {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, ] [[package]] @@ -635,14 +530,14 @@ files = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, - {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -663,14 +558,14 @@ markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "commitizen" -version = "4.9.1" +version = "4.13.7" description = "Python commitizen client tool" optional = false -python-versions = "<4.0,>=3.9" +python-versions = "<4.0,>=3.10" groups = ["dev"] files = [ - {file = "commitizen-4.9.1-py3-none-any.whl", hash = "sha256:4241b2ecae97b8109af8e587c36bc3b805a09b9a311084d159098e12d6ead497"}, - {file = "commitizen-4.9.1.tar.gz", hash = "sha256:b076b24657718f7a35b1068f2083bd39b4065d250164a1398d1dac235c51753b"}, + {file = "commitizen-4.13.7-py3-none-any.whl", hash = "sha256:7e179fa2a29626d330e38332e3f306caa3f11ac1cc0bc139f61dff167f224434"}, + {file = "commitizen-4.13.7.tar.gz", hash = "sha256:61b1872a3b5b9da69f1819a4e238249071a636ef422cf6f3735c491eb77e9526"}, ] [package.dependencies] @@ -681,22 +576,22 @@ decli = ">=0.6.0,<1.0" deprecated = ">=1.2.13,<2" jinja2 = ">=2.10.3" packaging = ">=19" -prompt_toolkit = "!=3.0.52" -pyyaml = ">=3.08" +prompt-toolkit = "!=3.0.52" +pyyaml = ">=3.8" questionary = ">=2.0,<3.0" termcolor = ">=1.1.0,<4.0.0" -tomlkit = ">=0.5.3,<1.0.0" +tomlkit = ">=0.8.0,<1.0.0" [[package]] name = "comtypes" -version = "1.4.13" +version = "1.4.15" description = "Pure Python COM package" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "comtypes-1.4.13-py3-none-any.whl", hash = "sha256:21546210748ba52e839e52112124b16ffab7d7fb68096493165fbc249e9023ad"}, - {file = "comtypes-1.4.13.zip", hash = "sha256:fc573997ae32b374891cfa8d79ebb8289809ed3a4a3f789d5348371099c7788a"}, + {file = "comtypes-1.4.15-py3-none-any.whl", hash = "sha256:cda90486de8762ec57d7ce04e68721920911f3f03415cb29afdf7609c427c7e3"}, + {file = "comtypes-1.4.15.tar.gz", hash = "sha256:c72b9968a4e920087183a364c5a13b174e02b11c302cdd92690d14c95ac1b312"}, ] [[package]] @@ -832,18 +727,18 @@ files = [ [[package]] name = "deprecated" -version = "1.2.18" +version = "1.3.1" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["main", "dev"] files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, + {file = "deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f"}, + {file = "deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223"}, ] [package.dependencies] -wrapt = ">=1.10,<2" +wrapt = ">=1.10,<3" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] @@ -860,121 +755,89 @@ files = [ {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[[package]] -name = "docutils" -version = "0.22.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8"}, - {file = "docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d"}, -] - [[package]] name = "filelock" -version = "3.20.0" +version = "3.24.2" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, - {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, -] - -[[package]] -name = "filetype" -version = "1.2.0" -description = "Infer file type and MIME type of any file/buffer. No external dependencies." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, - {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, + {file = "filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556"}, + {file = "filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b"}, ] [[package]] name = "fonttools" -version = "4.60.1" +version = "4.61.1" description = "Tools to manipulate font files" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28"}, - {file = "fonttools-4.60.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15"}, - {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c"}, - {file = "fonttools-4.60.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea"}, - {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652"}, - {file = "fonttools-4.60.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a"}, - {file = "fonttools-4.60.1-cp310-cp310-win32.whl", hash = "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce"}, - {file = "fonttools-4.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038"}, - {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f"}, - {file = "fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2"}, - {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914"}, - {file = "fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1"}, - {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d"}, - {file = "fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa"}, - {file = "fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258"}, - {file = "fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf"}, - {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc"}, - {file = "fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877"}, - {file = "fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c"}, - {file = "fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401"}, - {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903"}, - {file = "fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed"}, - {file = "fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6"}, - {file = "fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383"}, - {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb"}, - {file = "fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4"}, - {file = "fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c"}, - {file = "fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77"}, - {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199"}, - {file = "fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c"}, - {file = "fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272"}, - {file = "fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac"}, - {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3"}, - {file = "fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85"}, - {file = "fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537"}, - {file = "fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003"}, - {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08"}, - {file = "fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99"}, - {file = "fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6"}, - {file = "fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987"}, - {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299"}, - {file = "fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01"}, - {file = "fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801"}, - {file = "fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc"}, - {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc"}, - {file = "fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed"}, - {file = "fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259"}, - {file = "fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c"}, - {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2"}, - {file = "fonttools-4.60.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036"}, - {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856"}, - {file = "fonttools-4.60.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7"}, - {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854"}, - {file = "fonttools-4.60.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da"}, - {file = "fonttools-4.60.1-cp39-cp39-win32.whl", hash = "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a"}, - {file = "fonttools-4.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217"}, - {file = "fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb"}, - {file = "fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9"}, + {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24"}, + {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958"}, + {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da"}, + {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6"}, + {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1"}, + {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881"}, + {file = "fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47"}, + {file = "fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6"}, + {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09"}, + {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37"}, + {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb"}, + {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9"}, + {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87"}, + {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56"}, + {file = "fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a"}, + {file = "fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7"}, + {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e"}, + {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2"}, + {file = "fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796"}, + {file = "fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d"}, + {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8"}, + {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0"}, + {file = "fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261"}, + {file = "fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9"}, + {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c"}, + {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e"}, + {file = "fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5"}, + {file = "fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd"}, + {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3"}, + {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d"}, + {file = "fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c"}, + {file = "fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b"}, + {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd"}, + {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e"}, + {file = "fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c"}, + {file = "fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75"}, + {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063"}, + {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2"}, + {file = "fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c"}, + {file = "fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c"}, + {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa"}, + {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91"}, + {file = "fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19"}, + {file = "fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba"}, + {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7"}, + {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118"}, + {file = "fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5"}, + {file = "fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b"}, + {file = "fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371"}, + {file = "fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69"}, ] [package.extras] -all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] +repacker = ["uharfbuzz (>=0.45.0)"] symfont = ["sympy"] type1 = ["xattr ; sys_platform == \"darwin\""] -unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] [[package]] @@ -1012,14 +875,14 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.45" +version = "3.1.46" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, - {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, + {file = "gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058"}, + {file = "gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f"}, ] [package.dependencies] @@ -1027,22 +890,54 @@ gitdb = ">=4.0.1,<5" [package.extras] doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "griffe" -version = "1.14.0" +version = "2.0.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0"}, - {file = "griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13"}, + {file = "griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa"}, +] + +[package.dependencies] +griffecli = "2.0.0" +griffelib = "2.0.0" + +[package.extras] +pypi = ["griffelib[pypi] (==2.0.0)"] + +[[package]] +name = "griffecli" +version = "2.0.0" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d"}, ] [package.dependencies] colorama = ">=0.4" +griffelib = "2.0.0" + +[[package]] +name = "griffelib" +version = "2.0.0" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f"}, +] + +[package.extras] +pypi = ["pip (>=24.0)", "platformdirs (>=4.2)", "wheel (>=0.42)"] [[package]] name = "hexproof" @@ -1084,14 +979,14 @@ files = [ [[package]] name = "identify" -version = "2.6.15" +version = "2.6.16" description = "File identification library for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, - {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, + {file = "identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0"}, + {file = "identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980"}, ] [package.extras] @@ -1112,94 +1007,90 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] -[[package]] -name = "imageio" -version = "2.37.0" -description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"}, - {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"}, -] - -[package.dependencies] -numpy = "*" -pillow = ">=8.3.2" - -[package.extras] -all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"] -all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] -build = ["wheel"] -dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] -docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] -ffmpeg = ["imageio-ffmpeg", "psutil"] -fits = ["astropy"] -full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"] -gdal = ["gdal"] -itk = ["itk"] -linting = ["black", "flake8"] -pillow-heif = ["pillow-heif"] -pyav = ["av"] -rawpy = ["numpy (>2)", "rawpy"] -test = ["fsspec[github]", "pytest", "pytest-cov"] -tifffile = ["tifffile"] - [[package]] name = "inflate64" -version = "1.0.1" +version = "1.0.4" description = "deflate64 compression/decompression library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5122a188995e47a735ab969edc9129d42bbd97b993df5a3f0819b87205ce81b4"}, - {file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:975ed694c680e46a5c0bb872380a9c9da271a91f9c0646561c58e8f3714347d4"}, - {file = "inflate64-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bcaf445d9cda5f7358e0c2b78144641560f8ce9e3e4351099754c49d26a34e8"}, - {file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daede09baba24117279109b30fdf935195e91957e31b995b86f8dd01711376ee"}, - {file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df40eaaba4fb8379d5c4fa5f56cc24741c4f1a91d4aef66438207473351ceaa"}, - {file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ef90855ff63d53c8fd3bfbf85b5280b22f82b9ab2e21a7eee45b8a19d9866c42"}, - {file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5daa4566c0b009c9ab8a6bf18ce407d14f5dbbb0d3068f3a43af939a17e117a7"}, - {file = "inflate64-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:d58a360b59685561a8feacee743479a9d7cc17c8d210aa1f2ae221f2513973cb"}, - {file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31198c5f156806cee05b69b149074042b7b7d39274ff4c259b898e617294ac17"}, - {file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ab693bb1cd92573a997f8fe7b90a2ec1e17a507884598f5640656257b95ef49"}, - {file = "inflate64-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:95b6a60e305e6e759e37d6c36691fcb87678922c56b3ddc2df06cd56e04f41f6"}, - {file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:711ef889bdb3b3b296881d1e49830a3a896938fba7033c4287f1aed9b9a20111"}, - {file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3178495970ecb5c6a32167a8b57fdeef3bf4e2843eaf8f2d8f816f523741e36"}, - {file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e8373b7feedf10236eb56d21598a19a3eb51077c3702d0ce3456b827374025e1"}, - {file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf026d5c885f2d2bbf233e9a0c8c6d046ec727e2467024ffe0ac76b5be308258"}, - {file = "inflate64-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:3aa7489241e6c6f6d34b9561efdf06031c35305b864267a5b8f406abcd3e85c5"}, - {file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b81b3d373190ecd82901f42afd90b7127e9bdef341032a94db381c750ed3ddb2"}, - {file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbfddc5dac975227c20997f0ac515917a15421767c6bff0c209ac6ff9d7b17cc"}, - {file = "inflate64-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2adeabe79cc2f90bca832673520c8cbad7370f86353e151293add7ca529bed34"}, - {file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b235c97a05dbe2f92f0f057426e4d05a449e1fccf8e9aa88075ea9c6a06a182"}, - {file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b74e30734dca5f1c83ca07074e1f25bf7b63f4a5ee7e074d9a4cb05af65cd5"}, - {file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b298feb85204b5ef148ccf807744c836fffed7c1ed3ec8bc9b4e323a03163291"}, - {file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a4c75241bc442267f79b8242135f2ded29405662c44b9353d34fbd4fa6e56b3"}, - {file = "inflate64-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:7b210392f0830ab27371e36478592f47757f5ea6c09ddb96e2125847b309eb5e"}, - {file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8dd58aa1adc4f98bf9b52baffa8f2ddf589e071a90db2f2bec9024328d4608cf"}, - {file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c108be2b87e88c966570f84f839eb37f489b45dc3fa3046dc228327af6e921bb"}, - {file = "inflate64-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63971c6b096c0d533c0e38b4257f5a7748501a8bc04d00cf239bd06467888703"}, - {file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d0077edb6b1cabfa2223b71a4a725e5755148f551a7a396c7d5698e45fb8828"}, - {file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05b5f2a6f1bf2f70e9c20d997261711cbc1ae477379662b05b36911da60a67"}, - {file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c7402165f7e15789caa0787e5a349465d9a454105d0c3a0ccf2e9cdfb8117"}, - {file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39bced168822e4bf2f545d1b6dbeded6db01c32629d9e4549ef2cd1604a12e1b"}, - {file = "inflate64-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:70bb6a22d300d8ca25c26bc60afb5662c5a96d97a801962874d0461568512789"}, - {file = "inflate64-1.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f3d5ea758358a1cc50f9e8e41de2134e9b5c5ca8bbcd88d1cd135d0e953d0fa8"}, - {file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fa102c834314c3d7edbf249d1be0bce5d12a9e122228a7ac3f861ee82c3dc5c"}, - {file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2ae56a34e6cc2a712418ac82332e5d550ef8599e0ffb64c19b86d63a7df0c5"}, - {file = "inflate64-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9808ae50b5db661770992566e51e648cac286c32bd80892b151e7b1eca81afe8"}, - {file = "inflate64-1.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:04b2788c6a26e1e525f53cc3d8c58782d41f18bef8d2a34a3d58beaaf0bfdd3b"}, - {file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67fd5b1f9e433b0abab8cb91f4da94d16223a5241008268a57f4729fdbfc4dbc"}, - {file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6f3b00c17ae365e82fc3d48ff9a7a566820a6c8c55b4e16c6cfbcbd46505a72"}, - {file = "inflate64-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:91c0c1d41c1655fb0189630baaa894a3b778d77062bb90ca11db878422948395"}, - {file = "inflate64-1.0.1.tar.gz", hash = "sha256:3b1c83c22651b5942b35829df526e89602e494192bf021e0d7d0b600e76c429d"}, + {file = "inflate64-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1a47837d4322e0684824f91eb635aa6fd1967584140c478b0a1aca7b11740d6"}, + {file = "inflate64-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8600478542e2354d1ee7b5c57c957006cabacd8b787b4046951f487a2216e5c0"}, + {file = "inflate64-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb2b5a62579d074f38352a3494c3c6ac1a90516b75c5793c39303547f1fea925"}, + {file = "inflate64-1.0.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dcfafc572a642215894af1ec8d05949fa35eb7cb36d053aa97b11eccf1ae579e"}, + {file = "inflate64-1.0.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb93159cb60aee8cab62541aa70e4c460f13359660a27a1a486518bba0153535"}, + {file = "inflate64-1.0.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:89126ceb4d96e76842f4697017a9a3e750c34e029ddb360b3d8ca79a648d47f6"}, + {file = "inflate64-1.0.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f70e6692617ec82500b203eefac8765302298ce7e73584fcf995bb9e23184530"}, + {file = "inflate64-1.0.4-cp310-cp310-win32.whl", hash = "sha256:d08cdda33341b4f992af60c12dc60e370e9993b80a936c17244a602711eeb727"}, + {file = "inflate64-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:950dd7fe53474df5f4699b8f099980027e812d55fd82d8e167d599822c3d27d6"}, + {file = "inflate64-1.0.4-cp310-cp310-win_arm64.whl", hash = "sha256:bad20de249d6336793f6267880668dbb286ca5c6e6991795aa6344c817588068"}, + {file = "inflate64-1.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bccda9815b27623e805a34ee3ee4f46c93f0cc7ac621f9834d75f033fd79c27a"}, + {file = "inflate64-1.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c11e2a3cb9d9b49620c9b0c806dd0c55daec3b6bb665299b770a68f01bfc5432"}, + {file = "inflate64-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e42def03ace8c58fd50b0df4f40241c45a2314c3876d020cce24acf958323c98"}, + {file = "inflate64-1.0.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7912927a509ca58d1a445ce4ff6e6e9f276dc1d72687386cdf7103bf590e785c"}, + {file = "inflate64-1.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec40c0383cbd84d845dcb785a48ae76eef43246c923f84fda380fdd5ea653d3c"}, + {file = "inflate64-1.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b01539fea372c6078b9707d9121c12cb321e587e193f50e257ce06cf5b15e41"}, + {file = "inflate64-1.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bf4e34e32a37a42e9cf8bd9681f89e3e37b218f97d8b8cc95bd065419bc8db13"}, + {file = "inflate64-1.0.4-cp311-cp311-win32.whl", hash = "sha256:2725ccc14b138f0ad622d0322b769f177f9edfe016ee9ed3404102935d39e7de"}, + {file = "inflate64-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:7056148548c1f25dcb38251f88c19b4635a5f32af4c7bad00621c85509e3d8c0"}, + {file = "inflate64-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:2ea7bdcad65e255b4596f84880f6e0c1756d6336d620e302653257defa407742"}, + {file = "inflate64-1.0.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8009e4a4918ee6c8cbc49e58fe159464895064cfdf0565fed3f49ca81e45272"}, + {file = "inflate64-1.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d173a7a0e865bb7d19685c5b1ad2994712b8361b24136d7e94abeff58505647"}, + {file = "inflate64-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8bad992f2d034f5f7e36208e54502d1b0829ce772c898e5dc59109833420148a"}, + {file = "inflate64-1.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bfcf806912ced77a21394f7363805ecacd626b79f93cba87d505a48e88ede78"}, + {file = "inflate64-1.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62d1aac3aba094ae42e27ce7581b414c90f218248be0953b6aeb11a127225e5d"}, + {file = "inflate64-1.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8065166f355122484f004225b379d403346bdae69ec624786a9334f025580675"}, + {file = "inflate64-1.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a95f32087d223d2e119ff5c7c264109e8d4cb7e421e7a688a899a6fe021b38"}, + {file = "inflate64-1.0.4-cp312-cp312-win32.whl", hash = "sha256:ad4fa490bb7dc2a4640a3adaa2d5950f4a465ba034bbcf184c2103646e58ad97"}, + {file = "inflate64-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:2c6befdf83d088a6e0d10d0873a9d4bfde2ce00ad7a52c8189cf303306f98030"}, + {file = "inflate64-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:2b263c619469f90a75f29c421c53d31b208ad494a078235a8f6db2bc96583fdc"}, + {file = "inflate64-1.0.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3f37540d0e64884a935fd62a7d17e40ab69f05ec63e815483b6513675d01bef"}, + {file = "inflate64-1.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4d24112180c95d12f279cade9a1e21f8be7f4790c4109c293292edf87d061992"}, + {file = "inflate64-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5c098dab17821f466fc6e6a3d78fc6e0295bb51458015f03416b1d58d6a8df4f"}, + {file = "inflate64-1.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a984b9287ff0fb596eb058d66a9e94530556afd2b7c054b44f2e0aeeff894e8f"}, + {file = "inflate64-1.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62a13d0327631778fa2a47c308ae2b07b2659b7bb8564783259ac65949f8c0c"}, + {file = "inflate64-1.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:513201336fb3b0b7e2aee5dbbbe30a9f1b23291738b5ceb80076fc285f2ec2f1"}, + {file = "inflate64-1.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84ce3a97272ba745fce52b38363855c7201968f6402a794bbade774e64c657b9"}, + {file = "inflate64-1.0.4-cp313-cp313-win32.whl", hash = "sha256:332051a9d7e50579b90a3f555d68f53414b06f636c9ffe82e97c0baae3c8fbcc"}, + {file = "inflate64-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:3983f53b590ff7d0ba243f664ce852aca882482f30f7a8eab33e10d769336d0c"}, + {file = "inflate64-1.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:118d8286f085e99a14341c76ef9fbffd56619ccc80318a9a204aea3dbfa71470"}, + {file = "inflate64-1.0.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4f61925b2d4248eac2ebb15350a80aaa0d1f7f1dc770bd5ebbbb3b0db4a6a416"}, + {file = "inflate64-1.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c1acf18b08b32981a4a11ec5a112b8ad5d7c7a5b15cb5bdbdb5b19201e9aa180"}, + {file = "inflate64-1.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abddae8920b2eaef824254e14b8d4ff54afbe6194a1bbe9816584859f0c1244d"}, + {file = "inflate64-1.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b303132cc562a906543a56f35c4e164e3880da6ff041cb4a7b1df9f9d2b4bb69"}, + {file = "inflate64-1.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f0993214dea0738c557fa56c13cd9083aef0097a201d726c21984ad7f577514"}, + {file = "inflate64-1.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a6baedc3288d7a4ff588951d3a9a97a5391dceed6255ff5b16e42cae7274bfa9"}, + {file = "inflate64-1.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a846ce1f38845b20bef2625af1b512be83416d97824539524c5a34e7a729aec7"}, + {file = "inflate64-1.0.4-cp314-cp314-win32.whl", hash = "sha256:eef87908c780439393d577a155868317f0a275b47b417db9f47d8633ec791745"}, + {file = "inflate64-1.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:fb2fdd63ef3933b67af98b3f2ee2f57e7787278041d7ba4821382fedd729b68a"}, + {file = "inflate64-1.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:2e129669a0243ac7816fd526946ee01c25688fe81623a6d6bc95b3156d80f4fb"}, + {file = "inflate64-1.0.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b17bf665d948dc4edeea0cd17752415d0cd7240c882b9c7e136ad4cc4321e9d4"}, + {file = "inflate64-1.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6751758301936fbb38fa38eb5312e14e27b6a1abf568f83c17557fab2694373d"}, + {file = "inflate64-1.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d6a4136752aa2a544301059d8f13780aeb88c34d60770258436a87dacd3fc304"}, + {file = "inflate64-1.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:938ebc6b28578bfd365d1a9fdb18b7faab08321babeb2198e8025d07d8dc7fb5"}, + {file = "inflate64-1.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61f51f80fa6f367288343c1a2cd20a42af454883087064e9274fd2a8c3a5a200"}, + {file = "inflate64-1.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:172b51da7bbfa66b33f0a5405e944807b9949e92cf4cd9f983c07af8152766df"}, + {file = "inflate64-1.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ca9a2985afd5a14fb48cd126a67e5944ccb7a0a6bdec58c4f796c8c88a84539"}, + {file = "inflate64-1.0.4-cp314-cp314t-win32.whl", hash = "sha256:f8964ceaabea294bc20abc9ef408c6aae978a75c25c83168a76cd87a37c38938"}, + {file = "inflate64-1.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:7d13b04cba65c12d21e65eaa77da9484e265e8e821b26e0761d1455ad3a878d9"}, + {file = "inflate64-1.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:9ae3ee727235a06dc3cd353ee5761fdd8e3b56ad119c711f61680528972a6ced"}, + {file = "inflate64-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:179e39069c56a69c37d3b939505254cf7e495a06fcbc0c4bd5a61fa8fc43c678"}, + {file = "inflate64-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:067af498c34a8b69b2957d20dbce4d72affda23aed37ea231b1ea5ad9aab5731"}, + {file = "inflate64-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e408d3b667fb693d45448327dc8cab8c684c6627d4fd8e71819f212ab1435e81"}, + {file = "inflate64-1.0.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b5aa704141c9bad08b4e57bd722f68dd3a8cff62490b16a1605d2698e268fbd"}, + {file = "inflate64-1.0.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a55295f493a40d7e68929f0ba54e489ab9b623b6aa968dc5d1389ba77a63eff"}, + {file = "inflate64-1.0.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e107d1d6e6d9b94409a68d1ac20522815996ad44269cc05a69c93d0f1c450f95"}, + {file = "inflate64-1.0.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19ffca993315ce7a4439efc388ffebdd395a20dbea2942b2445d87ec15373737"}, + {file = "inflate64-1.0.4-cp39-cp39-win32.whl", hash = "sha256:dd8fdb7350728aa488edabeb9d2afbac5273522b50665e8dc99844a7eb99925b"}, + {file = "inflate64-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4662b2a4bba73bd64f803c97ec5134d6fd19227c3b3d3785e1babb359f1b4c33"}, + {file = "inflate64-1.0.4-cp39-cp39-win_arm64.whl", hash = "sha256:f5bb6c58c642859ecf5e23178b1e7e4724f3ce968fc49090919d59a5e186f3e6"}, + {file = "inflate64-1.0.4.tar.gz", hash = "sha256:b398c686960c029777afc0ed281a86f66adb956cfc3fbf6667cc6453f7b407ce"}, ] [package.extras] check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "flake8-isort", "mypy (>=1.10.0)", "mypy_extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] -docs = ["docutils", "sphinx (>=5.0)"] +docs = ["docutils", "sphinx (>=5.0)", "sphinx_rtd_theme"] test = ["pytest"] [[package]] @@ -1243,143 +1134,6 @@ files = [ {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"}, ] -[[package]] -name = "kivy" -version = "2.3.1" -description = "An open-source Python framework for developing GUI apps that work cross-platform, including desktop, mobile and embedded platforms." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "Kivy-2.3.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ace93c166c9400f9435cfd3bd179b5ef9fdd40d69ee8171a6b8beba08c402d09"}, - {file = "Kivy-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d6215762510b463b0461d173f8a0b22e449beb12ba79cf151e18aa1d3d12a40"}, - {file = "Kivy-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba83dd8266fc2b1247de18c5e8114fa47ea20eb33eb7c3a9e2eb6202b9778088"}, - {file = "Kivy-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:d28ad14162554abd0324ae8f66ce2f374c05456d2656d60cfa80814f715d62c0"}, - {file = "Kivy-2.3.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:acb58843763075818de919989a73657307f4d833a7cc5547c1b16c226e260e5d"}, - {file = "Kivy-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a1799b19f6ab3bcfcef1e729a0229cee646167a1633e067c2add6978f928bb"}, - {file = "Kivy-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f180280df46a8c2f9988159938aa1a3e5a0094060d9586ea79df4b4ead9cad98"}, - {file = "Kivy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:002de19fef53955c48108758beea3092cf281326642d2e71eca1c443f4227cce"}, - {file = "Kivy-2.3.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:3f74679ef305f0ed0d8bb3599a2dddc80ffc81157bdc07947498dd689fc9a5d9"}, - {file = "Kivy-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:663e9b2fe5002f53371b3ad3712dccdaaa96905bbeaa83d7c7e64f3c44fec94e"}, - {file = "Kivy-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be79fe1494b6e60cb5aa5f124c37961530417cf27a53171b5a72c9e4c7d41cf"}, - {file = "Kivy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:2046f6608d17b6c1a0530ac9aa127307fa25f6f75764f1d60428a1c0f6c0af88"}, - {file = "Kivy-2.3.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:d8d9e57501961c5d45e5a2c5af0caef24e48f43a0cd88f607eb3b517198cfec4"}, - {file = "Kivy-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfe25296e9612cbfa2b68cfb0ccd3c80db1441c11261a9e131d5f8fed7618c2c"}, - {file = "Kivy-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:950d17e275f817ca34cc7c9d55f9d229067e2f7fbd0fad985a74c94893f7e739"}, - {file = "Kivy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:b5127af11c2fc1299f2331402fe4f6edb0985711c2841fbfdf509830c058c78e"}, - {file = "Kivy-2.3.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:d9e92c4894f99685d822ab7d059a3912bbff17d812e64a12ed3cf0acd37924cb"}, - {file = "Kivy-2.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b6054afcd08fd271b75e5552a72a5ffb122b05e8511e46bf69e3b5e344d31"}, - {file = "Kivy-2.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e473b10e9b9a49a6475760fd1f7d674873852f9561505ff6b4d8e5f1691d4f9"}, - {file = "Kivy-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:ee628e5dbe5e397ceeeda7b49cf4c800b79a695c6345fee1a8f1b71d3fc530bb"}, - {file = "Kivy-2.3.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:38a265ff95120694ab7dfc29ed2ccdec40a8a47344387b886f498449b0c3c66c"}, - {file = "Kivy-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae8168549c822a7122044965715d9f953a1862fdef132ea7725df8c1d2f19e5c"}, - {file = "Kivy-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748163206ce95aab5aaad1ada772a79e422a80b6308623510e74a1b7baf80f0a"}, - {file = "Kivy-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:91c836b7c2b4958fb4b3839f63b1724435bd617548baace0602c122d39756746"}, - {file = "Kivy-2.3.1.tar.gz", hash = "sha256:0833949e3502cdb4abcf9c1da4384674045ad7d85644313aa1ee7573f3b4f9d9"}, -] - -[package.dependencies] -docutils = "*" -filetype = "*" -"kivy-deps.angle" = {version = ">=0.4.0,<0.5.0", markers = "sys_platform == \"win32\""} -"kivy-deps.glew" = {version = ">=0.3.1,<0.4.0", markers = "sys_platform == \"win32\""} -"kivy-deps.sdl2" = {version = ">=0.8.0,<0.9.0", markers = "sys_platform == \"win32\""} -Kivy-Garden = ">=0.1.4" -pygments = "*" -pypiwin32 = {version = "*", markers = "sys_platform == \"win32\""} -requests = "*" - -[package.extras] -angle = ["kivy-deps.angle (>=0.4.0,<0.5.0) ; sys_platform == \"win32\""] -base = ["pillow (>=9.5.0,<11)"] -dev = ["flake8", "kivy-deps.glew-dev (>=0.3.1,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.gstreamer-dev (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "kivy-deps.sdl2-dev (>=0.8.0,<0.9.0) ; sys_platform == \"win32\"", "pre-commit", "pyinstaller", "pytest (>=3.6)", "pytest-asyncio (!=0.11.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout", "responses", "sphinx (>=6.2.1,<6.3.0)", "sphinxcontrib-jquery (>=4.1,<5.0)"] -full = ["ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\"", "pillow (>=9.5.0,<11)"] -glew = ["kivy-deps.glew (>=0.3.1,<0.4.0) ; sys_platform == \"win32\""] -gstreamer = ["kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] -media = ["ffpyplayer ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "kivy-deps.gstreamer (>=0.3.3,<0.4.0) ; sys_platform == \"win32\""] -sdl2 = ["kivy-deps.sdl2 (>=0.8.0,<0.9.0) ; sys_platform == \"win32\""] -tuio = ["oscpy"] - -[[package]] -name = "kivy-deps-angle" -version = "0.4.0" -description = "Repackaged binary dependency of Kivy." -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "kivy_deps.angle-0.4.0-cp310-cp310-win32.whl", hash = "sha256:7873a551e488afa5044c4949a4aa42c4a4c4290469f0a6dd861e6b95283c9638"}, - {file = "kivy_deps.angle-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71f2f01a3a7bbe1d4790e2a64e64a0ea8ae154418462ea407799ed66898b2c1f"}, - {file = "kivy_deps.angle-0.4.0-cp311-cp311-win32.whl", hash = "sha256:c3899ff1f3886b80b155955bad07bfa33bbebd97718cdf46dfd788dc467124bc"}, - {file = "kivy_deps.angle-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:574381d4e66f3198bc48aa10f238e7a3816ad56b80ec939f5d56fb33a378d0b1"}, - {file = "kivy_deps.angle-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4fa7a6366899fba13f7624baf4645787165f45731db08d14557da29c12ee48f0"}, - {file = "kivy_deps.angle-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:668e670d4afd2551af0af2c627ceb0feac884bd799fb6a3dff78fdbfa2ea0451"}, - {file = "kivy_deps.angle-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9afbf702f8bb9a993c48f39c018ca3b4d2ec381a5d3f82fe65bdaa6af0bba29b"}, - {file = "kivy_deps.angle-0.4.0-cp37-cp37m-win32.whl", hash = "sha256:24cfc0076d558080a00c443c7117311b4a977c1916fe297232eff1fd6f62651e"}, - {file = "kivy_deps.angle-0.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:48592ac6f7c183c5cd10d9ebe43d4148d0b2b9e400a2b0bcb5d21014cc929ce2"}, - {file = "kivy_deps.angle-0.4.0-cp38-cp38-win32.whl", hash = "sha256:1bbacf20bf6bd6ee965388f95d937c8fba2c54916fb44faa166c2ba58276753c"}, - {file = "kivy_deps.angle-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:e2ba4e390b02ad5bcb57b43a9227fa27ff55e69cd715a87217b324195eb267c3"}, - {file = "kivy_deps.angle-0.4.0-cp39-cp39-win32.whl", hash = "sha256:6546a62aba2b7e18a800b3df79daa757af3a980c297646c986896522395794e2"}, - {file = "kivy_deps.angle-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:bfaf9b37f2ecc3e4e7736657eed507716477af35cdd3118903e999d9d567ae8c"}, -] - -[[package]] -name = "kivy-deps-glew" -version = "0.3.1" -description = "Repackaged binary dependency of Kivy." -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "kivy_deps.glew-0.3.1-cp310-cp310-win32.whl", hash = "sha256:8f4b3ed15acb62474909b6d41661ffb4da9eb502bb5684301fb2da668f288a58"}, - {file = "kivy_deps.glew-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef2d2a93f129d8425c75234e7f6cc0a34b59a4aee67f6d2cd7a5fdfa9915b53"}, - {file = "kivy_deps.glew-0.3.1-cp311-cp311-win32.whl", hash = "sha256:ee2f80ef7ac70f4b61c50da8101b024308a8c59a57f7f25a6e09762b6c48f942"}, - {file = "kivy_deps.glew-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:22e155ec59ce717387f5d8804811206d200a023ba3d0bc9bbf1393ee28d0053e"}, - {file = "kivy_deps.glew-0.3.1-cp312-cp312-win32.whl", hash = "sha256:b64ee4e445a04bc7c848c0261a6045fc2f0944cc05d7f953e3860b49f2703424"}, - {file = "kivy_deps.glew-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:3acbbd30da05fc10c185b5d4bb75fbbc882a6ef2192963050c1c94d60a6e795a"}, - {file = "kivy_deps.glew-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:f4aa8322078359862ccd9e16e5cea61976d75fb43125d87922e20c916fa31a11"}, - {file = "kivy_deps.glew-0.3.1-cp37-cp37m-win32.whl", hash = "sha256:5bf6a63fe9cc4fe7bbf280ec267ec8c47914020a1175fb22152525ff1837b436"}, - {file = "kivy_deps.glew-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d64a8625799fab7a7efeb3661ef8779a7f9c6d80da53eed87a956320f55530fa"}, - {file = "kivy_deps.glew-0.3.1-cp38-cp38-win32.whl", hash = "sha256:00f4ae0a4682d951266458ddb639451edb24baa54a35215dce889209daf19a06"}, - {file = "kivy_deps.glew-0.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f8b89dcf1846032d7a9c5ef88b0ee9cbd13366e9b4c85ada61e01549a910677"}, - {file = "kivy_deps.glew-0.3.1-cp39-cp39-win32.whl", hash = "sha256:4e377ed97670dfda619a1b63a82345a8589be90e7c616a458fba2810708810b1"}, - {file = "kivy_deps.glew-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:081a09b92f7e7817f489f8b6b31c9c9623661378de1dce1d6b097af5e7d42b45"}, -] - -[[package]] -name = "kivy-deps-sdl2" -version = "0.8.0" -description = "Repackaged binary dependency of Kivy." -optional = false -python-versions = "*" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "kivy_deps.sdl2-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5af0a3b318a6ec9e0f0c1d476a4af4b2d0cbcce4dbfd89bc4681c33bcd6b3bcd"}, - {file = "kivy_deps.sdl2-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae3735480841ec9a57c0fb26e8647adee474a3d746147e3d75a1fc177c0fbc01"}, - {file = "kivy_deps.sdl2-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:bfe0cfca77883dde7e297b3b6039fa9cd7ee8df6b0d12516b38addb0551a574c"}, - {file = "kivy_deps.sdl2-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:56b1c44565b5e8cfc510585db13396edfc605965254f49ed8931189c546d481f"}, - {file = "kivy_deps.sdl2-0.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e9f8c0c1e76eb43f0bad8f36c5b92a46fb5696f733ec441db45e6864b1d4065"}, - {file = "kivy_deps.sdl2-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dbaa6718e66e8cd4967c2d4021e05114c558342e2468a86c0bce917bea10003f"}, -] - -[[package]] -name = "kivy-garden" -version = "0.1.5" -description = "" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "Kivy Garden-0.1.5.tar.gz", hash = "sha256:2b8377378e87501d5d271f33d94f0e44c089884572c64f89c9d609b1f86a2748"}, - {file = "Kivy_Garden-0.1.5-py3-none-any.whl", hash = "sha256:ef50f44b96358cf10ac5665f27a4751bb34ef54051c54b93af891f80afe42929"}, -] - -[package.dependencies] -requests = "*" - [[package]] name = "kiwisolver" version = "1.4.9" @@ -1492,35 +1246,116 @@ files = [ ] [[package]] -name = "lazy-loader" -version = "0.4" -description = "Makes it easy to load subpackages and functions on demand." +name = "librt" +version = "0.8.0" +description = "Mypyc runtime library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] -files = [ - {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, - {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db63cf3586a24241e89ca1ce0b56baaec9d371a328bd186c529b27c914c9a1ef"}, + {file = "librt-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba9d9e60651615bc614be5e21a82cdb7b1769a029369cf4b4d861e4f19686fb6"}, + {file = "librt-0.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb4b3ad543084ed79f186741470b251b9d269cd8b03556f15a8d1a99a64b7de5"}, + {file = "librt-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d2720335020219197380ccfa5c895f079ac364b4c429e96952cd6509934d8eb"}, + {file = "librt-0.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726305d3e53419d27fc8cdfcd3f9571f0ceae22fa6b5ea1b3662c2e538f833e"}, + {file = "librt-0.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3d107f603b5ee7a79b6aa6f166551b99b32fb4a5303c4dfcb4222fc6a0335e"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41064a0c07b4cc7a81355ccc305cb097d6027002209ffca51306e65ee8293630"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c6e4c10761ddbc0d67d2f6e2753daf99908db85d8b901729bf2bf5eaa60e0567"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba581acad5ac8f33e2ff1746e8a57e001b47c6721873121bf8bbcf7ba8bd3aa4"}, + {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bdab762e2c0b48bab76f1a08acb3f4c77afd2123bedac59446aeaaeed3d086cf"}, + {file = "librt-0.8.0-cp310-cp310-win32.whl", hash = "sha256:6a3146c63220d814c4a2c7d6a1eacc8d5c14aed0ff85115c1dfea868080cd18f"}, + {file = "librt-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:bbebd2bba5c6ae02907df49150e55870fdd7440d727b6192c46b6f754723dde9"}, + {file = "librt-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369"}, + {file = "librt-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6"}, + {file = "librt-0.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa"}, + {file = "librt-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f"}, + {file = "librt-0.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef"}, + {file = "librt-0.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da"}, + {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1"}, + {file = "librt-0.8.0-cp311-cp311-win32.whl", hash = "sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258"}, + {file = "librt-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817"}, + {file = "librt-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4"}, + {file = "librt-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645"}, + {file = "librt-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467"}, + {file = "librt-0.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a"}, + {file = "librt-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45"}, + {file = "librt-0.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d"}, + {file = "librt-0.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a"}, + {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79"}, + {file = "librt-0.8.0-cp312-cp312-win32.whl", hash = "sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c"}, + {file = "librt-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8"}, + {file = "librt-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e"}, + {file = "librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1"}, + {file = "librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf"}, + {file = "librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8"}, + {file = "librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad"}, + {file = "librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01"}, + {file = "librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3"}, + {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b"}, + {file = "librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935"}, + {file = "librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab"}, + {file = "librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2"}, + {file = "librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda"}, + {file = "librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556"}, + {file = "librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06"}, + {file = "librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376"}, + {file = "librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816"}, + {file = "librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab"}, + {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3"}, + {file = "librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a"}, + {file = "librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7"}, + {file = "librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4"}, + {file = "librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb"}, + {file = "librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944"}, + {file = "librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25"}, + {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c"}, + {file = "librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447"}, + {file = "librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9"}, + {file = "librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc"}, + {file = "librt-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b705f85311ee76acec5ee70806990a51f0deb519ea0c29c1d1652d79127604d"}, + {file = "librt-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7ce0a8cb67e702dcb06342b2aaaa3da9fb0ddc670417879adfa088b44cf7b3b6"}, + {file = "librt-0.8.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:aaadec87f45a3612b6818d1db5fbfe93630669b7ee5d6bdb6427ae08a1aa2141"}, + {file = "librt-0.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56901f1eec031396f230db71c59a01d450715cbbef9856bf636726994331195d"}, + {file = "librt-0.8.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b055bb3abaf69abed25743d8fc1ab691e4f51a912ee0a6f9a6c84f4bbddb283d"}, + {file = "librt-0.8.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ef3bd856373cf8e7382402731f43bfe978a8613b4039e49e166e1e0dc590216"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e0ffe88ebb5962f8fb0ddcbaaff30f1ea06a79501069310e1e030eafb1ad787"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82e61cd1c563745ad495387c3b65806bfd453badb4adbc019df3389dddee1bf6"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:667e2513cf69bfd1e1ed9a00d6c736d5108714ec071192afb737987955888a25"}, + {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b6caff69e25d80c269b1952be8493b4d94ef745f438fa619d7931066bdd26de"}, + {file = "librt-0.8.0-cp39-cp39-win32.whl", hash = "sha256:02a9fe85410cc9bef045e7cb7fd26fdde6669e6d173f99df659aa7f6335961e9"}, + {file = "librt-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:de076eaba208d16efb5962f99539867f8e2c73480988cb513fcf1b5dbb0c9dcf"}, + {file = "librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b"}, ] -[package.dependencies] -packaging = "*" - -[package.extras] -dev = ["changelist (==0.5)"] -lint = ["pre-commit (==3.7.0)"] -test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] - [[package]] name = "limits" -version = "5.6.0" +version = "5.8.0" description = "Rate limiting utilities" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "limits-5.6.0-py3-none-any.whl", hash = "sha256:b585c2104274528536a5b68864ec3835602b3c4a802cd6aa0b07419798394021"}, - {file = "limits-5.6.0.tar.gz", hash = "sha256:807fac75755e73912e894fdd61e2838de574c5721876a19f7ab454ae1fffb4b5"}, + {file = "limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8"}, + {file = "limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da"}, ] [package.dependencies] @@ -1535,7 +1370,7 @@ async-redis = ["coredis (>=3.4.0,<6)"] async-valkey = ["valkey (>=6)"] memcached = ["pymemcache (>3,<5.0.0)"] mongodb = ["pymongo (>4.1,<5)"] -redis = ["redis (>3,!=4.5.2,!=4.5.3,<7.0.0)"] +redis = ["redis (>3,!=4.5.2,!=4.5.3,<8.0.0)"] rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] valkey = ["valkey (>=6)"] @@ -1560,15 +1395,15 @@ dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; pytho [[package]] name = "macholib" -version = "1.16.3" +version = "1.16.4" description = "Mach-O header analysis and editing" optional = false python-versions = "*" groups = ["dev"] markers = "sys_platform == \"darwin\"" files = [ - {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, - {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, + {file = "macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea"}, + {file = "macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362"}, ] [package.dependencies] @@ -1576,18 +1411,18 @@ altgraph = ">=0.17" [[package]] name = "markdown" -version = "3.9" +version = "3.10.2" description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"}, - {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"}, + {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, + {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, ] [package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] testing = ["coverage", "pyyaml"] [[package]] @@ -1715,67 +1550,67 @@ files = [ [[package]] name = "matplotlib" -version = "3.10.7" +version = "3.10.8" description = "Python plotting package" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "matplotlib-3.10.7-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380"}, - {file = "matplotlib-3.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d"}, - {file = "matplotlib-3.10.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297"}, - {file = "matplotlib-3.10.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42"}, - {file = "matplotlib-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7"}, - {file = "matplotlib-3.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3"}, - {file = "matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a"}, - {file = "matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6"}, - {file = "matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a"}, - {file = "matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1"}, - {file = "matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc"}, - {file = "matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e"}, - {file = "matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9"}, - {file = "matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748"}, - {file = "matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f"}, - {file = "matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0"}, - {file = "matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695"}, - {file = "matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65"}, - {file = "matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee"}, - {file = "matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8"}, - {file = "matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f"}, - {file = "matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c"}, - {file = "matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1"}, - {file = "matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632"}, - {file = "matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84"}, - {file = "matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815"}, - {file = "matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7"}, - {file = "matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355"}, - {file = "matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b"}, - {file = "matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67"}, - {file = "matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67"}, - {file = "matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84"}, - {file = "matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2"}, - {file = "matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf"}, - {file = "matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100"}, - {file = "matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f"}, - {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715"}, - {file = "matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1"}, - {file = "matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722"}, - {file = "matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866"}, - {file = "matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb"}, - {file = "matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1"}, - {file = "matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4"}, - {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318"}, - {file = "matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca"}, - {file = "matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc"}, - {file = "matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8"}, - {file = "matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657"}, - {file = "matplotlib-3.10.7-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68"}, - {file = "matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91"}, - {file = "matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7"}, + {file = "matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7"}, + {file = "matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656"}, + {file = "matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df"}, + {file = "matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17"}, + {file = "matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933"}, + {file = "matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a"}, + {file = "matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160"}, + {file = "matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78"}, + {file = "matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4"}, + {file = "matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2"}, + {file = "matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6"}, + {file = "matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9"}, + {file = "matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2"}, + {file = "matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a"}, + {file = "matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58"}, + {file = "matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04"}, + {file = "matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f"}, + {file = "matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466"}, + {file = "matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf"}, + {file = "matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b"}, + {file = "matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6"}, + {file = "matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1"}, + {file = "matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486"}, + {file = "matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce"}, + {file = "matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6"}, + {file = "matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149"}, + {file = "matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645"}, + {file = "matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077"}, + {file = "matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22"}, + {file = "matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39"}, + {file = "matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565"}, + {file = "matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a"}, + {file = "matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958"}, + {file = "matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5"}, + {file = "matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f"}, + {file = "matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b"}, + {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d"}, + {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008"}, + {file = "matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c"}, + {file = "matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11"}, + {file = "matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8"}, + {file = "matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50"}, + {file = "matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908"}, + {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a"}, + {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1"}, + {file = "matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c"}, + {file = "matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b"}, + {file = "matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7"}, + {file = "matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a"}, + {file = "matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2"}, + {file = "matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3"}, ] [package.dependencies] @@ -1879,14 +1714,14 @@ mkdocs = ">=1.2.3" [[package]] name = "mkdocs-autorefs" -version = "1.4.3" +version = "1.4.4" description = "Automatically link across pages in MkDocs." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9"}, - {file = "mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75"}, + {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, + {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, ] [package.dependencies] @@ -1896,18 +1731,18 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-gen-files" -version = "0.5.0" +version = "0.6.0" description = "MkDocs plugin to programmatically generate documentation pages during the build" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea"}, - {file = "mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc"}, + {file = "mkdocs_gen_files-0.6.0-py3-none-any.whl", hash = "sha256:815af15f3e2dbfda379629c1b95c02c8e6f232edf2a901186ea3b204ab1135b2"}, + {file = "mkdocs_gen_files-0.6.0.tar.gz", hash = "sha256:52022dc14dcc0451e05e54a8f5d5e7760351b6701eff816d1e9739577ec5635e"}, ] [package.dependencies] -mkdocs = ">=1.0.3" +mkdocs = ">=1.4.1" [[package]] name = "mkdocs-get-deps" @@ -1944,14 +1779,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.2.0" +version = "7.2.1" description = "Mkdocs Markdown includer plugin." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_include_markdown_plugin-7.2.0-py3-none-any.whl", hash = "sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b"}, - {file = "mkdocs_include_markdown_plugin-7.2.0.tar.gz", hash = "sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3"}, + {file = "mkdocs_include_markdown_plugin-7.2.1-py3-none-any.whl", hash = "sha256:30da634c568ea5d5f9e5881d51f80ac30d8c5f891cec160344ad7a0fdaea6286"}, + {file = "mkdocs_include_markdown_plugin-7.2.1.tar.gz", hash = "sha256:5d94db87b06cd303619dbaebba5f7f43a3ded7fd7709451d26f08c176376ffec"}, ] [package.dependencies] @@ -1963,28 +1798,28 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.6.22" +version = "9.7.1" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.6.22-py3-none-any.whl", hash = "sha256:14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84"}, - {file = "mkdocs_material-9.6.22.tar.gz", hash = "sha256:87c158b0642e1ada6da0cbd798a3389b0bc5516b90e5ece4a0fb939f00bacd1c"}, + {file = "mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c"}, + {file = "mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8"}, ] [package.dependencies] -babel = ">=2.10,<3.0" -backrefs = ">=5.7.post1,<6.0" -colorama = ">=0.4,<1.0" -jinja2 = ">=3.1,<4.0" -markdown = ">=3.2,<4.0" -mkdocs = ">=1.6,<2.0" -mkdocs-material-extensions = ">=1.3,<2.0" -paginate = ">=0.5,<1.0" -pygments = ">=2.16,<3.0" -pymdown-extensions = ">=10.2,<11.0" -requests = ">=2.26,<3.0" +babel = ">=2.10" +backrefs = ">=5.7.post1" +colorama = ">=0.4" +jinja2 = ">=3.1" +markdown = ">=3.2" +mkdocs = ">=1.6" +mkdocs-material-extensions = ">=1.3" +paginate = ">=0.5" +pygments = ">=2.16" +pymdown-extensions = ">=10.2" +requests = ">=2.30" [package.extras] git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] @@ -2053,18 +1888,18 @@ mkdocs = ">=1.0.3" [[package]] name = "mkdocstrings" -version = "0.30.1" +version = "1.0.3" description = "Automatic documentation from sources, for MkDocs." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82"}, - {file = "mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f"}, + {file = "mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046"}, + {file = "mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434"}, ] [package.dependencies] -Jinja2 = ">=2.11.1" +Jinja2 = ">=3.1" Markdown = ">=3.6" MarkupSafe = ">=1.1" mkdocs = ">=1.6" @@ -2079,14 +1914,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.18.2" +version = "2.0.2" description = "A Python handler for mkdocstrings." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d"}, - {file = "mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323"}, + {file = "mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0"}, + {file = "mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8"}, ] [package.dependencies] @@ -2096,158 +1931,158 @@ mkdocstrings = ">=0.30" [[package]] name = "multidict" -version = "6.7.0" +version = "6.7.1" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, - {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, - {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, - {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, - {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, - {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, - {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, - {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, - {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, - {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, - {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, - {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, - {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, - {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, - {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, - {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, - {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, - {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, - {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, - {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, - {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, - {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, - {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, - {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, - {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, - {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, - {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, - {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, - {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, - {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, - {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, - {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, - {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, - {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, - {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, - {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, - {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, - {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, - {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, - {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, - {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, - {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, - {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, - {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, - {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, - {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, - {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, - {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, - {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, - {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, - {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, - {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, + {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, + {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, + {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, + {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, + {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, + {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, + {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, + {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, + {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, + {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, + {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, + {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, + {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, + {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, + {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, + {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, + {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, + {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, + {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, + {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, + {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, + {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, + {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, + {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, + {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, + {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, + {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, + {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, + {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, + {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, + {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, + {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, + {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, + {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, + {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, + {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, ] [[package]] @@ -2269,53 +2104,54 @@ type = ["mypy", "mypy-extensions"] [[package]] name = "mypy" -version = "1.18.2" +version = "1.19.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, - {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, - {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, - {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, - {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, - {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, - {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, - {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, - {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, - {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, - {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, - {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, - {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, - {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, - {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, - {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, - {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, - {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, - {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, - {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, - {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, - {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, - {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, - {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, - {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, - {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, - {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, - {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, - {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, + {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, + {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, + {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, + {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, + {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, + {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, + {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, + {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, + {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, + {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, + {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, + {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, + {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, + {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, + {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, + {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, + {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, + {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, + {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, + {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, + {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, + {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, + {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, + {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, + {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, + {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] +librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=0.9.0" typing_extensions = ">=4.6.0" @@ -2339,121 +2175,98 @@ files = [ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] -[[package]] -name = "networkx" -version = "3.5" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, - {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, -] - -[package.extras] -default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"] -developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"] -doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"] -example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] -extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"] -test-extras = ["pytest-mpl", "pytest-randomly"] - [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, ] [[package]] name = "numpy" -version = "2.3.4" +version = "2.4.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032"}, - {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7"}, - {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda"}, - {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0"}, - {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a"}, - {file = "numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1"}, - {file = "numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996"}, - {file = "numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef"}, - {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e"}, - {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a"}, - {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16"}, - {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786"}, - {file = "numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc"}, - {file = "numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32"}, - {file = "numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e"}, - {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7"}, - {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953"}, - {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37"}, - {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd"}, - {file = "numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646"}, - {file = "numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d"}, - {file = "numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6"}, - {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7"}, - {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0"}, - {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f"}, - {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64"}, - {file = "numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb"}, - {file = "numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c"}, - {file = "numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b"}, - {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7"}, - {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2"}, - {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52"}, - {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26"}, - {file = "numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc"}, - {file = "numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9"}, - {file = "numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252"}, - {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e"}, - {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0"}, - {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0"}, - {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f"}, - {file = "numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d"}, - {file = "numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6"}, - {file = "numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f"}, - {file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73"}, + {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1"}, + {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32"}, + {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390"}, + {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413"}, + {file = "numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda"}, + {file = "numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695"}, + {file = "numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27"}, + {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548"}, + {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f"}, + {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460"}, + {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba"}, + {file = "numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f"}, + {file = "numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85"}, + {file = "numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef"}, + {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7"}, + {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499"}, + {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb"}, + {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7"}, + {file = "numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110"}, + {file = "numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622"}, + {file = "numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab"}, + {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82"}, + {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f"}, + {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554"}, + {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257"}, + {file = "numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657"}, + {file = "numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b"}, + {file = "numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74"}, + {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a"}, + {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325"}, + {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909"}, + {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a"}, + {file = "numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a"}, + {file = "numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75"}, + {file = "numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d"}, + {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8"}, + {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5"}, + {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e"}, + {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a"}, + {file = "numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443"}, + {file = "numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236"}, + {file = "numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0"}, + {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, ] [[package]] @@ -2491,14 +2304,14 @@ resolved_reference = "3c9acacc479067b092354c8ac345f24d24ec33d1" [[package]] name = "packaging" -version = "25.0" +version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] [[package]] @@ -2519,16 +2332,22 @@ lint = ["black"] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, ] +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "pathvalidate" version = "3.3.1" @@ -2548,15 +2367,15 @@ test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1 [[package]] name = "pefile" -version = "2023.2.7" +version = "2024.8.26" description = "Python PE parsing module" optional = false python-versions = ">=3.6.0" groups = ["dev"] markers = "sys_platform == \"win32\"" files = [ - {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, - {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, + {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, + {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, ] [[package]] @@ -2564,120 +2383,120 @@ name = "photoshop-python-api" version = "0.24.1" description = "Python API for Photoshop." optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.10,<4.0" groups = ["main"] files = [] develop = false [package.dependencies] -comtypes = "^1.1.11" -wheel = "^0.45.0" +comtypes = "^1.4.15" +wheel = "^0.46.3" [package.source] type = "git" url = "https://github.com/pappnu/photoshop-python-api.git" reference = "type-annotation" -resolved_reference = "9f8496ef88c96918a8ba4434f5878968e034fcaa" +resolved_reference = "cee5fe194c5c37b5da1279871cc892b5ed28a4af" [[package]] name = "pillow" -version = "12.0.0" +version = "12.1.1" description = "Python Imaging Library (fork)" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, - {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, - {file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}, - {file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}, - {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}, - {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}, - {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}, - {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}, - {file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}, - {file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}, - {file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}, - {file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}, - {file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}, - {file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}, - {file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}, - {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}, - {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}, - {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}, - {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}, - {file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}, - {file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}, - {file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}, - {file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}, - {file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}, - {file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}, - {file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}, - {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}, - {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}, - {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}, - {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}, - {file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}, - {file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}, - {file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}, - {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}, - {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}, - {file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}, - {file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}, - {file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}, - {file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}, - {file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}, - {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}, - {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}, - {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}, - {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}, - {file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}, - {file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}, - {file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}, - {file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}, - {file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}, - {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}, - {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}, - {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}, - {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}, - {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}, - {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}, - {file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}, - {file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}, - {file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}, - {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}, - {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}, - {file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}, - {file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}, - {file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}, - {file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}, - {file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}, - {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}, - {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}, - {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}, - {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}, - {file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}, - {file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}, - {file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}, - {file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}, - {file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}, - {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}, - {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}, - {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}, - {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}, - {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}, - {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}, - {file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}, - {file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}, - {file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}, - {file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}, - {file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}, + {file = "pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0"}, + {file = "pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4"}, + {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4"}, + {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e"}, + {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff"}, + {file = "pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40"}, + {file = "pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23"}, + {file = "pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9"}, + {file = "pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32"}, + {file = "pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af"}, + {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b"}, + {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5"}, + {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d"}, + {file = "pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c"}, + {file = "pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563"}, + {file = "pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80"}, + {file = "pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052"}, + {file = "pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397"}, + {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0"}, + {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3"}, + {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35"}, + {file = "pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a"}, + {file = "pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6"}, + {file = "pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9"}, + {file = "pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6"}, + {file = "pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60"}, + {file = "pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e"}, + {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717"}, + {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a"}, + {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029"}, + {file = "pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b"}, + {file = "pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1"}, + {file = "pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a"}, + {file = "pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da"}, + {file = "pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20"}, + {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13"}, + {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf"}, + {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524"}, + {file = "pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986"}, + {file = "pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c"}, + {file = "pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f"}, + {file = "pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642"}, + {file = "pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd"}, + {file = "pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f"}, + {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e"}, + {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0"}, + {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb"}, + {file = "pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f"}, + {file = "pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15"}, + {file = "pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f"}, + {file = "pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8"}, + {file = "pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f"}, + {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586"}, + {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce"}, + {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8"}, + {file = "pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36"}, + {file = "pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b"}, + {file = "pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735"}, + {file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"}, + {file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"}, ] [package.extras] @@ -2690,21 +2509,16 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.9.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, - {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, + {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, + {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, ] -[package.extras] -docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] -type = ["mypy (>=1.18.2)"] - [[package]] name = "pluggy" version = "1.6.0" @@ -2723,14 +2537,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "4.3.0" +version = "4.5.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, - {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, + {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, + {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, ] [package.dependencies] @@ -2889,208 +2703,212 @@ files = [ [[package]] name = "psd-tools" -version = "1.10.13" +version = "1.12.1" description = "Python package for working with Adobe Photoshop PSD files" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "psd_tools-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f59337c9787338f1847273b7d5dc3fde0c4d3e5884059ebe08b7e0ce5bdf7061"}, - {file = "psd_tools-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:06ea568b0f96b18df7ce400ab4e5bb917680af8ea010382f779e000bb4ecb0be"}, - {file = "psd_tools-1.10.13-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:873736c32f961cfbf2c294812360aa6d0bc04e6538c46b060dfb46b8f50f31de"}, - {file = "psd_tools-1.10.13-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3e4aca47bb2c0c60ed2e660359d2bdfd42270dfaac91c5c4f28c468f5d9079"}, - {file = "psd_tools-1.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:92216ca858b093981a273978910dffed0cb2aa592c3dc3cb32deeac12c4b34b4"}, - {file = "psd_tools-1.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:553c548e91d6051e4781d17eabaef7063c20e12a3eaa98c1a0a06d493a7b4db6"}, - {file = "psd_tools-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:99fe2c05b3088bdcfddcd56d68b9d72ea99923af983e18153fa1d9fcaca7a8c8"}, - {file = "psd_tools-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d88f6f181333b1310a360e30dd7f7716c8df80e2f157837e91d9fee7b6372604"}, - {file = "psd_tools-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6f249e7c2cffa70288d1d35b7bf79f18fd572ccc7700bcd924e451383173bc50"}, - {file = "psd_tools-1.10.13-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339e75a9b980c886a7fc7ea78a9dbfc117ad247e33c657ce19b879624ee0cb92"}, - {file = "psd_tools-1.10.13-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eec54dd2352fca42447e54cdd308c43fcd11c5ad5031a93d7639fbd27e668ce"}, - {file = "psd_tools-1.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f87b119e36c9b1dfa1de25ac2d56d3f6671897391c0d867795b0dc1e95d63fa6"}, - {file = "psd_tools-1.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b223b6a2d3ed7e9d006ae0a5c6fc503b9b205c9de673c661f1bd55b941baa07"}, - {file = "psd_tools-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:750073bd0a58800939b26fa766454f5cc9ca63b19c0b61dba84b1429f8e143b5"}, - {file = "psd_tools-1.10.13-cp311-cp311-win_arm64.whl", hash = "sha256:b768a1cbe0b189e700be2f09cfdec5560579b443e8bfedd57f5cd69b7b9cfb18"}, - {file = "psd_tools-1.10.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8bd22dd4b9dda0f2237d6be93b121d72f9754a65efc1222de8ee0f91e56cee63"}, - {file = "psd_tools-1.10.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e386dec96ec52753dd078d1777dd2d3265cccd6334d89f8e6ddea9d4b3b99328"}, - {file = "psd_tools-1.10.13-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ab4811dc5beb8ba110b70b0af191602deec4973e10a1db99f040c1c4d685089"}, - {file = "psd_tools-1.10.13-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d6a2055fa1176fe6b7e134432b71bef8eb7ac18aa785c141ba5c098a039eb68"}, - {file = "psd_tools-1.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afda6cbb96c5c4791955e4c1f4bf2f326139fa148b297f01ef5de0e0e0f14fdc"}, - {file = "psd_tools-1.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95e2017c4eb942524de85816fcd9207c10eb379bb4a78b77324c3ee294ed0e1e"}, - {file = "psd_tools-1.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:8d2347f58c17cdcec5583b0a4dd50ed6f8c68984df27946cce8ca63c88975098"}, - {file = "psd_tools-1.10.13-cp312-cp312-win_arm64.whl", hash = "sha256:b5c5192f8552d1f2b74d1f58454965104a0551aa84e829c105aa25f99e1cdf9b"}, - {file = "psd_tools-1.10.13-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:2a01ce3f67f943239286bfb3aef51f7bbce0fe735299ff0e3ea36746206b9f7f"}, - {file = "psd_tools-1.10.13-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:3b245056f0103b2c52f850317363825fe9d452ed4a2407aa5cea0199423c7fee"}, - {file = "psd_tools-1.10.13-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2dcda60d03bbe9bb7811fbae32fde19e592320405bf1a4005980d379ae3fbfd"}, - {file = "psd_tools-1.10.13-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9198ce1172def62d0b61b6b4392e44b29bf1d3acd3ae962bf47e500f318b461"}, - {file = "psd_tools-1.10.13-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90d7346b4670064ab0c48a740965a75729ce862505f0a5a71eefbe0d4daa1d63"}, - {file = "psd_tools-1.10.13-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a6249c47131b6fb027107c7077fe3caafec380d169f20092744c30da8ef316ca"}, - {file = "psd_tools-1.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:1158b771ead967365ed170c1f4e7ed990f73a86d7a9e502e3c90d92c85a59a2e"}, - {file = "psd_tools-1.10.13-cp313-cp313-win_arm64.whl", hash = "sha256:d27d264c56cd27b01ad970c9e020ed46232e03206feea315f9309a61a367ba2c"}, - {file = "psd_tools-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a5c647c4b33a872e87b5846230135644666481a4e10bce3570402386b66729d7"}, - {file = "psd_tools-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:205ecd744b8f88f33a9f3ee16dc24da77c7081a02b5453e5d6648b8cac18d0da"}, - {file = "psd_tools-1.10.13-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a746f91dc3430d112abaf2839274b825d9282a166a096d0012138d661b46d803"}, - {file = "psd_tools-1.10.13-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f8adc10e1d70508d1982837df8a079acb49aba63212a9db1d7f85dcaef4602a"}, - {file = "psd_tools-1.10.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e527adb03d30ad4d306ae7c3e20842963989e267615ec0470464b5cb2ecc0b01"}, - {file = "psd_tools-1.10.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8f729643723d10d13eed78534d71a02b384bf63969c40a4fec9fb1278ce0927a"}, - {file = "psd_tools-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:8f5ac5a1f02ee3f06620e3112c307058ebcbfaa1ab905cbd74ae71ea1c03f5e4"}, + {file = "psd_tools-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e99464f6368997b9dce5b504fab9734dd13e45080a78e4666f23c0395705942"}, + {file = "psd_tools-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:57cf15e1b2a40d532e6e22c5c739483e4d65b8783f68abd2c28e3536c2cb63e6"}, + {file = "psd_tools-1.12.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e73bbebe496749a5b4a9f2e001a107132cc56368050c18624c6a1721cc354dc"}, + {file = "psd_tools-1.12.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9be3c9098cc565fe4d8e2d48894172620fba9915c12fc924b8e87dbaeb292e6"}, + {file = "psd_tools-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb7f01057e0a01ea460f60b289ac4169e3670f5567df7a8709c10b5948e60041"}, + {file = "psd_tools-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d739ad4353105c49116ac58ab1de1063af4bb996981579ce90aae647683edca5"}, + {file = "psd_tools-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:c11291c4790fa93933128e4e1c444c8429a2d4dc504fb9dc004a337356a07ef7"}, + {file = "psd_tools-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c87fbf36682d36f2f30659542f90cbfbd3f58306f3767209c80fbd0314c4176"}, + {file = "psd_tools-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8791f0c04ceb7d709140590821d502eed186b53f215bfd6b594fb555f14db09c"}, + {file = "psd_tools-1.12.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8432db9e9cdc64e74de0cc587c28a93061ef72168de0045ab5f3fb6d73bc4099"}, + {file = "psd_tools-1.12.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e85101b347094edf7e9cbd109fd39309cbdc472cd636850088e42af84b1251fc"}, + {file = "psd_tools-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:44d0d1716cb1898f1b546c19068b347d7b5dfa7849953bc65cfe54489bbd3b56"}, + {file = "psd_tools-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46e556a01ee90dafeb58e5ea2ee7d86ffe47c794f341f41705e4a48000c17b4d"}, + {file = "psd_tools-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf93c133adb01755a58d261fb76a34c2ebcbc772ab696b711538f75f7fff772c"}, + {file = "psd_tools-1.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:705acfb3eac10aa0f05900860bb79ff19b6229a854ae953fc9c644c3aa506789"}, + {file = "psd_tools-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:93aa190b84205a991ad43432683be4627adb39a4b81f548b0052c3f49b65b4f3"}, + {file = "psd_tools-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d9034ede9d50ddb10aeda7d11188424dae087945f80d45badf2bc73a4879ba1"}, + {file = "psd_tools-1.12.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9f0c0929cb4b69120c545c27ddfe4b7aee22d8a754319f987ebb21da0d90c0"}, + {file = "psd_tools-1.12.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38628432f87b00d914acf914a52385f434fed9ec6b9f19a825b6798e4f5deab8"}, + {file = "psd_tools-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a162fbe2a24eb71e27d402c6356c6f1924c44617cfc676e0d96567f832148cdf"}, + {file = "psd_tools-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:041e9a452e8c283b7fb8d04ff5e481556b9d904df9c87e407b9554097e2ff5a4"}, + {file = "psd_tools-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:d3c8c8d9359deb6bbec9feb8ef074a716e14491cef136d9a040d36ac8f351f8f"}, + {file = "psd_tools-1.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:ccca70398ada1eb9f79917ac9ea8c521a75ce7779364ba5c6621a6d9357bc4f3"}, + {file = "psd_tools-1.12.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:a5cbd9b140490836137962528671c4d450e4a3b7e19171c566d5ea1c45a5e0c8"}, + {file = "psd_tools-1.12.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:2787b8caa2f8c7089de5705833a0a4d5cb787a76de435e3845fb62b9c6211a9e"}, + {file = "psd_tools-1.12.1-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad52c3ae9fe3c57bd5bb95cd3d8524256d9fb7542cb6f5b32af5bc9ff7e033e2"}, + {file = "psd_tools-1.12.1-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a09ed4a97c5a5634c672fb5a8c1732378bda9a548bebe92087508d9dfef65b7"}, + {file = "psd_tools-1.12.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a7a57edb16dbdece015cbdcdfaaa232382443a056deb4ff507e1856d4e04a064"}, + {file = "psd_tools-1.12.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:568ed2bd02153015bb87243faa77bb337be2859a13b853e6e45281db072defed"}, + {file = "psd_tools-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:a835c014116b8d5f739a51f7e3c51892451c66635d8b39bf0c870b2cbc917593"}, + {file = "psd_tools-1.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:8547002c4336c3eddda47a717065015170706cd818369f13ec0ccaf72b58fc1d"}, + {file = "psd_tools-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:3b3658928238cbdf0ff3ff9d5bd6f730ff2527b8ca12fe3123263ee8b46fd9f3"}, + {file = "psd_tools-1.12.1-cp314-cp314-win_arm64.whl", hash = "sha256:f642eea09aff7d56993c8a49c444b807d2cac7ac4eb1be736066748d77ec4865"}, ] [package.dependencies] -aggdraw = "*" attrs = ">=23.0.0" numpy = "*" Pillow = ">=10.3.0" -scikit-image = "*" -scipy = "*" + +[package.extras] +composite = ["aggdraw (>=1.3.16) ; sys_platform != \"win32\"", "aggdraw (>=1.3.16,<1.4.1) ; sys_platform == \"win32\" and python_version < \"3.11\"", "aggdraw (>=1.4.1) ; sys_platform == \"win32\" and python_version >= \"3.11\"", "scikit-image", "scipy"] [[package]] name = "psutil" -version = "7.1.2" +version = "7.2.2" description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "psutil-7.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0cc5c6889b9871f231ed5455a9a02149e388fffcb30b607fb7a8896a6d95f22e"}, - {file = "psutil-7.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8e9e77a977208d84aa363a4a12e0f72189d58bbf4e46b49aae29a2c6e93ef206"}, - {file = "psutil-7.1.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d9623a5e4164d2220ecceb071f4b333b3c78866141e8887c072129185f41278"}, - {file = "psutil-7.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:364b1c10fe4ed59c89ec49e5f1a70da353b27986fa8233b4b999df4742a5ee2f"}, - {file = "psutil-7.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f101ef84de7e05d41310e3ccbdd65a6dd1d9eed85e8aaf0758405d022308e204"}, - {file = "psutil-7.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:20c00824048a95de67f00afedc7b08b282aa08638585b0206a9fb51f28f1a165"}, - {file = "psutil-7.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e09cfe92aa8e22b1ec5e2d394820cf86c5dff6367ac3242366485dfa874d43bc"}, - {file = "psutil-7.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa6342cf859c48b19df3e4aa170e4cfb64aadc50b11e06bb569c6c777b089c9e"}, - {file = "psutil-7.1.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:625977443498ee7d6c1e63e93bacca893fd759a66c5f635d05e05811d23fb5ee"}, - {file = "psutil-7.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a24bcd7b7f2918d934af0fb91859f621b873d6aa81267575e3655cd387572a7"}, - {file = "psutil-7.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:329f05610da6380982e6078b9d0881d9ab1e9a7eb7c02d833bfb7340aa634e31"}, - {file = "psutil-7.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7b04c29e3c0c888e83ed4762b70f31e65c42673ea956cefa8ced0e31e185f582"}, - {file = "psutil-7.1.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c9ba5c19f2d46203ee8c152c7b01df6eec87d883cfd8ee1af2ef2727f6b0f814"}, - {file = "psutil-7.1.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:2a486030d2fe81bec023f703d3d155f4823a10a47c36784c84f1cc7f8d39bedb"}, - {file = "psutil-7.1.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3efd8fc791492e7808a51cb2b94889db7578bfaea22df931424f874468e389e3"}, - {file = "psutil-7.1.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2aeb9b64f481b8eabfc633bd39e0016d4d8bbcd590d984af764d80bf0851b8a"}, - {file = "psutil-7.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:8e17852114c4e7996fe9da4745c2bdef001ebbf2f260dec406290e66628bdb91"}, - {file = "psutil-7.1.2-cp37-abi3-win_arm64.whl", hash = "sha256:3e988455e61c240cc879cb62a008c2699231bf3e3d061d7fce4234463fd2abb4"}, - {file = "psutil-7.1.2.tar.gz", hash = "sha256:aa225cdde1335ff9684708ee8c72650f6598d5ed2114b9a7c5802030b1785018"}, + {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, + {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, + {file = "psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63"}, + {file = "psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312"}, + {file = "psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b"}, + {file = "psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9"}, + {file = "psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00"}, + {file = "psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9"}, + {file = "psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a"}, + {file = "psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf"}, + {file = "psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1"}, + {file = "psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841"}, + {file = "psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486"}, + {file = "psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979"}, + {file = "psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9"}, + {file = "psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e"}, + {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8"}, + {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc"}, + {file = "psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988"}, + {file = "psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee"}, + {file = "psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372"}, ] markers = {main = "sys_platform != \"cygwin\""} [package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] -test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] +dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] +test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] [[package]] name = "py7zr" -version = "1.0.0" +version = "1.1.0" description = "Pure python 7-zip library" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "py7zr-1.0.0-py3-none-any.whl", hash = "sha256:6f42d2ff34c808e9026ad11b721c13b41b0673cf2b4e8f8fb34f9d65ae143dd1"}, - {file = "py7zr-1.0.0.tar.gz", hash = "sha256:f6bfee81637c9032f6a9f0eb045a4bfc7a7ff4138becfc42d7cb89b54ffbfef1"}, + {file = "py7zr-1.1.0-py3-none-any.whl", hash = "sha256:5921bc30fb72b5453aafe3b2183664c08ef508cde2655988d5e9bd6078353ef7"}, + {file = "py7zr-1.1.0.tar.gz", hash = "sha256:087b1a94861ad9eb4d21604f6aaa0a8986a7e00580abd79fedd6f82fecf0592c"}, ] [package.dependencies] -brotli = {version = ">=1.1.0", markers = "platform_python_implementation == \"CPython\""} -brotlicffi = {version = ">=1.1.0.0", markers = "platform_python_implementation == \"PyPy\""} -inflate64 = ">=1.0.0,<1.1.0" +brotli = {version = ">=1.2.0", markers = "platform_python_implementation == \"CPython\""} +brotlicffi = {version = ">=1.2.0.0", markers = "platform_python_implementation == \"PyPy\""} +inflate64 = ">=1.0.4" multivolumefile = ">=0.2.3" psutil = {version = "*", markers = "sys_platform != \"cygwin\""} -pybcj = ">=1.0.0,<1.1.0" +pybcj = ">=1.0.6" pycryptodomex = ">=3.20.0" -pyppmd = ">=1.1.0,<1.3.0" -pyzstd = ">=0.16.1" +pyppmd = ">=1.3.1" texttable = "*" [package.extras] -check = ["black (>=24.8.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=5.13.2)", "lxml", "mypy (>=1.10.0)", "mypy_extensions (>=1.0.0)", "pygments", "pylint", "readme-renderer", "twine", "types-psutil"] +check = ["black (>=25.1.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=7.0.0)", "lxml", "mypy (>=1.17.0)", "mypy_extensions (>=1.1.0)", "pygments", "pylint", "readme-renderer", "twine", "types-psutil"] debug = ["pytest", "pytest-leaks", "pytest-profiling"] -docs = ["docutils", "sphinx (>=7.0.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] -test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-httpserver", "pytest-remotedata", "pytest-timeout", "requests"] -test-compat = ["libarchive-c"] +docs = ["docutils", "sphinx (>=8.0.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] +test = ["coverage[toml] (>=7.10.7)", "coveralls (>=4.0.2)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-httpserver", "pytest-remotedata", "pytest-timeout", "requests"] [[package]] name = "pybcj" -version = "1.0.6" +version = "1.0.7" description = "bcj filter library" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pybcj-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0fc8eda59e9e52d807f411de6db30aadd7603aa0cb0a830f6f45226b74be1926"}, - {file = "pybcj-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0495443e8691510129f0c589ed956af4962c22b7963c5730b0c80c9c5b818c06"}, - {file = "pybcj-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c7998b546c3856dbe9ae879cb90393df80507f65097e7019785852769f4a990"}, - {file = "pybcj-1.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c859f85e718924f48b3ac967cda5528ccbef1e448a4462652cca688eee477"}, - {file = "pybcj-1.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:186fbb849883ac80764d96dbd253503dd9cecbcf6133504a0c9d6a2df81d5746"}, - {file = "pybcj-1.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:437bd5f5e6579bde404404ad2de915d1306c389595c68d0eb8933fee1408e951"}, - {file = "pybcj-1.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:933d6be8f07c653ff3eba16900376b3212249be1c71caf9db17f4cd52da5076c"}, - {file = "pybcj-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:90e169b669bbed30e22d36ba97d23dcfc71e044d3be41c8010fd6a53950725e5"}, - {file = "pybcj-1.0.6-cp310-cp310-win_arm64.whl", hash = "sha256:06441026c773f8abeb7816566acfffe7cd65a9b69094197a9de64d0496cd4c3c"}, - {file = "pybcj-1.0.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0275564a1afc4b2d1a6ff465384fb73a64622a88b6e4856cb7964ba2335a06e"}, - {file = "pybcj-1.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa794b134b4ee183a4ceb739e9c3a445a24ee12e7e3231c37820f66848db4c52"}, - {file = "pybcj-1.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d8945e8157c7fa469db110fc78579d154a31d121d14705b26d7d3ec3a471c8e"}, - {file = "pybcj-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7109177b4f77526a6ce4b565ee37483f5a5dd29bc92eaea6739b3c58618aeb7"}, - {file = "pybcj-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c48cbc9ebed137ac8759d0f2c3d12b999581dae7b4f84d974888c402f00fdb78"}, - {file = "pybcj-1.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6dccff82008e3cb5e5e639737320c02341b8718e189b9ece13f0230e0d57e7af"}, - {file = "pybcj-1.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4e68cfc4fb099e8200386ac2255a9f514b8bb056189273bcce874bda3597459"}, - {file = "pybcj-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:13747c01b60bf955878267718f28c36e2bbb81fb8495b0173b21083c7d08a4a4"}, - {file = "pybcj-1.0.6-cp311-cp311-win_arm64.whl", hash = "sha256:6f81d6106c50c5e91c16ad58584fd7ab9eb941360188547e0184b1ede9e47f1d"}, - {file = "pybcj-1.0.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f5d1dbc76f615595d7d8f3846c07f607fb1e2305d085c34556b32dacf8e88d12"}, - {file = "pybcj-1.0.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1398f556ed2afe16ae363a2b6e8cf6aeda3aa21861757286bc6c498278886c60"}, - {file = "pybcj-1.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e269cfc7b6286af87c5447c9f8c685f19cff011cac64947ffb4cd98919696a7f"}, - {file = "pybcj-1.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7393d0b0dcaa0b1a7850245def78fa14438809e9a3f73b1057a975229d623fd3"}, - {file = "pybcj-1.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e252891698d3e01d0f60eb5adfe849038cd2d429cb9510f915a0759301f1884d"}, - {file = "pybcj-1.0.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ae5c891fcda9d5a6826a1b8e843b1e52811358594121553e6683e65b13eccce7"}, - {file = "pybcj-1.0.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eac3cb317df1cefed2783ce9cafdae61899dd02f2f4749dc0f4494a7c425745f"}, - {file = "pybcj-1.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:72ebec5cda5a48de169c2d7548ea2ce7f48732de0175d7e0e665ca7360eaa4c4"}, - {file = "pybcj-1.0.6-cp312-cp312-win_arm64.whl", hash = "sha256:8f1f75a01e45d01ecf88d31910ca1ace5d345e3bfb7c18db0af3d0c393209b63"}, - {file = "pybcj-1.0.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3e6800eb599ce766e588095eedb2a2c45a93928d1880420e8ecfad7eff0c73dc"}, - {file = "pybcj-1.0.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69a841ca0d3df978a2145488cec58460fa4604395321178ba421384cff26062f"}, - {file = "pybcj-1.0.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:887521da03302c96048803490073bd0423ff408a3adca2543c6ee86bc0af7578"}, - {file = "pybcj-1.0.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39a5a9a2d0e1fa4ddd2617a549c11e5022888af86dc8e29537cfee7f5761127d"}, - {file = "pybcj-1.0.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57757bc382f326bd93eb277a9edfc8dff6c22f480da467f0c5a5d63b9d092a41"}, - {file = "pybcj-1.0.6-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb1872b24b30d8473df433f3364e828b021964229d47a07f7bfc08496dbfd23e"}, - {file = "pybcj-1.0.6-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:5fedfeed96ab0e34207097f663b94e8c7076025c2c7af6a482e670e808ea5bb0"}, - {file = "pybcj-1.0.6-cp313-cp313-win_amd64.whl", hash = "sha256:caefc3109bf172ad37b52e21dc16c84cf495b2ea2890cc7256cdf0188914508d"}, - {file = "pybcj-1.0.6-cp313-cp313-win_arm64.whl", hash = "sha256:b24367175528da452a19e4c55368d5c907f4584072dc6aeee8990e2a5e6910fc"}, - {file = "pybcj-1.0.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:558128fbc201c9f11c1b1df30377fab3821ebb736c28e5eaf9fff9cc9e56b806"}, - {file = "pybcj-1.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d05f4026154d77c97486d5ce04261b473e3ec8c2f7cf0f937b7baa439c616559"}, - {file = "pybcj-1.0.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96ce9c428800ecc0d52cec9947ee167f3a7f913cc2ba58b9a462e7f19c52ac4b"}, - {file = "pybcj-1.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05038a58d78ab15a847ed90c17d924be5b7848f27a43517dc88a5589fba1ca78"}, - {file = "pybcj-1.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:591f58891ff52585a38894b28c8b952e4c7be93f65d6d43751672cde8edeff36"}, - {file = "pybcj-1.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1f416250101631ac04705a19d78ec407d261da9dffa0e1fa1f1f2d9409ec70d"}, - {file = "pybcj-1.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27c489dd9e0d9745ebf7cd4344f23b6cb655edb2dea879ca63a0558a993e0d4b"}, - {file = "pybcj-1.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:56fe3ff939653c6b0e35aa105170af3494ee9e2469494ef1d0fa2bac3fdd99d0"}, - {file = "pybcj-1.0.6-cp39-cp39-win_arm64.whl", hash = "sha256:6c88e1a04b90547f0470e4d2bd190bbe6b73c8666d4f7196c3ca43a379a15de5"}, - {file = "pybcj-1.0.6.tar.gz", hash = "sha256:70bbe2dc185993351955bfe8f61395038f96f5de92bb3a436acb01505781f8f2"}, + {file = "pybcj-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:618ec7345775c306d83527750e2d0ab3f42ffdc5ad6282f62f88cb53c9b2b679"}, + {file = "pybcj-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7e7faa1b0f7d894685e4567dd41268b93df89cff347ebfdfdc48b4bc0d68cb2"}, + {file = "pybcj-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cd4b2d05272df605d5bdb54b3386985a2b074b4d97072da944736abd639fdee"}, + {file = "pybcj-1.0.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eb5cd6f52df8857a8d9de594ca28a71683169b9de5af7e727c0e510aedb4550"}, + {file = "pybcj-1.0.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9d10760356b7d254b7b04ff38e052d5229c5f5a69a5514c9c31cb1dbb7d7f82"}, + {file = "pybcj-1.0.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1696d9b50971e317f72802bebd18f9b53684892ad3c43e0258f34e0a01738484"}, + {file = "pybcj-1.0.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e3d91b5dfdb0a200545b68145d81dae4edd2c385d89643dc45d6d01291f5c04"}, + {file = "pybcj-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:74df8f4c897f937105e8cd830df3b4ddf65ab5b5ba3e63cd6e3aeb3f4ecb0864"}, + {file = "pybcj-1.0.7-cp310-cp310-win_arm64.whl", hash = "sha256:dc121ecb26fdc1a4173a20b3c7cca5d8cc81494b485d4b44a62ed8448f8c796e"}, + {file = "pybcj-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:906ee707e89302253813a123f90a36d94d1f3c8785a4a1b853b31ac67296857a"}, + {file = "pybcj-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93da8503161fd51e01843aca031444fd46dce83e8a8bb4972f0256d6b3d280d3"}, + {file = "pybcj-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb4a52cd573f4359a89fd3a4a1d82c914f8b758a5c9f16cd5dd13fb8aa24436"}, + {file = "pybcj-1.0.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6d9a26fa9e627eb2fbba0f5b376ab42246bebdaf38cf437e384a6b7e3d78e23"}, + {file = "pybcj-1.0.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47207b69997fdc39e91a66812477506267964284b7d45fed68876dd74323d44f"}, + {file = "pybcj-1.0.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b33d6ef1de94720f4856e198bd2b8eca978015ed685aef4138755ba3910eb963"}, + {file = "pybcj-1.0.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:053c7cda499a8934151d0c915b6efce8e53fa6b47d162434a5b24afef7af5d17"}, + {file = "pybcj-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:555e90270d665d94cd34d2e50b096f68dba6baf7035ae11ac65c2bc126f8cef7"}, + {file = "pybcj-1.0.7-cp311-cp311-win_arm64.whl", hash = "sha256:22bdb390da9a4e38b2191070a62b88ad52edc3f6e12fe7eea278217ccfdbc02c"}, + {file = "pybcj-1.0.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d39787b85678d2ab1c67e2f21dd2e71be851f08e5c9fe619c605877b57dd529d"}, + {file = "pybcj-1.0.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8cd5dd166093a1fb146fb78859aac0f00b45db6c11074705517bc72a940a1c8e"}, + {file = "pybcj-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82152e8641f5ce68638f3504227065f27b6b1efe96479ffbf20d81530c220062"}, + {file = "pybcj-1.0.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2095b45d05f8d19430167b7df52ebd920df854ab8d064bae879df0a4611374b3"}, + {file = "pybcj-1.0.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b400c9f48faed01edb7f0df54b4354270325c886e785f31c866c581a46023b"}, + {file = "pybcj-1.0.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8210e51a2d4e5ccb4fdb75a1e692dd8c121858b589026bb28988ed7ffdb7ed00"}, + {file = "pybcj-1.0.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6c3fe420083186ae2e5f75c23aa6563dcb030b8fc188d00778ce374d1df1984"}, + {file = "pybcj-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:c435062d66364f85674a639541980000e37657b98367a2ce2699514e44b8ab05"}, + {file = "pybcj-1.0.7-cp312-cp312-win_arm64.whl", hash = "sha256:3f74fd70b08092e58b1ee13c67fbf9de63d73eb1c61ab06670a0d7161efeb252"}, + {file = "pybcj-1.0.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d5e0feeeee3a659b30d7afbd89bf41da84e8c8fe13e5b997457e799a70fa550"}, + {file = "pybcj-1.0.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:60baaf9f0da31438515a401145f920f75f2ec7d511165bbf57475467af72a3e6"}, + {file = "pybcj-1.0.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c6e726618c3d43c730df5a4067fc19653b360f89c2f72f4323dae10d324552"}, + {file = "pybcj-1.0.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a5fcd40a4ce8f0c5428032ec5db9f03abb42214b993886cdf558e5644de636e"}, + {file = "pybcj-1.0.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:029112255c22de66e0117bec932c8be341ed20c56dcf6a961c14689f7f0ce772"}, + {file = "pybcj-1.0.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6492bcef5cb6883506b9dce5e48cb81217407305957b0e602c6c689c60097c5e"}, + {file = "pybcj-1.0.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22a7f4a51d36a1abb67a61e93248f997eb2be278f788d681096f5044ae18b4f9"}, + {file = "pybcj-1.0.7-cp313-cp313-win_amd64.whl", hash = "sha256:ebcce9b419fe5d3109150a1fab0fc93a64d5cd812ca44c5ddb7d4f7128ea369f"}, + {file = "pybcj-1.0.7-cp313-cp313-win_arm64.whl", hash = "sha256:bc6acf0320976b4e31bdc0e59b16689083d5c346a6c62ac4f799685d1cc5cf27"}, + {file = "pybcj-1.0.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:293f951eb3877840acab79f0c4dcfc06eab03e087cb9e4c004ec058e093acb1d"}, + {file = "pybcj-1.0.7-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3ae64960904f362d33ffca10715803afd9f9a6a2a592f871dcb335acf82edf29"}, + {file = "pybcj-1.0.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:70aa4476910f982025f878e598c136559a6d78b59fc20ba8b4b592306cde6051"}, + {file = "pybcj-1.0.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79ce3ce9b380b1b75c5e490abc3888ee3b5b2d28c22b59618674bf410b9cee16"}, + {file = "pybcj-1.0.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc73ee1bc064d6f97dfd66051d3859b32e1b6a4cf89b077f5c8ef6c2dccb71af"}, + {file = "pybcj-1.0.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5b0d13f41a9f85b3f95dd5dc7bfaa9539e80f8ae60a96db7f34c07ed732e4a82"}, + {file = "pybcj-1.0.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:597d7e9a8cbb30a6ed54d552fd3436edb32bbb821a7ac2fa8e5c7ebd1f7e0e93"}, + {file = "pybcj-1.0.7-cp314-cp314-win_amd64.whl", hash = "sha256:4603cc41ceb1236abe9169e2ead344140be5d2c3ac01bbc5e44cb1b13078a009"}, + {file = "pybcj-1.0.7-cp314-cp314-win_arm64.whl", hash = "sha256:adf985e816ddd59f3bf6d1066b7fa89de7424a4f19f3725f9976284cabe54e28"}, + {file = "pybcj-1.0.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bbd835873de147481d62c11ba91a75d26a72df1142de3516b384b04e5a1db6d"}, + {file = "pybcj-1.0.7-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b7576b25d7b01a953e2f987e77cef93c001db7b95924a5541d5a55f9195a7e89"}, + {file = "pybcj-1.0.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57b36920498f82ca6a325a98b13e0fbff8fc29bade7aaaddc7d284640bffd87d"}, + {file = "pybcj-1.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac2a46faf41e373939f6d3e6a5aa2121bf09e2446972c14a8e5d1ca3b0f8130"}, + {file = "pybcj-1.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c7d6156ef2b4e8ecd450b62dc4cc3a89e8dda307cb26288b670952ef0df3a37"}, + {file = "pybcj-1.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0fe306213de1e764abae63c06ae5a4e9a83632f62612805f1f883b8d74431901"}, + {file = "pybcj-1.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00448182d535cca37e8f24d892d480fa86f80ff20c79385f6eca75f118efcbb4"}, + {file = "pybcj-1.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:7e94aa712d0fa5fda9875828441755ece7121fc3f8c5cc3bc8ee92d05b853590"}, + {file = "pybcj-1.0.7-cp314-cp314t-win_arm64.whl", hash = "sha256:16fd4e51a5556d1f38d7ba5d1fab588bfb60ae23d2299b5179779bf9900adf71"}, + {file = "pybcj-1.0.7.tar.gz", hash = "sha256:72d64574069ffb0a800020668376b7ebd7adea159adbf4d35f8effc62f0daa67"}, ] [package.extras] -check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=1.10.0)", "pygments", "readme-renderer"] +check = ["check-manifest", "flake8 (<8)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=1.10.0)", "pygments", "readme-renderer"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pyclean" -version = "3.2.0" +version = "3.5.0" description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyclean-3.2.0-py3-none-any.whl", hash = "sha256:6622535af7aa6132b80373f779e2dd9b6e8cfe7f67457cb6c566b820bd479a6a"}, - {file = "pyclean-3.2.0.tar.gz", hash = "sha256:256cf317cc58ca64fd94694dd6dfe211366266e3095e0449baa87421d0e2ab39"}, + {file = "pyclean-3.5.0-py3-none-any.whl", hash = "sha256:584801fecd52a7690ba85a141c21ecfbbfb69bcfcd03b2b1b87f5e0d8c7a7c29"}, + {file = "pyclean-3.5.0.tar.gz", hash = "sha256:6033b7cb728fbdacc5912dc72e09baee76aeef328430f3e9ccb1a78def4fbfd4"}, ] [[package]] name = "pycparser" -version = "2.23" +version = "3.0" description = "C parser in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] markers = "platform_python_implementation == \"PyPy\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, - {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] [[package]] @@ -3146,19 +2964,19 @@ files = [ [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, - {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.41.4" +pydantic-core = "2.41.5" typing-extensions = ">=4.14.1" typing-inspection = ">=0.4.2" @@ -3168,129 +2986,133 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, - {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] @@ -3298,14 +3120,14 @@ typing-extensions = ">=4.14.1" [[package]] name = "pydantic-settings" -version = "2.11.0" +version = "2.13.0" description = "Settings management using Pydantic" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, - {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, + {file = "pydantic_settings-2.13.0-py3-none-any.whl", hash = "sha256:d67b576fff39cd086b595441bf9c75d4193ca9c0ed643b90360694d0f1240246"}, + {file = "pydantic_settings-2.13.0.tar.gz", hash = "sha256:95d875514610e8595672800a5c40b073e99e4aae467fa7c8f9c263061ea2e1fe"}, ] [package.dependencies] @@ -3337,32 +3159,32 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyinstaller" -version = "6.16.0" +version = "6.19.0" description = "PyInstaller bundles a Python application and all its dependencies into a single package." optional = false python-versions = "<3.15,>=3.8" groups = ["dev"] files = [ - {file = "pyinstaller-6.16.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fd1c785219a87ca747c21fa92f561b0d2926a7edc06d0a0fe37f3736e00bd7a"}, - {file = "pyinstaller-6.16.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b756ddb9007b8141c5476b553351f9d97559b8af5d07f9460869bfae02be26b0"}, - {file = "pyinstaller-6.16.0-py3-none-manylinux2014_i686.whl", hash = "sha256:0a48f55b85ff60f83169e10050f2759019cf1d06773ad1c4da3a411cd8751058"}, - {file = "pyinstaller-6.16.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:73ba72e04fcece92e32518bbb1e1fb5ac2892677943dfdff38e01a06e8742851"}, - {file = "pyinstaller-6.16.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b1752488248f7899281b17ca3238eefb5410521291371a686a4f5830f29f52b3"}, - {file = "pyinstaller-6.16.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ba618a61627ee674d6d68e5de084ba17c707b59a4f2a856084b3999bdffbd3f0"}, - {file = "pyinstaller-6.16.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c8b7ef536711617e12fef4673806198872033fa06fa92326ad7fd1d84a9fa454"}, - {file = "pyinstaller-6.16.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ebf84d02c51fed19b82a8abb4df536923abd55bb684d694e1356e4ae2a0ce5"}, - {file = "pyinstaller-6.16.0-py3-none-win32.whl", hash = "sha256:6d5f8617f3650ff9ef893e2ab4ddbf3c0d23d0c602ef74b5df8fbef4607840c8"}, - {file = "pyinstaller-6.16.0-py3-none-win_amd64.whl", hash = "sha256:bc10eb1a787f99fea613509f55b902fbd2d8b73ff5f51ff245ea29a481d97d41"}, - {file = "pyinstaller-6.16.0-py3-none-win_arm64.whl", hash = "sha256:d0af8a401de792c233c32c44b16d065ca9ab8262ee0c906835c12bdebc992a64"}, - {file = "pyinstaller-6.16.0.tar.gz", hash = "sha256:53559fe1e041a234f2b4dcc3288ea8bdd57f7cad8a6644e422c27bb407f3edef"}, + {file = "pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63"}, + {file = "pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe"}, + {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83"}, + {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6"}, + {file = "pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2"}, + {file = "pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33"}, + {file = "pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea"}, + {file = "pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865"}, ] [package.dependencies] altgraph = "*" macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} packaging = ">=22.0" -pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2025.8" +pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2026.0" pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} setuptools = ">=42.0.0" @@ -3372,14 +3194,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.9" +version = "2026.0" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2025.9-py3-none-any.whl", hash = "sha256:ccbfaa49399ef6b18486a165810155e5a8d4c59b41f20dc5da81af7482aaf038"}, - {file = "pyinstaller_hooks_contrib-2025.9.tar.gz", hash = "sha256:56e972bdaad4e9af767ed47d132362d162112260cbe488c9da7fee01f228a5a6"}, + {file = "pyinstaller_hooks_contrib-2026.0-py3-none-any.whl", hash = "sha256:0590db8edeba3e6c30c8474937021f5cd39c0602b4d10f74a064c73911efaca5"}, + {file = "pyinstaller_hooks_contrib-2026.0.tar.gz", hash = "sha256:0120893de491a000845470ca9c0b39284731ac6bace26f6849dea9627aaed48e"}, ] [package.dependencies] @@ -3388,14 +3210,14 @@ setuptools = ">=42.0.0" [[package]] name = "pymdown-extensions" -version = "10.16.1" +version = "10.21" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, - {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, + {file = "pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f"}, + {file = "pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5"}, ] [package.dependencies] @@ -3407,124 +3229,169 @@ extra = ["pygments (>=2.19.1)"] [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.2" description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, - {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] [[package]] -name = "pypiwin32" -version = "223" -description = "" +name = "pyppmd" +version = "1.3.1" +description = "PPMd compression/decompression library" optional = false -python-versions = "*" +python-versions = ">=3.10" groups = ["main"] -markers = "sys_platform == \"win32\"" files = [ - {file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"}, - {file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"}, + {file = "pyppmd-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:041f46fbeb0a59888c0a94d6b9a557c652935633a104be1c31c12de491b5f448"}, + {file = "pyppmd-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9512a8b39740923559c26eb16266bf8b70d4eab6ad27a9b39cd2465e60e0acfa"}, + {file = "pyppmd-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8966f26b91ba7cdff3cfec5512d39d1f8bf4a8dbb75c44085e33b564566fea66"}, + {file = "pyppmd-1.3.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d1cff657e85655c67426c29c90c78a6210148b207993e643fc351c72c60d188"}, + {file = "pyppmd-1.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9de2cdcc3932e7c23a54beb48dfe1b5ab7b4aedd5ffaae1e4871bd213d630cb3"}, + {file = "pyppmd-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e1985461219c30d4576070b7e2de718dbb6f32637d1e658d25f838dfda2a4bb"}, + {file = "pyppmd-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20d9d1aa4d0f32118c8094c212c66b7af50e55f47e7c6dffa5f35a8ac391faca"}, + {file = "pyppmd-1.3.1-cp310-cp310-win32.whl", hash = "sha256:44d25e7dede2abb614bc023fe87835365fdd5865981c2273b70bfad71b84db29"}, + {file = "pyppmd-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:1e503a28c9a275d31f24af9b735d2cca543b62f438b064e2833e9833e758bdbc"}, + {file = "pyppmd-1.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:3fb3708d7b2b38e2999385a2f02c8e68e0f5a364d94f94e475e2e8b09e9338fc"}, + {file = "pyppmd-1.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdf55aa6ee7aef492f6896464e7a5a528f8615bb9e435f55bc8dff226fcc8292"}, + {file = "pyppmd-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa820aac385ac4ee57160b26d92862c69d31c08f92272dbef05fe8e619cea8d1"}, + {file = "pyppmd-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e16593ba4ca0a85821ae698ef06847a52937662f5ce1b130c39cca2979a4e8cd"}, + {file = "pyppmd-1.3.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:486dde2294ff9b30465ab5bb0f213b20bd5ac0e4adf21be801a1ceb29aa75d9d"}, + {file = "pyppmd-1.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89730cf026416ae2546c92738966ecf117c8176d52c229ad621a61c34643818b"}, + {file = "pyppmd-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e77f5a6770950d464b50da760d53e67bce308a3abc8e3bd51db620b3f8cf1fa8"}, + {file = "pyppmd-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8e3bf8deef44f8e03612689a6067a4a3dd7e50d2ef00af4cf987c59b62ff3006"}, + {file = "pyppmd-1.3.1-cp311-cp311-win32.whl", hash = "sha256:a3509b3f881409ebc5522942438108c48a78f8df88bcf3f9d907b74131b9431c"}, + {file = "pyppmd-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:23b83799f33f9a24577f22e092b0feecda8cd1ea33871ad8610a58629874f7bc"}, + {file = "pyppmd-1.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:234489036a1758670655d1ceafd4caeb93b858bd4c0ca39686837d38aef044c0"}, + {file = "pyppmd-1.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3faa58ab2ebe3b13ec23b1904639d687fb727270d2962fd2d239ca00fd6eb865"}, + {file = "pyppmd-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27703f041ee96912a5410fd3ce31c5cde32f9323bd67f72f100bd960ee67bf13"}, + {file = "pyppmd-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e773d8353b36f7e7973a43526993fb276b98a97839cb5dc8f4e6465ad873f41a"}, + {file = "pyppmd-1.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b1883accf840cb0b711785d353f8548853a1401d381da007c0aec362f3ffac"}, + {file = "pyppmd-1.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bd6d179ad39b6191ca0cbe62fb9592f33f49277b4384ad7bc5eb0e6ca27ebee"}, + {file = "pyppmd-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:806cf8d33606e44bf5ff5786c57891f57993f1eef1c763da3c58ea97de3a13c8"}, + {file = "pyppmd-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1826cbce9a2c944aa08df79310a7e6d4a61fd20636b6dff64a77ea4bc43da30f"}, + {file = "pyppmd-1.3.1-cp312-cp312-win32.whl", hash = "sha256:d3ff96671319318d941dd34300d641745048e8a3251b077bddf98652d6ddc513"}, + {file = "pyppmd-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:c8c1ad39e7ebde71bf5a54cf61f489bf4790f1dd0beb70dc2e8f5ad3329d7ca7"}, + {file = "pyppmd-1.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:391b2bf76d7dc45b343781754d0b734dcbf539b92667986a343f5488c4bf9ca0"}, + {file = "pyppmd-1.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4b4edb3e9619fd0bc39c1a07eb03e8731db833a93b23134f36c7ef581a94b37a"}, + {file = "pyppmd-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8b5c813e462c91048b88e2adfbcc0c69f2c905f70097001d32066f86f675bd4"}, + {file = "pyppmd-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e8d372d9fac382183e0371cf0c2d736b494b1857a1befe98d563342b1205265b"}, + {file = "pyppmd-1.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b765ae21f7ed2f4ea8f32bfd9e3a4a8d738e73fc8f8dcddec9cbe2c898d60be"}, + {file = "pyppmd-1.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd00522ddfcc292304577386b6c217758c0c10e1fb9ce7877ad7d3b7b821a808"}, + {file = "pyppmd-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3de62099ff2ca876c2d39bc547bcba6f7b878988663abd782a5bad4edac3bb44"}, + {file = "pyppmd-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:011f845de195d60fe973a635a1f4be981b7d80f357a8acb1b2d83bdf5087c808"}, + {file = "pyppmd-1.3.1-cp313-cp313-win32.whl", hash = "sha256:7d61bd01f25289b6ae54832db4254602fb0c6d105f6e6bf0aee39b803b698b98"}, + {file = "pyppmd-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:df8d84ab72381058a964ba66e5e81ed52dbd0b5ad734a5ef8353452983506098"}, + {file = "pyppmd-1.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:124a04aab6936ba011f9ad57067798c7f052fdb1848b0cc4318606eea55475e6"}, + {file = "pyppmd-1.3.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ea53f71ac16e113599b8441a9d8b6dcd71cfdf15cdb33ba5151810b8e656c5ec"}, + {file = "pyppmd-1.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:985c8703b53e5f68fe17f653e96748d60b1f855676c852a6e67cd472eb853671"}, + {file = "pyppmd-1.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33daa996ad5203c665c0b55aff6329817b2cb7fa95f2c33a2e83ed0121b400cb"}, + {file = "pyppmd-1.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b49870c6d7194f6eb80f30335ca03596d153e02fcde2c222e4f1202ac25f7fcf"}, + {file = "pyppmd-1.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:385e92c97c42e8a6f0bfc0e4acfc6c074cb1ba3a2f650f292696dd9f19e2e603"}, + {file = "pyppmd-1.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017a1e2903f1c3147a1046db486990d401e8a25eb52c320b1fc2fb3e7b83cbeb"}, + {file = "pyppmd-1.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f2770a4b777c0c5236b3d9294b7bf4bc15538c95d45b2079eb8ebc1298e62e37"}, + {file = "pyppmd-1.3.1-cp314-cp314-win32.whl", hash = "sha256:b9d54cd59ce97f2ba57be1da91b3d874d129faca21c9565d7afec111f942e6a1"}, + {file = "pyppmd-1.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0e64247618cb150d2909beb0137da3084fef1d3479b4cc73b5b47fda7611abf9"}, + {file = "pyppmd-1.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:d354d6e551d2630b0ac98f27e3ad63e86cdcac9ce2115b5dfe46e2c9d3f4e82c"}, + {file = "pyppmd-1.3.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2d77ed79662c32e2551748d59763cfe3dcd10855bf3495937e3d5e5917507818"}, + {file = "pyppmd-1.3.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6a1f92b94635c23d85270bb26db25cc0db544e436af86efc1cf58302d71d5af1"}, + {file = "pyppmd-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:610f214f2405e27eb5a3dad7fa15f385cfc42141a01cda71995d9c1e0b09fab9"}, + {file = "pyppmd-1.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae81a14895498d9a23429d92114c98da478b74b8e33251527d7cff3e01c09de0"}, + {file = "pyppmd-1.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1683e6d1ba09e377e0ae02de3a518191a3d63ccdb0b6037c74e6ddf577b5644"}, + {file = "pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e4d74fa0f3531e9dadc56e0ace41bce82d3c0babed47b3f224101dc0dbde7287"}, + {file = "pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f8d6375b18b9c79127fee0885cfd52e2e983edb67041464309426571d38dcd4"}, + {file = "pyppmd-1.3.1-cp314-cp314t-win32.whl", hash = "sha256:de87f7acd575fb07a4ff42d41bcc071570fe759a36f345f1f54f574ecfccfc5b"}, + {file = "pyppmd-1.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:76e4800aa67292b4cc80058fd29b39e02a5dded721af9fe5654f356ef24307f4"}, + {file = "pyppmd-1.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e066cbf1d335fe20480cd8ecc10848ba78d99fe6d1e44ea00def48feaf46afdf"}, + {file = "pyppmd-1.3.1.tar.gz", hash = "sha256:ced527f08ade4408c1bfc5264e9f97ffac8d221c9d13eca4f35ec1ec0c7b6b2e"}, +] + +[package.extras] +check = ["check-manifest", "flake8", "flake8-black", "flake8-isort", "mypy (>=1.10.0)", "pygments", "readme-renderer"] +docs = ["sphinx", "sphinx_rtd_theme"] +fuzzer = ["atheris", "hypothesis"] +test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pyside6" +version = "6.10.2" +description = "Python bindings for the Qt cross-platform application and UI framework" +optional = false +python-versions = "<3.15,>=3.9" +groups = ["main"] +files = [ + {file = "pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:4b084293caa7845d0064aaf6af258e0f7caae03a14a33537d0a552131afddaf0"}, + {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1b89ce8558d4b4f35b85bff1db90d680912e4d3ce9e79ff804d6fef1d1a151ef"}, + {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:0439f5e9b10ebe6177981bac9e219096ec970ac6ec215bef055279802ba50601"}, + {file = "pyside6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:032bad6b18a17fcbf4dddd0397f49b07f8aae7f1a45b7e4de7037bf7fd6e0edf"}, + {file = "pyside6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:65a59ad0bc92525639e3268d590948ce07a80ee97b55e7a9200db41d493cac31"}, ] [package.dependencies] -pywin32 = ">=223" +PySide6_Addons = "6.10.2" +PySide6_Essentials = "6.10.2" +shiboken6 = "6.10.2" [[package]] -name = "pyppmd" -version = "1.2.0" -description = "PPMd compression/decompression library" +name = "pyside6-addons" +version = "6.10.2" +description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" optional = false -python-versions = ">=3.9" +python-versions = "<3.15,>=3.9" groups = ["main"] files = [ - {file = "pyppmd-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4a25d8b2a71e0cc6f34475c36450e905586b13d0c88fb28471655c215f370908"}, - {file = "pyppmd-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9dd8a6261152591a352d91e5e52c16b81fa760f64c361a7afb24a1f3b5e048"}, - {file = "pyppmd-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2cd2694f43720fa1304c1fa31b8a1e7d80162f045e91569fb93473277c2747b8"}, - {file = "pyppmd-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0354919ab0f4065d76c64ad0dc21f14116651a2124cf4915b96c4e76d9caf470"}, - {file = "pyppmd-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:416c15576924ff9d2852fbe53d162c946e0466ce79d8a03a058e6f09a54934f0"}, - {file = "pyppmd-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dcdd5bf53f562af2a9be76739be69c9de080dfa59a4c4a8bcc4a163f9c5cb53e"}, - {file = "pyppmd-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c67196af6cfcc68e72a8fffbc332d743327bb9323cb7f3709e27185e743c7272"}, - {file = "pyppmd-1.2.0-cp310-cp310-win32.whl", hash = "sha256:d529c78382a2315db22c93e1c831231ee3fd2ad5a352f61496d72474558c6b00"}, - {file = "pyppmd-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f19285ae4dd20bb917c4fd177f0911847feb3abada91cec5fd5d9d5f1b9f3e0"}, - {file = "pyppmd-1.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:30068ed6da301f6ba25219f96d828f3c3a80ca227647571d21c7704301e095e6"}, - {file = "pyppmd-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a5f0b78d68620ffb51c46c34c9e0ec02c40bb68e903e6c3ce02870c528164af"}, - {file = "pyppmd-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5f1ee49b88fd2e58a144b1ae0da9c2fe0dabc1962531da9475badbed6fba61fc"}, - {file = "pyppmd-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c98697fea3f3baf5ffc759fd41c766d708ff3fba7379776031077527873ce4ac"}, - {file = "pyppmd-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a3087d7ee6fc35db0bfecabd1df4615f2a9d58a56af61f5fc18b9ce2b379cbf"}, - {file = "pyppmd-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fe10feb24a92e673b68aca5d945564232d09e25a4e185899e0c657096ae695"}, - {file = "pyppmd-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aa40c982d1df515cd4cb366d3e1ae95ce22f3c20e6b8b2d31aa492679f7ad78c"}, - {file = "pyppmd-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a5c03dd85da64a237c601dd690d8eb95951b7c2eef91b89e110eb208010c6035"}, - {file = "pyppmd-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c577f3dadd514979255e9df6eb89a63409d0e91855bb8c0969ffcd67d5d4f124"}, - {file = "pyppmd-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29dfb7aaf4b49ebc09d726fcdeabbce1cb21e9cf3a055244bb1384b8b51dd3b"}, - {file = "pyppmd-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:bf26c2def22322135fbaaa3de3c0963062c1835bd43d595478e3a2a674466a1a"}, - {file = "pyppmd-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d28cc9febcf37f2ff08b9e25d472de529e8973119c0a3279603b1915c809dd45"}, - {file = "pyppmd-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0f07d5376e1f699d09fbb9139562e5b72a347100aecaa73b688fa08461b3c118"}, - {file = "pyppmd-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:874f52eae03647b653aa34476f4e23c4c30458245c0eb7aa7fb290940abbd5b9"}, - {file = "pyppmd-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abafffb3d5b292924eafd8214ad80487400cf358c4e9dc2ac6c21d2c651c5ee2"}, - {file = "pyppmd-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e955de43991346d4ccb28a74fb4c80cadecf72a6724705301fe1adb569689fe"}, - {file = "pyppmd-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14ed0846c3bcee506555cd943db950d5787a6ffa1089e05deced010759ef1fe5"}, - {file = "pyppmd-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3caef2fb93a63d696b21e5ff72cb2955687b5dfcbed1938936334f9f7737fcd3"}, - {file = "pyppmd-1.2.0-cp312-cp312-win32.whl", hash = "sha256:011c813389476e84387599ad4aa834100e888c6608a6e7d6f07ea7cbac8a8e65"}, - {file = "pyppmd-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:42c7c9050b44b713676d255f0c212b8ff5c0463821053960c89292cf6b6221cc"}, - {file = "pyppmd-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:5768bff11936047613bcb91ee466f21779efc24360bd7953bd338b32da88577a"}, - {file = "pyppmd-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4aa8ffca1727872923d2673045975bca571eb810cf14a21d048648da5237369b"}, - {file = "pyppmd-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6dc00f0ce9f79e1c1c87d9998220a714ab8216873b6c996776b88ab23efe05ac"}, - {file = "pyppmd-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d437881763ffd0d19079402f50e7f4aad5895e3cd5312d095edef0b32dac3aef"}, - {file = "pyppmd-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c763f2e3a011d5e96dfa0195f38accce9a14d489725a3d3641a74addbb5b72"}, - {file = "pyppmd-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38e3835a1951d18dd273000c870a4eb2804c20c400aa3c72231449f300cedf19"}, - {file = "pyppmd-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c76b8881fc087e70338b1cccd452bd12566206587a0d0d8266ba2833be902194"}, - {file = "pyppmd-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8b43e299310e27af5a4bc505bcc87d10cfc38ae28e5ed1f6a779d811705e5ad6"}, - {file = "pyppmd-1.2.0-cp313-cp313-win32.whl", hash = "sha256:4b3249256f8a7ecdd36477f277b232a46ee2c8ca280b23faaeacb7f50cab949a"}, - {file = "pyppmd-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:625896f5da7038fe7145907b68b0b58f7c13b88ad6bbfdc1c20c05654c17fa6c"}, - {file = "pyppmd-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:bec8abbf1edb0300c0a2e4f1bbad6a96154de3e507a2b054a0b1187f1c2e4982"}, - {file = "pyppmd-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b5c3284be4dccebb87d81c14b148c81e035356cd01a29889736c75672f6187d"}, - {file = "pyppmd-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40bfa26fdb3332a6a8d90fe1f6e0d9f489505a014911b470d66f2f79caea6d61"}, - {file = "pyppmd-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75b173bbc9164cdc6fb257d3480269cc26b1eb102ad72281a98cf90e0f7dc860"}, - {file = "pyppmd-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91534eb8c9c0bff9d6c6ec5eb5119a583d31bb9f8cf208d5a925b4e2293c9a7b"}, - {file = "pyppmd-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edc4fcd928bf6219bcddb8230a5830e33a35b684b16ca3e8d1357b17029a9ef7"}, - {file = "pyppmd-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5ff515c2c3544096fe524f341c244787d6449b36692d27131bf74d5075e5c83b"}, - {file = "pyppmd-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:af9be87228cba6b543531260f44675a23b4a1527158a44162dce186157cb13d9"}, - {file = "pyppmd-1.2.0-cp39-cp39-win32.whl", hash = "sha256:3674b5eba0e312b9af987ec7e6af59248f54db9a7f5ca63add5365d6c6639e9e"}, - {file = "pyppmd-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:cff27496fd164b587f150abba9524cae81629adbd2e9472f09e7b2b24b2d4939"}, - {file = "pyppmd-1.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:c9d0f5a903045ee6b399f5fb308e192e39f8f1f551b61441a595676d95dc76ad"}, - {file = "pyppmd-1.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86e252979fc5ae2492ebb46ed0eed0625a46a2cce519c4616b870eab58d77fb7"}, - {file = "pyppmd-1.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9095d8b098ce8cb5c1e404843a16e5167fb5bdebb4d6aed259d43dd2d73cfca3"}, - {file = "pyppmd-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:064307c7fec7bdf3da63f5e28c0f1c5cb5c9bf888c1b268c6df3c131391ab345"}, - {file = "pyppmd-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c012c17a53b6d9744e0514b17b0c4169c5f21fb54b4db7a0119bc2d7b3fcc609"}, - {file = "pyppmd-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0877758ffa73b2e9d2f93b698e17336a4d8acab8d9a3d17cd7960aec08347387"}, - {file = "pyppmd-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac0960d2d0a1738af3ca3f27c6ed6eead38518d77875a47b2b4aae90ae933f4"}, - {file = "pyppmd-1.2.0.tar.gz", hash = "sha256:cc04af92f1d26831ec96963439dfb27c96467b5452b94436a6af696649a121fd"}, + {file = "pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:0de7d0c9535e17d5e3b634b61314a1867f3b0f6d35c3d7cdc99efc353192faff"}, + {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:030a851163b51dbf0063be59e9ddb6a9e760bde89a28e461ccc81a224d286eaf"}, + {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:fcee0373e3fd7b98f014094e5e37b4a39e4de7c5a47c13f654a7d557d4a426ad"}, + {file = "pyside6_addons-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:c20150068525a17494f3b6576c5d61c417cf9a5870659e29f5ebd83cd20a78ea"}, + {file = "pyside6_addons-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:3d18db739b46946ba7b722d8ad4cc2097135033aa6ea57076e64d591e6a345f3"}, ] -[package.extras] -check = ["check-manifest", "flake8", "flake8-black", "flake8-isort", "mypy (>=1.10.0)", "pygments", "readme-renderer"] -docs = ["sphinx", "sphinx_rtd_theme"] -fuzzer = ["atheris", "hypothesis"] -test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] +[package.dependencies] +PySide6_Essentials = "6.10.2" +shiboken6 = "6.10.2" + +[[package]] +name = "pyside6-essentials" +version = "6.10.2" +description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" +optional = false +python-versions = "<3.15,>=3.9" +groups = ["main"] +files = [ + {file = "pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:1dee2cb9803ff135f881dadeb5c0edcef793d1ec4f8a9140a1348cecb71074e1"}, + {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:660aea45bfa36f1e06f799b934c2a7df963bd31abc5083e8bb8a5bfaef45686b"}, + {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:c2b028e4c6f8047a02c31f373408e23b4eedfd405f56c6aba8d0525c29472835"}, + {file = "pyside6_essentials-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:0741018c2b6395038cad4c41775cfae3f13a409e87995ac9f7d89e5b1fb6b22a"}, + {file = "pyside6_essentials-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:db5f4913648bb6afddb8b347edae151ee2378f12bceb03c8b2515a530a4b38d9"}, +] + +[package.dependencies] +shiboken6 = "6.10.2" [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, - {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" +iniconfig = ">=1.0.1" +packaging = ">=22" pluggy = ">=1.5,<2" pygments = ">=2.7.2" @@ -3548,14 +3415,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, - {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, + {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, + {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, ] [package.extras] @@ -3702,113 +3569,6 @@ files = [ [package.dependencies] pyyaml = "*" -[[package]] -name = "pyzstd" -version = "0.18.0" -description = "Python bindings to Zstandard (zstd) compression library." -optional = false -python-versions = ">=3.5" -groups = ["main"] -files = [ - {file = "pyzstd-0.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79bb84d866bf57ad2c4bc6b8247628b38e965c4f66288f887bf90f546a42ae04"}, - {file = "pyzstd-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0576c48e2f7a2c457538414a6197397c343b1bf5bfe9332b049afd0366c0c92"}, - {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7702484795ee3c16c48a03d990123e833f1e1d6baabbe9a53256238eb04cbc"}, - {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c412ac29a9ebb76c8c40f2df146327b460ce184bbbdaa5bc9257317dce4caa8"}, - {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:36baae4201196c2ec6567faf4a3f19c68211efc2fca30836c885b848ed057f66"}, - {file = "pyzstd-0.18.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f6d9c8a535af243c5a19f2d66c3733595ab633e00b97237d877e70e8389edc5"}, - {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a533550740ce8c721aae27b377fb1160df68a9f457f16015ec8e47547a033dfc"}, - {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdd76049c8ccbb98276cfa78d807b4a497ec6bad2603361eceae993c6130e5bf"}, - {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09b73fe07a8d81898ef1575cb3063816168abb3305c1a9f30110383b61a4ee92"}, - {file = "pyzstd-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6baf9fd75d0af4f5d677b6e2d8dd3deb359c4ec2250c8536fe5ea48fd9305199"}, - {file = "pyzstd-0.18.0-cp310-cp310-win32.whl", hash = "sha256:c0634ab42226d2ad96c94d57fd242df2ca9417350c2969eb97c8c61d9574ba69"}, - {file = "pyzstd-0.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:ec99569321a99b9868666c85a5846151f9a16b6a222b59b2570e2ddeefd4d80c"}, - {file = "pyzstd-0.18.0-cp310-cp310-win_arm64.whl", hash = "sha256:85371149cc1d8168461981084438b9f2f139c1699e989fef44562f7504ba0632"}, - {file = "pyzstd-0.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:848914835a8a984d4c5fad2355dc66f0aca979b35ec22753c9e694be8e98403c"}, - {file = "pyzstd-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3938fea87fe83113b5d8ec2925bb265b4c540e374bb0ec73e5528de58d68c393"}, - {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9af4bcde7dde46ca7e82a4c6f5fda1760bcbfd15525dbea36fe625263ef06b5e"}, - {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15d9419d173d26de25342235256aba363190e48e3fd8a8988420a26221b45320"}, - {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b84f75f0494087afad31363e80a3463d1f32a0a6265f1a24660e6422b2b6fa6"}, - {file = "pyzstd-0.18.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cfcdf0e46020bda2e98814464ca3ae830da83937c4c61776bf8835c7094214e"}, - {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8551b6bc3690fb76e730967a628b6aab0d9331c38a41f5cddb546be994771191"}, - {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6883b47a4d5d5489890e24e74ef14c1f16dcd68bb326b86911ae0e254e33e4b7"}, - {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929dec930296362ce03fee81877fa93a68ca4de3af75fdfa96ecbe0e366b2ee3"}, - {file = "pyzstd-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:278c80fdeaf857b620295cc815a31f6478fcb217d476ac889985a43b2b67e9bd"}, - {file = "pyzstd-0.18.0-cp311-cp311-win32.whl", hash = "sha256:0d1b678644894e49b5a448f02eebe0ac31bde6f51813168f5ff223d7212e1974"}, - {file = "pyzstd-0.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:8285a464aed201b166bb0d2f4667485b61b607cf89f12943b1f21f7e84cb4550"}, - {file = "pyzstd-0.18.0-cp311-cp311-win_arm64.whl", hash = "sha256:942badf996589e5ab6cbdd0f7dd33f5dc2cd7ed0b65441c96b9a12ffa7700d51"}, - {file = "pyzstd-0.18.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5eef13ee3e230e50c01b288d581664e8758f7b831271f6f32cfc29823a6ab365"}, - {file = "pyzstd-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f78d6ef80d2f355b5bc1a897e9aa58659e85170b3fa268f3211c4979c768264c"}, - {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:394175aeeb4e2255ff5340b32f6db79375b3ffb25514fe4c1439015a7f335ec2"}, - {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3250c551f526d3b966cf4a2199a8d9538dc5c7083b7a26a45f305f8f2ab20a06"}, - {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a99ca80053ca37be21f05f6c4152c70777e0eface72b08277cb4b10b6d286e79"}, - {file = "pyzstd-0.18.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5dc4488536e87ff0aac698b9cd65f2913ac87417b3952d80be32463c8e95cc35"}, - {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c12da158f6ec1180be0a3d6f531050dfc1357a25e5d0fd8dd99d4506d2a3f448"}, - {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f9a7d6bff36dfbe87dce1730e4b70d6ab49058a6f8ea22e85b33642491a2d053"}, - {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0f56086bf8019f7c809a406dcc182ce0fb0d3623a9edf351ed80dbb484514613"}, - {file = "pyzstd-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1eb69217ad9b760537e93f2d578c7927b788a9cac0e2104e536855a2797b5b09"}, - {file = "pyzstd-0.18.0-cp312-cp312-win32.whl", hash = "sha256:05ce49412c7aef970e0a6be8e9add4748bc474a7f13533a14555642022f871e9"}, - {file = "pyzstd-0.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:e951c3013b9df479cff758d578b83837b2531d02fb6c3e59166a756795697e19"}, - {file = "pyzstd-0.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:33b54781c66a86e33c93c89ae426811d0aa35a216a23116fc5d5162449284305"}, - {file = "pyzstd-0.18.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:65117997d1e10e9b41336c90c2c4877c8d27533f753272805ff39df15fd5298a"}, - {file = "pyzstd-0.18.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8550efbfb5944343666d0e79d6a3687adcbeb4dbf17aa743146a25e72d12d47f"}, - {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac61854c4a77df66695540549a89f4c67039e4181a9158b8646425f1d56d947a"}, - {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4c453369483f67480f86d67a7b63ef22827db65e7f0d4bec7992bb81751a94b9"}, - {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ef4b757b2df808ac15058fc2aa41e07d93843ee5a95629ff51eb6e8f1950951"}, - {file = "pyzstd-0.18.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b42529770febd331e23c5e8a68e9899acb0cc0806ee4c970354806c0ceeec6c7"}, - {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f54d13c269cdc37d2f73c9b3e70c6d2bb168dec768a472d54c2ed830bb19fb9"}, - {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6686460ca4be536dca1b6f2f80055f383a78e92e68e03a14806428572c4fdba"}, - {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8da3978d7de9095cacc5089bd0c435ab84ebd127e0979cd31fa1b216111644af"}, - {file = "pyzstd-0.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1ebc87e6e50547cff97e07c3fed9999d79b6327c9c4143c3049a7cfeacb2cdba"}, - {file = "pyzstd-0.18.0-cp313-cp313-win32.whl", hash = "sha256:2dd203f2534b16dea2761394fda4e0f3c465a5109ae6450bdaada67e6ac14a45"}, - {file = "pyzstd-0.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:98f43488f88b859291d6bdc51cc7793d1eab17aa9382b17d762944bbb8567c98"}, - {file = "pyzstd-0.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:cff8922e25e19d8fbd95b53f451e637bc80e826ab53c8777a885d4e99d1c0c2d"}, - {file = "pyzstd-0.18.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:67f795ec745cfd6930cdaf5118fcdd8d87ce02b07b254d37efe75afd33ce9917"}, - {file = "pyzstd-0.18.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a8a589673b9b417a084e393f18d09a16b67b87a80f80da6d3b4f84dd983c9b3d"}, - {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdaee8c33f96a6568225e821e6cc33045917628ae0bc7d8d3855332085c1aa7c"}, - {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42bf45d8e835d7c9c0bef98ff703143a5129edf09ef6c3b757037cbf79eabcaa"}, - {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f4dff2a15e2047baea9359d3a547dee80f61887f17e0f23190b4b932fd617e4"}, - {file = "pyzstd-0.18.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ed87932d6c534fc8921f7d44a4dadb32881e10ebc68935175a2cba254f5cc83"}, - {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7d08a372b2b7fa1fd24217424e13d3d794e01299c43c8bd55f50934ef0785779"}, - {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:e8403108172e24622f51732a336a89fe32bf3842965e0dc677c65df3a562f3ad"}, - {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5604eeb7f00ec308b7e878dae92abfc4eee2e5d238765a62d4fadc0d57bbbff3"}, - {file = "pyzstd-0.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6b300c5240409f1e7ab9972ab2a880a1949447d8414dbc11d89c10bfcb31aa5"}, - {file = "pyzstd-0.18.0-cp314-cp314-win32.whl", hash = "sha256:83f4fe1409a59c45a5e6fccb4d451e1e3dd03a5fabebd2dd6ba651468f54025e"}, - {file = "pyzstd-0.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:73c3dcd9a16f1669ed6eef0dad1d840b7dd6070ab7d48719171ca691101e7975"}, - {file = "pyzstd-0.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:61333bbb337b9746284624ed14f6238838dfae1e395691ba49f227015374f760"}, - {file = "pyzstd-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bccd16621016b83c2d5d40408806a841bbca2860370dca5ef0e3db005417aca"}, - {file = "pyzstd-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c7ee6747541594a5851bae720d5ab070ba9ef644df779507f35819ea61fd83fd"}, - {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea0d70b4ec72b9d5feae4ec665ef8a4cd48f442921f2100117229c900a5a713"}, - {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d581aeeba9a3ed13e304b0efc27efdf310b58c1e69ebb99a08e0eeea3a392310"}, - {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d582d2fab7cc3e7606c2b09093f914e6e8b942ec52aa992a3a25d9d3ed7ba295"}, - {file = "pyzstd-0.18.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a25a72afa7d66d47a881e475ffe88d9961b36052bf6a512af3b84de22b20d41f"}, - {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5b4feed895f32b314f2b3aa3ba6a4e0ce903c6764f31ad78e68b6c3fa31415ac"}, - {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:20d9524adbc4efc8a1680e59cc325bc73ff56bf70bb54d233c3540efcb7bf476"}, - {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:72c25d14217854883b571f101253d39443ea2f226f85cf3223b4d4a4d644618d"}, - {file = "pyzstd-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c335605ac7d018ca2d4d68cc0bac10e3c4ccf8e9686972dfc569a4df53f7a8d3"}, - {file = "pyzstd-0.18.0-cp39-cp39-win32.whl", hash = "sha256:64ebf9bd8065388d778c4ab6d9c4e913c00633abcfbf55236202dd0398520cc0"}, - {file = "pyzstd-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a32751ac634eb685bec42935b0f6e494f018843da09596da3f2a0072ae8273b"}, - {file = "pyzstd-0.18.0-cp39-cp39-win_arm64.whl", hash = "sha256:6b64efb254fdc3c90ed4c74185beee62c24e517288aacfb3abd95c127e6f8f52"}, - {file = "pyzstd-0.18.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:35934369fcdfde6fb932f88fa441337c8ddaf4b08e7b0b12952010f0ba2082f7"}, - {file = "pyzstd-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:55b8e12c9657359a697440e88a8535d1a771025e5d8f1c3087ad69ba11bee6d2"}, - {file = "pyzstd-0.18.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134d33d3e56b5083c8f827b63254c2abf85d6ace2b323e69d28e3954b5b71883"}, - {file = "pyzstd-0.18.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6c4bffa0157ef9e5cfa32413a5a79448e5affadece4982df274f1b5aae3a680"}, - {file = "pyzstd-0.18.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8c36824d94cf77997a899b60886cc2be3ac969083f1d74eb4dd4127234ba50a4"}, - {file = "pyzstd-0.18.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:788e0889db436cd6d16a3b490006ab80a913d8ce6f46db127f1888066ff4560b"}, - {file = "pyzstd-0.18.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e70b7c36a40d7f946bf6391a206374b057299735d366fad6524d3b9f392441f"}, - {file = "pyzstd-0.18.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:571c5f71622943387370f76de8cc0de3d5c6217ab0f38386cb127665e4e09275"}, - {file = "pyzstd-0.18.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de0b730f374b583894d58b79cff76569540baf1e84bc493be191d3128b58e559"}, - {file = "pyzstd-0.18.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b32184013f33dba2fabcdda89f2a83289f5b717a0c2477cda764e53fdafec7ee"}, - {file = "pyzstd-0.18.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:27c281abfc2f13f19df92793f66e12cd0a19038ccbc02684af2a14bce664fdc4"}, - {file = "pyzstd-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7313f3a9bd2cb11158e5eaab3d5d2cd6b4582702e383a08ebb8273d0d45c3e49"}, - {file = "pyzstd-0.18.0-pp39-pypy39_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec4ae014abf835bd9995ee1b318fdf4e955ffb8439838373bdc19c80d51a541"}, - {file = "pyzstd-0.18.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94c2f15f0e67acf89bec97ea276f7a5ad4e6d0267f62f12424bf044a0de280a0"}, - {file = "pyzstd-0.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:898e41170fde5aa73105a0262572c286bafc5f24c7b4cf131168d9b198e4c586"}, - {file = "pyzstd-0.18.0.tar.gz", hash = "sha256:81b6851ab1ca2e5f2c709e896a1362e3065a64f271f43db77fb7d5e4a78e9861"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.13\""} - [[package]] name = "questionary" version = "2.1.1" @@ -3848,14 +3608,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "14.2.0" +version = "14.3.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, - {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, + {file = "rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69"}, + {file = "rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8"}, ] [package.dependencies] @@ -3865,155 +3625,41 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] -[[package]] -name = "scikit-image" -version = "0.25.2" -description = "Image processing in Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78"}, - {file = "scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063"}, - {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99"}, - {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09"}, - {file = "scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054"}, - {file = "scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17"}, - {file = "scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0"}, - {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173"}, - {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641"}, - {file = "scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b"}, - {file = "scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb"}, - {file = "scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed"}, - {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d"}, - {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824"}, - {file = "scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2"}, - {file = "scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da"}, - {file = "scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc"}, - {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341"}, - {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147"}, - {file = "scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f"}, - {file = "scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd"}, - {file = "scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde"}, -] - -[package.dependencies] -imageio = ">=2.33,<2.35.0 || >2.35.0" -lazy-loader = ">=0.4" -networkx = ">=3.0" -numpy = ">=1.24" -packaging = ">=21" -pillow = ">=10.1" -scipy = ">=1.11.4" -tifffile = ">=2022.8.12" - -[package.extras] -build = ["Cython (>=3.0.8)", "build (>=1.2.1)", "meson-python (>=0.16)", "ninja (>=1.11.1.1)", "numpy (>=2.0)", "pythran (>=0.16)", "spin (==0.13)"] -data = ["pooch (>=1.6.0)"] -developer = ["ipython", "pre-commit", "tomli ; python_version < \"3.11\""] -docs = ["PyWavelets (>=1.6)", "dask[array] (>=2023.2.0)", "intersphinx-registry (>=0.2411.14)", "ipykernel", "ipywidgets", "kaleido (==0.2.1)", "matplotlib (>=3.7)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=2.0)", "plotly (>=5.20)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.16)", "pytest-doctestplus", "scikit-learn (>=1.2)", "seaborn (>=0.11)", "sphinx (>=8.0)", "sphinx-copybutton", "sphinx-gallery[parallel] (>=0.18)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] -optional = ["PyWavelets (>=1.6)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=1.1.1)", "dask[array] (>=2023.2.0)", "matplotlib (>=3.7)", "pooch (>=1.6.0)", "pyamg (>=5.2)", "scikit-learn (>=1.2)"] -test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=8)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] - -[[package]] -name = "scipy" -version = "1.16.2" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88"}, - {file = "scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa"}, - {file = "scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1"}, - {file = "scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e"}, - {file = "scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e"}, - {file = "scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c"}, - {file = "scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472"}, - {file = "scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351"}, - {file = "scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936"}, - {file = "scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff"}, - {file = "scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c"}, - {file = "scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9"}, - {file = "scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779"}, - {file = "scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b"}, -] - -[package.dependencies] -numpy = ">=1.25.2,<2.6" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest (>=8.0.0)", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - [[package]] name = "setuptools" -version = "80.9.0" +version = "82.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0"}, + {file = "setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] + +[[package]] +name = "shiboken6" +version = "6.10.2" +description = "Python/C++ bindings helper module" +optional = false +python-versions = "<3.15,>=3.9" +groups = ["main"] +files = [ + {file = "shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:3bd4e94e9a3c8c1fa8362fd752d399ef39265d5264e4e37bae61cdaa2a00c8c7"}, + {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ace0790032d9cb0adda644b94ee28d59410180d9773643bb6cf8438c361987ad"}, + {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:f74d3ed1f92658077d0630c39e694eb043aeb1d830a5d275176c45d07147427f"}, + {file = "shiboken6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:10f3c8c5e1b8bee779346f21c10dbc14cff068f0b0b4e62420c82a6bf36ac2e7"}, + {file = "shiboken6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:20c671645d70835af212ee05df60361d734c5305edb2746e9875c6a31283f963"}, +] [[package]] name = "six" @@ -4041,26 +3687,26 @@ files = [ [[package]] name = "soupsieve" -version = "2.8" +version = "2.8.3" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, - {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, + {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, + {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, ] [[package]] name = "termcolor" -version = "3.2.0" +version = "3.3.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6"}, - {file = "termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58"}, + {file = "termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5"}, + {file = "termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"}, ] [package.extras] @@ -4078,79 +3724,61 @@ files = [ {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, ] -[[package]] -name = "tifffile" -version = "2025.10.16" -description = "Read and write TIFF files" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "tifffile-2025.10.16-py3-none-any.whl", hash = "sha256:41463d979c1c262b0a5cdef2a7f95f0388a072ad82d899458b154a48609d759c"}, - {file = "tifffile-2025.10.16.tar.gz", hash = "sha256:425179ec7837ac0e07bc95d2ea5bea9b179ce854967c12ba07fc3f093e58efc1"}, -] - -[package.dependencies] -numpy = "*" - -[package.extras] -all = ["defusedxml", "fsspec", "imagecodecs (>=2024.12.30)", "kerchunk", "lxml", "matplotlib", "zarr (>=3.1.3)"] -codecs = ["imagecodecs (>=2024.12.30)"] -plot = ["matplotlib"] -test = ["cmapfile", "czifile", "dask", "defusedxml", "fsspec", "imagecodecs", "kerchunk", "lfdfiles", "lxml", "ndtiff", "oiffile", "psdtags", "pytest", "requests", "roifile", "xarray", "zarr (>=3.1.3)"] -xml = ["defusedxml", "lxml"] -zarr = ["fsspec", "kerchunk", "zarr (>=3.1.3)"] - [[package]] name = "tomli" -version = "2.3.0" +version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] [[package]] @@ -4167,14 +3795,14 @@ files = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, - {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, + {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, + {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, ] [package.dependencies] @@ -4201,14 +3829,14 @@ files = [ [[package]] name = "types-requests" -version = "2.32.4.20250913" +version = "2.32.4.20260107" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}, - {file = "types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}, + {file = "types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d"}, + {file = "types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f"}, ] [package.dependencies] @@ -4243,42 +3871,42 @@ typing-extensions = ">=4.12.0" [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "20.35.3" +version = "20.37.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a"}, - {file = "virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44"}, + {file = "virtualenv-20.37.0-py3-none-any.whl", hash = "sha256:5d3951c32d57232ae3569d4de4cc256c439e045135ebf43518131175d9be435d"}, + {file = "virtualenv-20.37.0.tar.gz", hash = "sha256:6f7e2064ed470aa7418874e70b6369d53b66bcd9e9fd5389763e96b6c94ccb7c"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = {version = ">=3.24.2,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +docs = ["furo (>=2023.7.26)", "pre-commit-uv (>=4.1.4)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinx-autodoc-typehints (>=3.6.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2025.12.21.14)", "sphinxcontrib-mermaid (>=2)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "pytest-xdist (>=3.5)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "watchdog" @@ -4340,30 +3968,33 @@ bracex = ">=2.1.1" [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.6.0" description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, - {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, + {file = "wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad"}, + {file = "wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159"}, ] [[package]] name = "wheel" -version = "0.45.1" -description = "A built-package format for Python" +version = "0.46.3" +description = "Command line tool for manipulating wheel files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, - {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, + {file = "wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d"}, + {file = "wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803"}, ] +[package.dependencies] +packaging = ">=24.0" + [package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] +test = ["pytest (>=6.0.0)", "setuptools (>=77)"] [[package]] name = "win32-setctime" @@ -4383,95 +4014,91 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "wrapt" -version = "1.17.3" +version = "2.1.1" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, - {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, - {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, - {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, - {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, - {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, - {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, - {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, - {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, - {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, - {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, - {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, - {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, - {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, - {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, - {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, - {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, - {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, - {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, - {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, - {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, - {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, - {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, - {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, - {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, - {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, - {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, - {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, - {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, - {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, - {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, - {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, - {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, - {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, - {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, - {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, - {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, - {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, - {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, - {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, - {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, - {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, - {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, - {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, - {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, - {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, - {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, - {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, - {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, + {file = "wrapt-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e927375e43fd5a985b27a8992327c22541b6dede1362fc79df337d26e23604f"}, + {file = "wrapt-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c99544b6a7d40ca22195563b6d8bc3986ee8bb82f272f31f0670fe9440c869"}, + {file = "wrapt-2.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2be3fa5f4efaf16ee7c77d0556abca35f5a18ad4ac06f0ef3904c3399010ce9"}, + {file = "wrapt-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67c90c1ae6489a6cb1a82058902caa8006706f7b4e8ff766f943e9d2c8e608d0"}, + {file = "wrapt-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05c0db35ccffd7480143e62df1e829d101c7b86944ae3be7e4869a7efa621f53"}, + {file = "wrapt-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0c2ec9f616755b2e1e0bf4d0961f59bb5c2e7a77407e7e2c38ef4f7d2fdde12c"}, + {file = "wrapt-2.1.1-cp310-cp310-win32.whl", hash = "sha256:203ba6b3f89e410e27dbd30ff7dccaf54dcf30fda0b22aa1b82d560c7f9fe9a1"}, + {file = "wrapt-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f9426d9cfc2f8732922fc96198052e55c09bb9db3ddaa4323a18e055807410e"}, + {file = "wrapt-2.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:69c26f51b67076b40714cff81bdd5826c0b10c077fb6b0678393a6a2f952a5fc"}, + {file = "wrapt-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c366434a7fb914c7a5de508ed735ef9c133367114e1a7cb91dfb5cd806a1549"}, + {file = "wrapt-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d6a2068bd2e1e19e5a317c8c0b288267eec4e7347c36bc68a6e378a39f19ee7"}, + {file = "wrapt-2.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:891ab4713419217b2aed7dd106c9200f64e6a82226775a0d2ebd6bef2ebd1747"}, + {file = "wrapt-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef36a0df38d2dc9d907f6617f89e113c5892e0a35f58f45f75901af0ce7d81"}, + {file = "wrapt-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76e9af3ebd86f19973143d4d592cbf3e970cf3f66ddee30b16278c26ae34b8ab"}, + {file = "wrapt-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff562067485ebdeaef2fa3fe9b1876bc4e7b73762e0a01406ad81e2076edcebf"}, + {file = "wrapt-2.1.1-cp311-cp311-win32.whl", hash = "sha256:9e60a30aa0909435ec4ea2a3c53e8e1b50ac9f640c0e9fe3f21fd248a22f06c5"}, + {file = "wrapt-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7d79954f51fcf84e5ec4878ab4aea32610d70145c5bbc84b3370eabfb1e096c2"}, + {file = "wrapt-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:d3ffc6b0efe79e08fd947605fd598515aebefe45e50432dc3b5cd437df8b1ada"}, + {file = "wrapt-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab8e3793b239db021a18782a5823fcdea63b9fe75d0e340957f5828ef55fcc02"}, + {file = "wrapt-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c0300007836373d1c2df105b40777986accb738053a92fe09b615a7a4547e9f"}, + {file = "wrapt-2.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2b27c070fd1132ab23957bcd4ee3ba707a91e653a9268dc1afbd39b77b2799f7"}, + {file = "wrapt-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b0e36d845e8b6f50949b6b65fc6cd279f47a1944582ed4ec8258cd136d89a64"}, + {file = "wrapt-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4aeea04a9889370fcfb1ef828c4cc583f36a875061505cd6cd9ba24d8b43cc36"}, + {file = "wrapt-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d88b46bb0dce9f74b6817bc1758ff2125e1ca9e1377d62ea35b6896142ab6825"}, + {file = "wrapt-2.1.1-cp312-cp312-win32.whl", hash = "sha256:63decff76ca685b5c557082dfbea865f3f5f6d45766a89bff8dc61d336348833"}, + {file = "wrapt-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b828235d26c1e35aca4107039802ae4b1411be0fe0367dd5b7e4d90e562fcbcd"}, + {file = "wrapt-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:75128507413a9f1bcbe2db88fd18fbdbf80f264b82fa33a6996cdeaf01c52352"}, + {file = "wrapt-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9646e17fa7c3e2e7a87e696c7de66512c2b4f789a8db95c613588985a2e139"}, + {file = "wrapt-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:428cfc801925454395aa468ba7ddb3ed63dc0d881df7b81626cdd433b4e2b11b"}, + {file = "wrapt-2.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5797f65e4d58065a49088c3b32af5410751cd485e83ba89e5a45e2aa8905af98"}, + {file = "wrapt-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a2db44a71202c5ae4bb5f27c6d3afbc5b23053f2e7e78aa29704541b5dad789"}, + {file = "wrapt-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8d5350c3590af09c1703dd60ec78a7370c0186e11eaafb9dda025a30eee6492d"}, + {file = "wrapt-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d9b076411bed964e752c01b49fd224cc385f3a96f520c797d38412d70d08359"}, + {file = "wrapt-2.1.1-cp313-cp313-win32.whl", hash = "sha256:0bb7207130ce6486727baa85373503bf3334cc28016f6928a0fa7e19d7ecdc06"}, + {file = "wrapt-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:cbfee35c711046b15147b0ae7db9b976f01c9520e6636d992cd9e69e5e2b03b1"}, + {file = "wrapt-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:7d2756061022aebbf57ba14af9c16e8044e055c22d38de7bf40d92b565ecd2b0"}, + {file = "wrapt-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4814a3e58bc6971e46baa910ecee69699110a2bf06c201e24277c65115a20c20"}, + {file = "wrapt-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:106c5123232ab9b9f4903692e1fa0bdc231510098f04c13c3081f8ad71c3d612"}, + {file = "wrapt-2.1.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1a40b83ff2535e6e56f190aff123821eea89a24c589f7af33413b9c19eb2c738"}, + {file = "wrapt-2.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:789cea26e740d71cf1882e3a42bb29052bc4ada15770c90072cb47bf73fb3dbf"}, + {file = "wrapt-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ba49c14222d5e5c0ee394495a8655e991dc06cbca5398153aefa5ac08cd6ccd7"}, + {file = "wrapt-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ac8cda531fe55be838a17c62c806824472bb962b3afa47ecbd59b27b78496f4e"}, + {file = "wrapt-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:b8af75fe20d381dd5bcc9db2e86a86d7fcfbf615383a7147b85da97c1182225b"}, + {file = "wrapt-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:45c5631c9b6c792b78be2d7352129f776dd72c605be2c3a4e9be346be8376d83"}, + {file = "wrapt-2.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:da815b9263947ac98d088b6414ac83507809a1d385e4632d9489867228d6d81c"}, + {file = "wrapt-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aa1765054245bb01a37f615503290d4e207e3fd59226e78341afb587e9c1236"}, + {file = "wrapt-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:feff14b63a6d86c1eee33a57f77573649f2550935981625be7ff3cb7342efe05"}, + {file = "wrapt-2.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81fc5f22d5fcfdbabde96bb3f5379b9f4476d05c6d524d7259dc5dfb501d3281"}, + {file = "wrapt-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:951b228ecf66def855d22e006ab9a1fc12535111ae7db2ec576c728f8ddb39e8"}, + {file = "wrapt-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ddf582a95641b9a8c8bd643e83f34ecbbfe1b68bc3850093605e469ab680ae3"}, + {file = "wrapt-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fc5c500966bf48913f795f1984704e6d452ba2414207b15e1f8c339a059d5b16"}, + {file = "wrapt-2.1.1-cp314-cp314-win32.whl", hash = "sha256:4aa4baadb1f94b71151b8e44a0c044f6af37396c3b8bcd474b78b49e2130a23b"}, + {file = "wrapt-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:860e9d3fd81816a9f4e40812f28be4439ab01f260603c749d14be3c0a1170d19"}, + {file = "wrapt-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3c59e103017a2c1ea0ddf589cbefd63f91081d7ce9d491d69ff2512bb1157e23"}, + {file = "wrapt-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9fa7c7e1bee9278fc4f5dd8275bc8d25493281a8ec6c61959e37cc46acf02007"}, + {file = "wrapt-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c35e12e8215628984248bd9c8897ce0a474be2a773db207eb93414219d8469"}, + {file = "wrapt-2.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:94ded4540cac9125eaa8ddf5f651a7ec0da6f5b9f248fe0347b597098f8ec14c"}, + {file = "wrapt-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0af328373f97ed9bdfea24549ac1b944096a5a71b30e41c9b8b53ab3eec04a"}, + {file = "wrapt-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4ad839b55f0bf235f8e337ce060572d7a06592592f600f3a3029168e838469d3"}, + {file = "wrapt-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d89c49356e5e2a50fa86b40e0510082abcd0530f926cbd71cf25bee6b9d82d7"}, + {file = "wrapt-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:f4c7dd22cf7f36aafe772f3d88656559205c3af1b7900adfccb70edeb0d2abc4"}, + {file = "wrapt-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f76bc12c583ab01e73ba0ea585465a41e48d968f6d1311b4daec4f8654e356e3"}, + {file = "wrapt-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7ea74fc0bec172f1ae5f3505b6655c541786a5cabe4bbc0d9723a56ac32eb9b9"}, + {file = "wrapt-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e03b3d486eb39f5d3f562839f59094dcee30c4039359ea15768dc2214d9e07c"}, + {file = "wrapt-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fdf3073f488ce4d929929b7799e3b8c52b220c9eb3f4a5a51e2dc0e8ff07881"}, + {file = "wrapt-2.1.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cb4f59238c6625fae2eeb72278da31c9cfba0ff4d9cbe37446b73caa0e9bcf7"}, + {file = "wrapt-2.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f794a1c148871b714cb566f5466ec8288e0148a1c417550983864b3981737cd"}, + {file = "wrapt-2.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:95ef3866631c6da9ce1fc0f1e17b90c4c0aa6d041fc70a11bc90733aee122e1a"}, + {file = "wrapt-2.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66bc1b2446f01cbbd3c56b79a3a8435bcd4178ac4e06b091913f7751a7f528b8"}, + {file = "wrapt-2.1.1-cp39-cp39-win32.whl", hash = "sha256:1b9e08e57cabc32972f7c956d10e85093c5da9019faa24faf411e7dd258e528c"}, + {file = "wrapt-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e75ad48c3cca739f580b5e14c052993eb644c7fa5b4c90aa51193280b30875ae"}, + {file = "wrapt-2.1.1-cp39-cp39-win_arm64.whl", hash = "sha256:9ccd657873b7f964711447d004563a2bc08d1476d7a1afcad310f3713e6f50f4"}, + {file = "wrapt-2.1.1-py3-none-any.whl", hash = "sha256:3b0f4629eb954394a3d7c7a1c8cca25f0b07cefe6aa8545e862e9778152de5b7"}, + {file = "wrapt-2.1.1.tar.gz", hash = "sha256:5fdcb09bf6db023d88f312bd0767594b414655d58090fc1c46b3414415f67fac"}, ] +[package.extras] +dev = ["pytest", "setuptools"] + [[package]] name = "yarl" version = "1.22.0" @@ -4619,5 +4246,5 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" -python-versions = ">=3.12,<3.15" -content-hash = "66a8ac543cfd4352d4e1573499856c835c1d1a62f5482ef629bd865e4856a428" +python-versions = ">=3.14,<3.15" +content-hash = "64f161887655a5bbac62d7f32da658c61a2f849ba7e37ca5dba284ebbd5f0515" diff --git a/pyproject.toml b/pyproject.toml index f017601e..da9c66c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "proxyshop" version = "1.13.2" description = "Photoshop automation tool for generating high quality Magic the Gathering card renders." -authors = [{ name = "Investigamer", email = "freethoughtleft@gmail.com" }] +authors = [{name = "Investigamer", email = "freethoughtleft@gmail.com"}] license = "MPL-2.0" readme = "README.md" keywords = [ @@ -16,7 +16,7 @@ keywords = [ "magic the gathering", "playtest", ] -requires-python = ">=3.12,<3.15" +requires-python = ">=3.14,<3.15" [tool.poetry.urls] Changelog = "https://github.com/Investigamer/Proxyshop/blob/main/CHANGELOG.md" @@ -30,52 +30,54 @@ Sponsor = "https://patreon.com/mpcfill" include = 'src/../' [tool.poetry.dependencies] -photoshop-python-api = { git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation" } +photoshop-python-api = {git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation"} requests = "^2.32.5" -asynckivy = "^0.9.0" -Pillow = "^12.0.0" -kivy = "^2.3.1" +Pillow = "^12.1.1" typing-extensions = "^4.15.0" -limits = "^5.6.0" +limits = "^5.8.0" backoff = "^2.2.1" pathvalidate = "^3.3.1" -fonttools = "^4.60.1" +fonttools = "^4.61.1" pyyaml = "^6.0.3" -tqdm = "^4.67.1" -click = "^8.3.0" -tomli = "^2.3.0" +tqdm = "^4.67.3" +click = "^8.3.1" +tomli = "^2.4.0" yarl = "^1.22.0" -pydantic = "^2.12.3" -pydantic-settings = "^2.11.0" -omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } -hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } -rich = "^14.2.0" +pydantic = "^2.12.5" +pydantic-settings = "^2.13.0" +omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} +hexproof = {git = "https://github.com/pappnu/hexproof.git", branch = "dev"} +rich = "^14.3.2" pywin32 = "^311.0.0" +pyside6 = "^6.10.2" [tool.poetry.group.dev.dependencies] -pytest = "^8.4.2" -mypy = "^1.18.2" -commitizen = "^4.9.1" -setuptools = "^80.9.0" -matplotlib = "^3.10.7" -psd-tools = "^1.10.13" -pyclean = "^3.2.0" -pyinstaller = "^6.16.0" -pre-commit = "^4.3.0" +pytest = "^9.0.2" +mypy = "^1.19.1" +commitizen = "^4.13.7" +setuptools = "^82.0.0" +matplotlib = "^3.10.8" +psd-tools = "^1.12.1" +pyclean = "^3.5.0" +pyinstaller = "^6.19.0" +pre-commit = "^4.5.1" mkdocs = "^1.6.1" -mkdocs-material = "^9.6.22" -mkdocs-include-markdown-plugin = "^7.2.0" +mkdocs-material = "^9.7.1" +mkdocs-include-markdown-plugin = "^7.2.1" mkdocs-pymdownx-material-extras = "^2.8.0" mkdocs-minify-plugin = "^0.8.0" -mkdocstrings-python = "^1.18.2" -mkdocs-gen-files = "^0.5.0" +mkdocstrings-python = "^2.0.2" +mkdocs-gen-files = "^0.6.0" mkdocs-autolinks-plugin = "^0.7.1" mkdocs-same-dir = "^0.1.3" mkdocs-git-revision-date-plugin = "^0.3.2" -mkdocstrings = { extras = ["python"], version = "^0.30.1" } +mkdocstrings = {extras = ["python"], version = "^1.0.3"} memory-profiler = "^0.61.0" types-pywin32 = "^311.0.0.20251008" -types-requests = "^2.32.4.20250913" +types-requests = "^2.32.4.20260107" + +[tool.poetry.scripts] +proxyshop = 'main:launch_cli' [build-system] requires = ["poetry-core"] @@ -88,13 +90,12 @@ changelog_start_rev = 'v1.2.0' tag_format = "v$major.$minor.$patch" update_changelog_on_bump = true -[tool.poetry.scripts] -proxyshop = 'main:launch_cli' - [tool.ruff.lint] extend-select = [ - "I", # isort - "UP", # pyupgrade + # isort + "I", + # pyupgrade + "UP", ] ignore = ["F403", "UP015"] diff --git a/src/__init__.py b/src/__init__.py index 2950bebb..b77c6077 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,23 +2,11 @@ * Load Global Application State """ +from src._config import AppConfig +from src._state import AppConstants, AppEnvironment from src.utils.adobe import PhotoshopHandler from src.utils.threading import ThreadInitializedInstance -from ._config import AppConfig -from ._loader import ( - get_all_plugins, - get_all_templates, - get_template_map, - get_template_map_defaults, -) -from ._state import PATH, AppConstants, AppEnvironment - -""" -* Globally Loaded Objects -""" - - # Global environment object ENV = AppEnvironment() @@ -26,23 +14,10 @@ CON = AppConstants() # Global settings object -CFG = AppConfig(env=ENV) +CFG = AppConfig() # Global Photoshop handler APP = ThreadInitializedInstance(lambda: PhotoshopHandler(env=ENV)) -# Conditionally import the GUI console -if not ENV.HEADLESS: - from src.gui.console import GUIConsole as Console -else: - from .console import TerminalConsole as Console -CONSOLE = Console(cfg=CFG, env=ENV, app=APP) - -# Global plugins and templates -PLUGINS = get_all_plugins(con=CON, env=ENV) -TEMPLATES = get_all_templates(con=CON, env=ENV, plugins=PLUGINS) -TEMPLATE_MAP = get_template_map(templates=TEMPLATES) -TEMPLATE_DEFAULTS = get_template_map_defaults(TEMPLATE_MAP) - # Export objects -__all__ = ["APP", "CFG", "CON", "CONSOLE", "ENV", "PATH"] +__all__ = ["APP", "CFG", "CON", "ENV"] diff --git a/src/_config.py b/src/_config.py index 1fb9f1b1..c3d18855 100644 --- a/src/_config.py +++ b/src/_config.py @@ -2,17 +2,16 @@ * Global Settings Module """ +from enum import StrEnum from typing import Literal, overload -from omnitils.enums import StrConstant -from omnitils.metaclass import Singleton - -from src._loader import ConfigManager -from src._state import AppEnvironment +from src._loader import ConfigHandler, CustomConfigParser +from src._state import PATH from src.enums.settings import ( BorderColor, CollectorMode, CollectorPromo, + HasDefault, OutputFileType, ScryfallSorting, ScryfallUnique, @@ -24,12 +23,73 @@ class AppConfig: """Stores the current state of app and template settings. Can be changed within a template class to affect rendering behavior.""" - __metaclass__ = Singleton - - def __init__(self, env: AppEnvironment): + def __init__( + self, + app_config: ConfigHandler | None = None, + base_config: ConfigHandler | None = None, + user_config: ConfigHandler | None = None, + ): """Load initial settings values.""" - self.ENV = env - self.load() + self.app_config = app_config or ConfigHandler( + base_schema_path=PATH.SRC_DATA_CONFIG_APP, + schema_path=None, + ini_path=PATH.SRC_DATA_CONFIG_INI_APP, + ) + self.base_config = base_config or ConfigHandler( + base_schema_path=PATH.SRC_DATA_CONFIG_BASE, + schema_path=None, + ini_path=PATH.SRC_DATA_CONFIG_INI_BASE, + ) + self.load(user_config) + + # region Pre-rendering properties + + @property + def lang(self) -> str: + return self.app_config.get_str_setting( + "APP.DATA", "Scryfall.Language", default="en" + ) + + @property + def scry_sorting(self) -> ScryfallSorting: + return self.app_config.get_enum_setting( + "APP.DATA", + "Scryfall.Sorting", + ScryfallSorting, + default=ScryfallSorting.Released, + ) + + @property + def scry_ascending(self) -> bool: + return self.app_config.get_bool_setting( + "APP.DATA", "Scryfall.Ascending", default=False + ) + + @property + def scry_extras(self) -> bool: + return self.app_config.get_bool_setting( + "APP.DATA", "Scryfall.Extras", default=False + ) + + @property + def scry_unique(self) -> ScryfallUnique: + return self.app_config.get_enum_setting( + "APP.DATA", "Scryfall.Unique", ScryfallUnique, default=ScryfallUnique.Arts + ) + + @property + def manually_edit_card_data(self) -> bool: + return self.app_config.get_bool_setting( + "APP.DATA", "Manually.Edit.Card.Data", default=False + ) + + @property + def manual_text_editor(self) -> str: + return self.app_config.get_str_setting( + "APP.DATA", "Manual.Text.Editor", default='notepad "{}"' + ) + + # endregion Pre-rendering properties def update_definitions(self): """Updates the defined settings values using the currently loaded ConfigParser object.""" @@ -48,28 +108,9 @@ def update_definitions(self): ) # APP - DATA - self.lang = self.file.get("APP.DATA", "Scryfall.Language", fallback="en") self.use_printed_texts = self.file.getboolean( "APP.DATA", "Scryfall.Use.Printed.Texts", fallback=False ) - self.scry_sorting = self.get_option( - "APP.DATA", "Scryfall.Sorting", ScryfallSorting - ) - self.scry_ascending = self.file.getboolean( - "APP.DATA", "Scryfall.Ascending", fallback=False - ) - self.scry_extras = self.file.getboolean( - "APP.DATA", "Scryfall.Extras", fallback=False - ) - self.scry_unique = self.get_option( - "APP.DATA", "Scryfall.Unique", ScryfallUnique - ) - self.manually_edit_card_data = self.file.getboolean( - "APP.DATA", "Manually.Edit.Card.Data", fallback=False - ) - self.manual_text_editor = self.file.get( - "APP.DATA", "Manual.Text.Editor", fallback='notepad "{}"' - ) # APP - TEXT self.force_english_formatting = self.file.getboolean( @@ -77,13 +118,8 @@ def update_definitions(self): ) # APP - RENDER - self.skip_failed = self.file.getboolean( - "APP.RENDER", "Skip.Failed", fallback=False - ) - self.generative_fill = ( - False - if self.ENV.TEST_MODE - else self.file.getboolean("APP.RENDER", "Generative.Fill", fallback=False) + self.generative_fill = self.file.getboolean( + "APP.RENDER", "Generative.Fill", fallback=False ) self.select_variation = self.file.getboolean( "APP.RENDER", "Select.Variation", fallback=False @@ -133,7 +169,7 @@ def update_definitions(self): self.watermark_default = self.file.get( "BASE.WATERMARKS", "Default.Watermark", fallback="WOTC" ) - self.watermark_opacity = self.file.getint( + self.watermark_opacity = self.file.getfloat( "BASE.WATERMARKS", "Watermark.Opacity", fallback=30 ) self.enable_basic_watermark = self.file.getboolean( @@ -154,10 +190,6 @@ def update_definitions(self): "BASE.TEMPLATES", "Border.Color", BorderColor ) - self.backup_template = self.file.getboolean( - "BASE.TEMPLATES", "Backup.Template", fallback=False - ) - """ * Setting Utils """ @@ -166,7 +198,7 @@ def get_option( self, section: str, key: str, - enum_class: type[StrConstant], + enum_class: type[StrEnum], default: str | None = None, ) -> str: """Returns the current value of an "options" setting if that option exists in its StrEnum class. @@ -181,7 +213,11 @@ def get_option( Returns: Validated current value, or default value. """ - defa = default or enum_class.Default + defa: str = ( + default or str(enum_class.Default) + if isinstance(enum_class, HasDefault) + else "" + ) if self.file.has_section(section): option = self.file[section].get(key, fallback=defa) if option in enum_class: @@ -286,17 +322,22 @@ def get_float_setting( * Load ConfigParser Object """ - def load(self, config: ConfigManager | None = None) -> None: + def load(self, config: ConfigHandler | None = None) -> None: """Reload the config file and define new values Args: - config: ConfigManager to load from if provided, otherwise use app-wide configuration. + config: to load from if provided, otherwise use app-wide configuration. """ - # Invalidate file cache - if hasattr(self, "file"): - del self.file - - # Load provided or load fresh - config = config or ConfigManager() - self.file = config.get_config() + self.file = CustomConfigParser(default_section="", allow_no_value=True) + # Combine app and base/template configs + self.file.read_dict(self.app_config.setting_values) + self.file.read_dict((config if config else self.base_config).setting_values) self.update_definitions() + + def copy(self, config: ConfigHandler | None = None) -> AppConfig: + """Copy the config. + + Args: + config: to load from if provided, otherwise use app-wide configuration. + """ + return AppConfig(self.app_config, self.base_config, config) diff --git a/src/_loader.py b/src/_loader.py index 0ed1e99b..7c889c26 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -1,43 +1,49 @@ """ * Plugin and Template Loader -* Only import enums and utils """ -import os -import shutil -import tomllib from collections.abc import Callable -from concurrent.futures import Future, ThreadPoolExecutor from configparser import ConfigParser from contextlib import suppress +from enum import Enum from functools import cached_property +from json import load from pathlib import Path -from traceback import format_exc, print_tb +from threading import Lock +from traceback import format_exc, print_exc from types import ModuleType -from typing import Any, Literal, NotRequired, Optional, Self, TypedDict +from typing import Annotated, Any, Literal, NotRequired, Protocol, TypedDict, overload -import yaml import yarl from omnitils.api.gdrive import gdrive_download_file, gdrive_get_metadata -from omnitils.files import ensure_file, load_data_file, mkdir_full_perms from omnitils.modules import get_local_module, import_module_from_path, import_package from omnitils.strings import normalize_ver from py7zr import SevenZipFile -from pydantic import BaseModel, ConfigDict, Field, RootModel, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + RootModel, + ValidationError, + WrapValidator, + create_model, + model_validator, +) from src._state import PATH, AppConstants, AppEnvironment from src.enums.mtg import ( - layout_map_category, + LayoutCategory, + LayoutType, layout_map_display_condition, layout_map_display_condition_dual, layout_map_types, layout_map_types_display, ) +from src.utils.data_structures import dump_model, parse_model from src.utils.download import download_cloudfront +from src.utils.event import SubscribableEvent -""" -* Types -""" +# region Types class TemplateUpdate(TypedDict): @@ -53,88 +59,11 @@ class TemplateDetails(TypedDict): name: str class_name: str - object: "AppTemplate" - config: "ConfigManager" + config: ConfigHandler +ManifestTemplateMap = dict[str, dict[str, list[LayoutType]]] """Dictionary which maps a template's displayed names to classes, and classes to template types.""" -ManifestTemplateMap = dict[str, dict[str, list[str]]] - -"""Reversed dictionary which maps a template type, to a template name, to a template class.""" -TemplateTypeMap = dict[str, dict[str, TemplateDetails]] - -"""Map of templates selected for each template type.""" -TemplateSelectedMap = dict[str, TemplateDetails | None] - - -class TemplateRequirements(TypedDict): - """Requirements that must be met for a template to be supported.""" - - templates: NotRequired[list[str]] - version: NotRequired[str] - - -class ManifestTemplateDetails(TypedDict): - """Template details pulled from a plugin's `manifest.yml` file or Proxyshop's `templates.yml` file.""" - - file: str - name: NotRequired[str] - id: NotRequired[str] - desc: NotRequired[str] - requires: NotRequired[TemplateRequirements] - version: NotRequired[str] - templates: ManifestTemplateMap - - -class TemplateMetadata(TypedDict): - """TemplateDetails, minus the template class map.""" - - file: str - name: NotRequired[str] - id: NotRequired[str] - desc: NotRequired[str] - requires: NotRequired[TemplateRequirements] - version: NotRequired[str] - - -class PluginMetadata(TypedDict): - """Metadata contained in the `PLUGIN` table of a plugin's `manifest.yml` file.""" - - name: NotRequired[str] - author: NotRequired[str] - desc: NotRequired[str] - source: NotRequired[str] - docs: NotRequired[str] - license: NotRequired[str] - requires: NotRequired[str] - version: NotRequired[str] - - -class TemplateCategoryMap(TypedDict): - """Data mapped to a displayed template category, e.g. 'Normal'.""" - - names: list[str] - map: TemplateTypeMap - - -class KivyConfigTitle(TypedDict): - type: str - title: str - - -class KivyConfigSection(KivyConfigTitle): - type: str - title: str - desc: str - section: str - key: str - options: NotRequired[list[str]] - default: str | int | float - - -class IniConfig(TypedDict): - key: str - value: str | int | float class BaseConfig(BaseModel): @@ -157,34 +86,49 @@ class BaseSetting(BaseSection): class BoolSetting(BaseSetting): type: Literal["bool"] - # Kivy doesn't support default fields in it's config, - # so they should not be serialized. - default: bool = Field(default=False, exclude=True) + default: bool = False class StringSetting(BaseSetting): type: Literal["string"] - default: str = Field(default="", exclude=True) + default: str = "" class NumericSetting(BaseSetting): type: Literal["numeric"] - default: int | float = Field(default=0, exclude=True) + default: int | float = 0 + + +class FloatSetting(BaseSetting): + type: Literal["float"] + default: float = 0 + + +class IntSetting(BaseSetting): + type: Literal["int"] + default: int = 0 class OptionsSetting(BaseSetting): type: Literal["options"] options: list[str] = [] - default: str = Field(exclude=True) + default: str -SettingsSection = BoolSetting | StringSetting | NumericSetting | OptionsSetting +TypedSetting = ( + BoolSetting + | StringSetting + | NumericSetting + | FloatSetting + | IntSetting + | OptionsSetting +) -_SomeSetting = RootModel[SettingsSection] +_SomeSetting = RootModel[TypedSetting] class ConfigSection(BaseSection): - settings: dict[str, SettingsSection] = Field(default={}, exclude=True) + settings: dict[str, TypedSetting] = Field(default={}, exclude=True) @model_validator(mode="before") @classmethod @@ -197,7 +141,7 @@ def extra_validator(cls, data: Any) -> Any: return data # pyright: ignore[reportUnknownVariableType] @model_validator(mode="after") - def post_validate(self) -> Self: + def post_validate(self) -> ConfigSection: if self.model_extra: self.settings = self.model_extra return self @@ -205,13 +149,16 @@ def post_validate(self) -> Self: model_config = ConfigDict(extra="allow") -class FormattedKivyConfig(RootModel[list[SectionTitle | SettingsSection]]): - root: list[SectionTitle | SettingsSection] = [] +class FormattedSettingsConfig(RootModel[list[SectionTitle | TypedSetting]]): + root: list[SectionTitle | TypedSetting] = [] -class KivyConfig(BaseModel): - config: BaseConfig | None = Field(default=None, alias="__CONFIG__") - sections: FormattedKivyConfig = Field(default=FormattedKivyConfig(), exclude=True) +class SettingsConfig(BaseModel): + config: Annotated[BaseConfig | None, Field(alias="__CONFIG__")] = None + sections: Annotated[ + FormattedSettingsConfig, + Field(exclude=True, default_factory=FormattedSettingsConfig), + ] @model_validator(mode="before") @classmethod @@ -225,10 +172,9 @@ def extra_validator(cls, data: Any) -> Any: return data # pyright: ignore[reportUnknownVariableType] @model_validator(mode="after") - def post_validate(self) -> Self: + def post_validate(self) -> SettingsConfig: if self.model_extra: sections: dict[str, ConfigSection] = self.model_extra - self.sections = FormattedKivyConfig() prefix = self.config.prefix if self.config else None for section, data in sections.items(): self.sections.root.append(SectionTitle(title=data.title)) @@ -268,304 +214,331 @@ class SymbolsManifest(BaseModel): watermark: SymbolsWatermark -""" -* Config File Utils -""" +class PluginInfo(BaseModel): + name: str | None = None + author: str | None = None + desc: str | None = None + source: str | None = None + docs: str | None = None + license: str | None = None + version: str | None = None -class CustomConfigParser(ConfigParser): - def optionxform(self, optionstr: str) -> str: - return optionstr - - -def parse_kivy_config(data_path: Path) -> FormattedKivyConfig: - """ - Tries to parse the Kivy config from a file at data_path. - - Raises: - OSError: If reading of the file at data_path fails. - ValidationError: If the data in file at data_path is invalid. - """ - if data_path.suffix == ".json": - return KivyConfig.model_validate_json(data_path.read_bytes()).sections - else: - if data_path.suffix == ".toml": - with open(data_path, "rb") as f: - data = tomllib.load(f) - return KivyConfig.model_validate(data).sections - if data_path.suffix in (".yaml", ".yml"): - with open(data_path, "rb") as f: - data = yaml.safe_load(f) - return KivyConfig.model_validate(data).sections - raise NotImplementedError( - f"Kivy config can be parsed only from .json, .toml, .yaml and .yml files. Got file: { - data_path - }" - ) +class TemplateFileInfo(BaseModel): + name: str | None = None + id: str | None = None + desc: str | None = None + version: str | None = None + templates: ManifestTemplateMap -def verify_config_fields(ini_file: Path, data_file: Path) -> None: - """Validate that all settings fields present in a given json data are present in config file. If any are missing, - add them and return. +TemplateFileInfoRoot = RootModel[dict[str, TemplateFileInfo]] - Args: - ini_file: Config file to verify contains the proper fields. - data_file: Data file containing config fields to check for, JSON or TOML. - """ - # Track data and changes - data: dict[str, list[IniConfig]] = {} - changed = False - # Data file doesn't exist or is unsupported data type - if not data_file.is_file() or data_file.suffix not in (".toml", ".json"): - return +class PluginManifest(BaseModel): + plugin: Annotated[PluginInfo, Field(alias="PLUGIN")] + files: Annotated[dict[str, TemplateFileInfo], Field(exclude=True)] = {} - # Load data from JSON or TOML file - settings_config = parse_kivy_config(data_file) - - # Ensure INI file exists and load ConfigParser - ensure_file(ini_file) - config = get_config_object(ini_file) - - # Build a dictionary of the necessary values - for section in settings_config.root: - # Add row if it's not a title - if isinstance(section, SectionTitle): - continue - data.setdefault(section.section, []).append( - {"key": section.key, "value": section.default} - ) + @model_validator(mode="before") + @classmethod + def extra_validator(cls, data: Any) -> Any: + if isinstance(data, dict): + exclude_keys = [*cls.model_fields.keys(), "PLUGIN"] + for key, value in data.items(): # pyright: ignore[reportUnknownVariableType] + if not isinstance(key, str) or key in exclude_keys: + continue + data[key] = TemplateFileInfo.model_validate(value) + return data # pyright: ignore[reportUnknownVariableType] - # Add the data to ini where missing - for section, settings in data.items(): - # Check if the section exists - if not config.has_section(section): - config.add_section(section) - changed = True - # Check if each setting exists - for setting in settings: - if not config.has_option(section, setting["key"]): - config.set(section, setting["key"], str(setting["value"])) - changed = True + @model_validator(mode="after") + def post_validate(self) -> PluginManifest: + if self.model_extra: + self.files = self.model_extra + return self - # If ini has changed, write changes - if changed: - with open(ini_file, "w", encoding="utf-8") as f: - config.write(f) # noqa + model_config = ConfigDict(extra="allow") -def get_kivy_config_from_schema(config: Path) -> str: - """Return valid JSON data for use with Kivy settings panel. +# endregion Types - Args: - config: Path to config schema file, JSON or TOML. +# region Configs - Returns: - Json string dump of validated data. - """ - return parse_kivy_config(config).model_dump_json() +class CustomConfigParser(ConfigParser): + def optionxform(self, optionstr: str) -> str: + return optionstr -def copy_config_or_verify(path_from: Path, path_to: Path, data_file: Path) -> None: - """Copy one config to another, or verify it if it exists. - Args: - path_from: Path to the file to be copied. - path_to: Path to the file to create, if it doesn't exist. - data_file: Data schema file to use for validating an existing INI file. +def parse_settings_config(data_path: Path) -> FormattedSettingsConfig: """ - if os.path.isfile(path_to): - return verify_config_fields(path_to, data_file) - shutil.copy(path_from, path_to) - - -def get_config_object( - path: str | os.PathLike[str] | list[str | os.PathLike[str]], -) -> ConfigParser: - """Returns a ConfigParser object using a valid ini path. - - Args: - path: Path to ini config file. - - Returns: - ConfigParser object. + Tries to parse the settings config from a file at data_path. Raises: - ValueError: If valid ini file wasn't received. + OSError: If reading of the file at data_path fails. + ValidationError: If the data in file at data_path is invalid. """ - config = CustomConfigParser(allow_no_value=True) - config.read(path, encoding="utf-8") - return config + return parse_model(data_path, SettingsConfig).sections -""" -* Classes -""" +_setting_to_python_type = { + "bool": bool, + "string": str, + "numeric": int | float, + "int": int, + "float": float, + "options": str, +} -class ConfigManager: - """Represents the combined loaded configuration data for the app and template/plugin if provided. +def configparser_to_dict(parser: ConfigParser) -> dict[str, dict[str, str]]: + return { + section: {key: value for key, value in content.items()} + for section, content in parser.items() + } - Args: - template_class: Name of the template class, if provided. - template: AppTemplate object corresponding to the template class, if provided. - """ - gui_elements = [] +class ConfigHandler: + """Handler for combined config schema and its saved values.""" def __init__( - self, - template_class: str | None = None, - template: Optional["AppTemplate"] = None, + self, base_schema_path: Path, schema_path: Path | None, ini_path: Path ) -> None: - # Establish template and plugin details - self._template_class: str | None = ( - template_class.replace("Back", "Front") if template_class else None - ) - self._template: AppTemplate | None = template or None - self._plugin: AppPlugin | None = template.plugin if template else None + self.base_schema_path = base_schema_path + self.schema_path = schema_path + self.ini_path = ini_path - # Core path where config folders exist - self._path_schema = ( - self._plugin.path_config if self._plugin else PATH.SRC_DATA_CONFIG - ) - self._path_ini = ( - self._plugin.path_ini if self._plugin else PATH.SRC_DATA_CONFIG_INI - ) - - """ - * Path: App settings only modified in Global - """ + self.config_added: SubscribableEvent[ConfigHandler] = SubscribableEvent() + self.config_reset: SubscribableEvent[ConfigHandler] = SubscribableEvent() + self.config_deleted: SubscribableEvent[ConfigHandler] = SubscribableEvent() - @property - def app_path_schema(self) -> Path: - """System config schema, in JSON or TOML.""" - return PATH.SRC_DATA_CONFIG_APP - - @property - def app_path_ini(self) -> Path: - """System config INI.""" - return PATH.SRC_DATA_CONFIG_INI_APP - - @property - def app_json(self) -> str: - """System config as Kivy readable JSON string dump.""" - return get_kivy_config_from_schema(self.app_path_schema) - - @property - def app_cfg(self) -> ConfigParser: - """System ConfigParser instance.""" - return get_config_object(self.app_path_ini) - - """ - * Path: Settings modifiable by Template - """ - - @property - def base_path_schema(self) -> Path: - """Main template config schema, in JSON or TOML.""" - return PATH.SRC_DATA_CONFIG_BASE - - @property - def base_path_ini(self) -> Path: - """Main template config INI.""" - return PATH.SRC_DATA_CONFIG_INI_BASE - - @property - def base_json(self) -> str: - """Main template config as Kivy readable JSON string dump.""" - return get_kivy_config_from_schema(self.base_path_schema) + @cached_property + def id(self) -> str: + return str(self.ini_path) - @property - def base_cfg(self) -> ConfigParser: - """Main template ConfigParser instance.""" - return get_config_object(self.base_path_ini) + @cached_property + def has_config(self) -> bool: + return self.ini_path.is_file() - """ - * Path: Template specific settings - """ + @cached_property + def base_schema(self) -> FormattedSettingsConfig: + return parse_settings_config(self.base_schema_path) - @property - def template_path_schema(self) -> Path | None: - """Template specific config schema, in JSON or TOML.""" - if self._template and self._template_class: - path = self._template.get_path_config(self._template_class) - return path if path.is_file() else None - return + @cached_property + def schema(self) -> FormattedSettingsConfig | None: + return parse_settings_config(self.schema_path) if self.schema_path else None - @property - def template_path_ini(self) -> Path | None: - """Template specific config INI.""" - if self._template and self._template_class: - return self._template.get_path_ini(self._template_class) - return + @cached_property + def ini_schema(self) -> type[BaseModel]: + sections: dict[str, dict[str, Any]] = {} + for schema in ( + (self.base_schema.root, self.schema.root) + if self.schema + else (self.base_schema.root,) + ): + for entry in schema: + if not isinstance(entry, SectionTitle): + sections.setdefault(entry.section, {}) + + # Early bind entry's value to avoid using the last value of + # the loop within each function + def validator_factory(item: TypedSetting = entry): + # Set invalid settings to their default value + def validator(v: Any, handler: Callable[[Any], Any]) -> Any: + try: + return handler(v) + except ValidationError: + return item.default + + return validator + + sections[entry.section][entry.key] = Annotated[ + _setting_to_python_type[entry.type] | None, + Field(default=entry.default), + WrapValidator(validator_factory()), + ] + + root_fields: dict[str, Any] = {} + for key, item in sections.items(): + model = create_model(key, **item) + + def factory(modl: type[BaseModel] = model): + def validator(v: Any, handler: Callable[[Any], Any]) -> Any: + try: + return handler(v) + except ValidationError: + return modl() + + return validator + + root_fields[key] = Annotated[ + model, Field(default_factory=model), WrapValidator(factory()) + ] + + return create_model( + "ConfigINISchema", + **root_fields, + ) - @property - def template_json(self) -> str | None: - """Template specific config as Kivy readable JSON string dump.""" - if self.template_path_schema: - return get_kivy_config_from_schema(self.template_path_schema) - return + @cached_property + def _parser(self) -> ConfigParser: + parser = CustomConfigParser(default_section="", allow_no_value=True) + if self.ini_path.is_file(): + parser.read_string(self.ini_path.read_text(encoding="utf-8")) + return parser @property - def template_cfg(self) -> ConfigParser | None: - """Template specific ConfigParser instance.""" - if self.template_path_ini: - return get_config_object(self.template_path_ini) - return + def parser(self) -> ConfigParser: + vals = self.setting_values + self._parser.clear() + self._parser.read_dict(vals) + return self._parser - """ - * Bool Checks - """ - - @property - def has_template_ini(self) -> bool: - """Returns True if a template has a separate INI file.""" - return bool(self.template_path_ini and self.template_path_ini.is_file()) + @cached_property + def _initial_setting_values(self) -> BaseModel | None: + return None - """ - * Utility Methods - """ + @cached_property + def setting_values(self) -> dict[str, dict[str, int | float | str | bool]]: + """Use `set_value` to change values. Otherwise the GUI might end up showing incorrect state + about the config being set or not.""" + self._initial_setting_values = self.ini_schema.model_validate( + configparser_to_dict(self._parser) + ) + values = self._initial_setting_values.model_dump() + return values + + def set_value( + self, section: str, key: str, value: int | float | str | bool + ) -> bool: + if section in self.setting_values and key in self.setting_values[section]: + self.setting_values[section][key] = value + return True + return False - def get_config(self) -> ConfigParser: - """Return a ConfigParser instance with all relevant config data loaded.""" - self.validate_configs() - if self.template_path_ini and self.template_path_ini.is_file(): - # Load template INI file instead of base - self.validate_template_configs() - return get_config_object([self.app_path_ini, self.template_path_ini]) - # Load app and base only - return get_config_object([self.app_path_ini, self.base_path_ini]) + def save( + self, + force: bool = False, + ) -> None: + # Save only if something has changed + if force or ( + self._initial_setting_values + and self._initial_setting_values + != self.ini_schema.model_validate(self.setting_values) + ): + parser = self.parser + with open(self.ini_path, "w", encoding="utf-8") as f: + parser.write(f) + self.has_config = True + self.config_added.trigger(self) - """ - * Validate Methods - """ + def reset(self) -> None: + self.setting_values = self.ini_schema().model_dump() + self.config_reset.trigger(self) - def validate_configs(self) -> None: - """Validate app and base configs against data schemas.""" - verify_config_fields(ini_file=self.app_path_ini, data_file=self.app_path_schema) - verify_config_fields( - ini_file=self.base_path_ini, data_file=self.base_path_schema - ) + def delete(self, notify: bool = True) -> None: + self.ini_path.unlink(missing_ok=True) + if notify: + self.config_deleted.trigger(self) - def validate_template_configs(self) -> None: - """Validate template configs against data schemas.""" - if not self.template_path_ini: - return - - # Validate base template configs - mkdir_full_perms(self.template_path_ini.parent) - copy_config_or_verify( - path_from=self.base_path_ini, - path_to=self.template_path_ini, - data_file=self.base_path_schema, - ) + # region Config getters - # Validate template specific configs - if self.template_path_schema: - verify_config_fields( - ini_file=self.template_path_ini, data_file=self.template_path_schema - ) + def get_setting( + self, + section: str, + key: str, + default: int | float | str | bool | None = None, + ) -> int | float | str | bool | None: + if sect := self.setting_values.get(section, None): + return sect.get(key, default) + return default + + @overload + def get_bool_setting(self, section: str, key: str, default: bool) -> bool: ... + + @overload + def get_bool_setting( + self, section: str, key: str, default: bool | None = None + ) -> bool | None: ... + + def get_bool_setting( + self, section: str, key: str, default: bool | None = None + ) -> bool | None: + return bool(self.get_setting(section, key, default)) + + @overload + def get_int_setting(self, section: str, key: str, default: int) -> int: ... + + @overload + def get_int_setting( + self, section: str, key: str, default: int | None = None + ) -> int | None: ... + + def get_int_setting( + self, section: str, key: str, default: int | None = None + ) -> int | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return int(setting) + return default + + @overload + def get_float_setting(self, section: str, key: str, default: float) -> float: ... + + @overload + def get_float_setting( + self, section: str, key: str, default: float | None = None + ) -> float | None: ... + + def get_float_setting( + self, section: str, key: str, default: float | None = None + ) -> float | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return float(setting) + return default + + @overload + def get_str_setting(self, section: str, key: str, default: str) -> str: ... + + @overload + def get_str_setting( + self, section: str, key: str, default: str | None = None + ) -> str | None: ... + + def get_str_setting( + self, section: str, key: str, default: str | None = None + ) -> str | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return str(setting) + return default + + @overload + def get_enum_setting[T: Enum]( + self, section: str, key: str, enum_type: type[T], default: T + ) -> T: ... + + @overload + def get_enum_setting[T: Enum]( + self, section: str, key: str, enum_type: type[T], default: T | None = None + ) -> T | None: ... + + def get_enum_setting[T: Enum]( + self, section: str, key: str, enum_type: type[T], default: T | None = None + ) -> T | None: + setting = self.get_setting(section, key, None) + if setting is not None: + return enum_type(setting) + return default + + # endregion Config getters + + +# endregion Configs + +# region Plugins + +_template_import_lock = Lock() class AppPlugin: @@ -578,8 +551,8 @@ class AppPlugin: Attributes: _root (Path): Root path where the plugin's files are located. _manifest (Path): Path to the plugin's manifest file. - _templates (dict[str, TemplateDetails]): Data loaded from manifest file. - _info (PluginMetadata): Top level table from the manifest file containing the plugin's metadata. + _templates (dict[str, TemplateFileInfo]): Data loaded from manifest file. + _info (PluginInfo): Top level table from the manifest file containing the plugin's metadata. _module (ModuleType): This plugin's loaded Python module. Raises: @@ -587,42 +560,57 @@ class AppPlugin: ModuleNotFoundError: If the plugin's python module couldn't be found. """ - def __init__(self, con: AppConstants, env: AppEnvironment, path: Path): + def __init__( + self, + con: AppConstants, + env: AppEnvironment, + path: Path, + template_file_versions: dict[str, str], + ): # Save a reference to the global application constants self.con: AppConstants = con self.env: AppEnvironment = env + self.versions = template_file_versions # Ensure path exists if not path.is_dir(): - raise FileNotFoundError(f"Couldn't locate plugin path: {str(path)}") + raise FileNotFoundError(f"Couldn't locate plugin path: {path}") self._root = path # Find a valid manifest - self._manifest = Path(path, "manifest.yml") - if not self._manifest.is_file(): - self._manifest = Path(path, "manifest.yaml") - if not self._manifest.is_file(): - self._manifest = Path(path, "manifest.json") - if not self._manifest.is_file(): + plugin_manifest_path = Path(path, "manifest.yml") + if not plugin_manifest_path.is_file(): + plugin_manifest_path = plugin_manifest_path.with_suffix(".yaml") + if not plugin_manifest_path.is_file(): + plugin_manifest_path = plugin_manifest_path.with_suffix(".json") + if not plugin_manifest_path.is_file(): + plugin_manifest_path = plugin_manifest_path.with_suffix(".toml") + if not plugin_manifest_path.is_file(): raise FileNotFoundError( - f"Couldn't locate manifest file for plugin at path: {str(path)}" + f"Couldn't locate manifest file for plugin at path: {path}" ) # Load the manifest data - self.load_manifest() if self._manifest.suffix.lower() != ".json" else self.load_manifest_json() + if plugin_manifest_path.suffix == ".json": + # Backwards compatibility + plugin_manifest = self.load_manifest_json_legacy(plugin_manifest_path) + else: + plugin_manifest: PluginManifest = parse_model( + plugin_manifest_path, PluginManifest + ) - # Attempt to load this plugin's module - self.load_module() + self._info: PluginInfo = plugin_manifest.plugin + self._templates: dict[str, TemplateFileInfo] = plugin_manifest.files # Load templates self.load_templates() - """ - * Tracked Properties - """ + @cached_property + def id(self) -> str: + return self._root.name @cached_property - def template_map(self) -> dict[str, "AppTemplate"]: + def template_map(self) -> dict[str, AppTemplate]: """dict[str, AppTemplate]: A dictionary mapping of AppTemplate's by file name pulled from this plugin.""" return {} @@ -661,50 +649,37 @@ def path_templates(self) -> Path: @cached_property def name(self) -> str: """str: Displayed name of the plugin. Fallback on root directory name.""" - return self._info.get("name", self._root.stem) + return self._info.name if self._info.name is not None else self._root.stem @cached_property def author(self) -> str: """str: Displayed name of the plugin's author. Fallback on name.""" - return self._info.get("author", self.name) + return self._info.author if self._info.author is not None else self.name @cached_property def description(self) -> str | None: """Optional[str]: Displayed description of the plugin. Fallback on None.""" - return self._info.get("desc", None) - - @cached_property - def source(self) -> yarl.URL | None: - """Optional[URL]: Link to the hosted files of this plugin.""" - if url := self._info.get("source"): - with suppress(Exception): - url = yarl.URL(url) - return url - return + return self._info.desc @cached_property - def docs(self) -> yarl.URL | None: - """Optional[URL]: Link to the hosted documentation for this plugin.""" - if url := self._info.get("docs"): - with suppress(Exception): - url = yarl.URL(url) - return url - return + def source(self) -> str | None: + """Link to the hosted files of this plugin.""" + return self._info.source @cached_property - def license(self) -> str: - """str: Name of the open source license carried by this plugin. Fallback on MPL-2.0.""" - return self._info.get("license", "MPL-2.0") + def docs(self) -> str | None: + """Link to the hosted documentation for this plugin.""" + return self._info.docs @cached_property - def required_version(self) -> str | None: - """Optional[str]: Proxyshop version this plugin requires to function as intended.""" - return self._info.get("requires", None) + def license(self) -> str | None: + """Name of the open source license carried by this plugin. Fallback on MPL-2.0.""" + return self._info.license @cached_property def version(self) -> str: - """str: Current version of the plugin.""" - return self._info.get("version", "0.1.0") + """Current version of the plugin.""" + return self._info.version or "0.0.0" """ * Module Details @@ -712,6 +687,8 @@ def version(self) -> str: @property def module(self) -> ModuleType | None: + if not self._module: + self.load_module() return self._module @cached_property @@ -728,40 +705,25 @@ def module_name(self) -> str: * Plugin Methods """ - def load_manifest(self) -> None: - """Load the `manifest.yml` data for this plugin. - - Raises: - ValueError: If the manifest file contains invalid data. - """ - try: - # Load plugin metadata and template details - data = load_data_file(self._manifest) - self._info: PluginMetadata = data.pop("PLUGIN", {}) - self._templates: dict[str, ManifestTemplateDetails] = data.copy() - except Exception as e: - raise ValueError( - f"Manifest file contains invalid data: {str(self._manifest)}" - ) from e - - def load_manifest_json(self) -> None: + def load_manifest_json_legacy(self, path: Path) -> PluginManifest: """Load the legacy `manifest.json` data for this plugin. Raises: ValueError: If the manifest file contains invalid data. """ + try: - # Load Plugin metadata - data = load_data_file(self._manifest) - self._info: PluginMetadata = data.pop("PLUGIN", {}) - templates: dict[str, dict[str, dict[str, str]]] = data.copy() + with open(path, "r", encoding="UTF-8") as f: + # Load Plugin metadata + templates: dict[str, Any] | None = load(f) + if not isinstance(templates, dict): + raise ValueError("Plugin manifest isn't a dictionary") + manifest = PluginManifest(plugin=templates.pop("PLUGIN", {}), files={}) except Exception as e: - raise ValueError( - f"Manifest file contains invalid data: {str(self._manifest)}" - ) from e + raise ValueError(f"Manifest file contains invalid data: {path}") from e # Re-format templates dict for TemplateDetails - self._templates: dict[str, ManifestTemplateDetails] = {} + manifest.files = {} for t, temps in templates.items(): if t not in layout_map_types: continue @@ -769,24 +731,31 @@ def load_manifest_json(self) -> None: # Add new file file_name = details.get("file", "") class_name = details.get("class", "") - self._templates.setdefault( - file_name, {"file": file_name, "templates": {}} - ) + manifest.files.setdefault(file_name, TemplateFileInfo(templates={})) # Add Google Drive ID if details.get("id"): - self._templates[file_name]["id"] = details["id"] + manifest.files[file_name].id = details["id"] + + template_type = LayoutType(t) + # Existing name - if name in self._templates[file_name]["templates"]: + if name in manifest.files[file_name].templates: # Existing class name - if class_name in self._templates[file_name]["templates"][name]: - self._templates[file_name]["templates"][name][ - class_name - ].append(t) + if class_name in manifest.files[file_name].templates[name]: + manifest.files[file_name].templates[name][class_name].append( + template_type + ) continue # Add new class - self._templates[file_name]["templates"][name][class_name] = [t] + manifest.files[file_name].templates[name][class_name] = [ + template_type + ] # Add new template - self._templates[file_name]["templates"][name] = {class_name: [t]} + manifest.files[file_name].templates[name] = { + class_name: [template_type] + } + + return manifest def load_templates(self) -> None: """Load the dictionary of AppTemplate's pulled from this plugin's manifest file. @@ -794,28 +763,38 @@ def load_templates(self) -> None: Returns: A dictionary where keys are PSD/PSB filenames and values are AppTemplate objects. """ + # Use one config manager per python class + config_managers: dict[str, ConfigHandler] = {} for file_name, data in self._templates.items(): - data["file"] = file_name self.template_map[file_name] = AppTemplate( - con=self.con, env=self.env, data=data, plugin=self + con=self.con, + env=self.env, + data=data, + file_name=file_name, + config_managers=config_managers, + versions=self.versions, + plugin=self, ) def load_module(self, hotswap: bool = False) -> None: """Load the plugin's Python module.""" - # Generate a 'py' module if it doesn't exist if not self.module_path.is_dir(): self.module_path.mkdir(mode=777, parents=True, exist_ok=True) # Check if root has init root_init = self._root / "__init__.py" - has_root_init = bool(root_init.is_file()) + has_root_init = root_init.is_file() if not has_root_init: with open(root_init, "w", encoding="utf-8") as f: f.write("") import_module_from_path(name=self._root.stem, path=root_init, hotswap=hotswap) - if not has_root_init: - os.remove(root_init) + # if not has_root_init: + # try: + # os.remove(root_init) + # except OSError: + # print("Failed to remove temporary plugin init file:", str(root_init)) + # print_exc() # Ensure init file in py module if not Path(self.module_path, "__init__.py").is_file(): @@ -829,119 +808,253 @@ def load_module(self, hotswap: bool = False) -> None: name=self.module_name, path=self.module_path, hotswap=hotswap ) - def get_template_list(self) -> list["AppTemplate"]: + def get_template_list(self) -> list[AppTemplate]: """list[AppTemplate]: Returns a list of AppTemplate's pulled from this plugin.""" return list(self.template_map.values()) -class AppTemplate: - """Represents a template definition from a `manifest.yml` file. +def get_all_plugins( + con: AppConstants, env: AppEnvironment, template_file_versions: dict[str, str] +) -> dict[str, AppPlugin]: + """Gets a dict of 'AppPlugin' objects mapped by their name attribute. Args: - con: Global application constants object. - data: Template data pulled from `manifest.yml` file. - plugin: Loaded Proxyshop plugin object representing a given plugin directory from `/plugins/`. + con: Global constants object. + env: Global environment object. - Attributes: - _info (TemplateMetadata): This template's metadata info. - plugin (AppPlugin): The `plugin` this template was loaded from, if provided. - map (TemplateTypeMap): Template's types, mapped to names, mapped to details. - manifest_map (TemplateClassMap): Template's display names, mapped to classes, mapped to types. + Returns: + A mapping of plugin names to their respective 'AppPlugin' object. """ + plugins: dict[str, AppPlugin] = {} + + # Load all plugins and plugin templates + for folder in [p for p in PATH.PLUGINS.iterdir() if p.is_dir()]: + if folder.stem.startswith("__") or folder.stem.startswith("!"): + continue + try: + plugin = AppPlugin( + con=con, + env=env, + path=folder, + template_file_versions=template_file_versions, + ) + plugins[plugin.name] = plugin + except Exception: + print_exc() + return dict(sorted(plugins.items())) + + +# endregion Plugins + +# region Templates + +TemplateFileVersionsModel = RootModel[dict[str, str]] + + +def get_template_file_versions() -> TemplateFileVersionsModel: + if PATH.SRC_DATA_VERSIONS.exists(): + try: + return parse_model(PATH.SRC_DATA_VERSIONS, TemplateFileVersionsModel) + except ValidationError: + pass + return TemplateFileVersionsModel({}) + + +def get_template_class( + class_name: str, plugin: AppPlugin | None = None, force_reload: bool = False +) -> type[object]: + """ + Loads the template class for a template of a given class name. + + Raises: + Exception: if module loading fails. + """ + with _template_import_lock: + if plugin: + # Load plugin module + if force_reload: + plugin.load_module(hotswap=True) + module = plugin.module + else: + # Load local module + module = get_local_module(module_path="src.templates", hotswap=force_reload) + return getattr(module, class_name) + + +def sort_layout_categories( + items: set[LayoutCategory], place_first: LayoutCategory | None = None +) -> list[LayoutCategory]: + first_item: LayoutCategory | None = None + out = list(items) + if place_first and (place_first in items): + out.remove(place_first) + first_item = place_first + out.sort() + if first_item: + out.insert(0, first_item) + return out + + +class RenderableTemplate(Protocol): + name: str + plugin: AppPlugin | None + + def get_config(self, class_name: str) -> ConfigHandler | None: ... + + def get_class_name_and_file_for_layout( + self, layout_type: LayoutType + ) -> tuple[str, Path] | None: ... + + +class TemplateClassDetails(TypedDict): + config: ConfigHandler + layouts: list[LayoutType] + + +class NamedTemplate(RenderableTemplate): + def __init__( + self, + name: str, + template_classes: dict[str, TemplateClassDetails], + parent: AppTemplate, + ) -> None: + self.name = name + self.template_classes = template_classes + self.parent = parent + self.plugin = parent.plugin + + @cached_property + def layout_categories(self) -> set[LayoutCategory]: + categories: set[LayoutCategory] = set() + for template_class_details in self.template_classes.values(): + for layout in template_class_details["layouts"]: + categories.add(layout_map_types[layout]) + return categories + + def get_config(self, class_name: str) -> ConfigHandler | None: + if class_name in self.template_classes: + return self.template_classes[class_name]["config"] + + def has_config(self, layout_category: LayoutCategory | None = None) -> bool: + for template_class_details in self.template_classes.values(): + if layout_category: + contains_layout: bool = False + for layout in template_class_details["layouts"]: + if layout_category.matches_layout_type(layout_category, layout): + contains_layout = True + break + if not contains_layout: + continue + + return template_class_details["config"].has_config + return False + + def get_preview_image_path(self, layout: LayoutCategory) -> Path | None: + for class_name, template_class_details in self.template_classes.items(): + for layout_type in template_class_details["layouts"]: + if LayoutCategory.matches_layout_type(layout, layout_type): + return self.parent.get_path_preview(class_name, layout_type) + + def get_class_name_for_layout(self, layout_type: LayoutType) -> str | None: + for class_name, template_class_details in self.template_classes.items(): + if layout_type in template_class_details["layouts"]: + return class_name + return None + + def get_class_name_and_file_for_layout( + self, layout_type: LayoutType + ) -> tuple[str, Path] | None: + if class_name := self.get_class_name_for_layout(layout_type): + return (class_name, self.parent.path_psd) + + +class AppTemplate: + """Represents a template definition from a `manifest.yml` file.""" def __init__( self, con: AppConstants, env: AppEnvironment, - data: ManifestTemplateDetails, + data: TemplateFileInfo, + file_name: str, + config_managers: dict[str, ConfigHandler], + versions: dict[str, str], plugin: AppPlugin | None = None, ): - # Save a reference to the global application constants self.con: AppConstants = con self.env: AppEnvironment = env - - # Save the template's plugin and class map + self._info: TemplateFileInfo = data + self.file_name = file_name + self.versions = versions self.plugin: AppPlugin | None = plugin - self.manifest_map: ManifestTemplateMap = { - name: ( - {str(mapped): ["normal"]} - if isinstance(mapped, str) - else {k: ([v] if isinstance(v, str) else v) for k, v in mapped.items()} - ) - for name, mapped in data["templates"].items() - } - self.generate_template_map(self.manifest_map) - # Save the template's metadata - self._info: TemplateMetadata = data.copy() - self._info.pop("templates") - - """ - * Template Mappings - """ - - def generate_template_map(self, _map: ManifestTemplateMap) -> None: - """Generates a TemplateTypeMap, the configuration of types mapped to names mapped to details: - - 'class_name': python class name - - 'config': ConfigManager object - - 'object': AppTemplate object - """ - mapped: TemplateTypeMap = {} - configs: dict[str, ConfigManager] = {} - for name, classes in _map.items(): - for class_name, types in classes.items(): - for t in types: - # Reuse ConfigManager object for identical class names - if class_name not in configs: - configs[class_name] = ConfigManager( - template_class=class_name, template=self + self.template_installed: SubscribableEvent[None] = SubscribableEvent() + + # Initialize per template class config managers + self.config_managers: dict[str, ConfigHandler] = {} + for classes in data.templates.values(): + for class_name in classes: + if class_name not in self.config_managers: + if not (conf_mgr := config_managers.get(class_name, None)): + conf_mgr = ConfigHandler( + base_schema_path=PATH.SRC_DATA_CONFIG_BASE, + schema_path=conf_path + if (conf_path := self.get_path_config(class_name)).is_file() + else None, + ini_path=self.get_path_ini(class_name), ) - - # Add to the map - mapped.setdefault(t, {}) - mapped[t][name] = TemplateDetails( - name=name, - class_name=class_name, - config=configs[class_name], - object=self, - ) - - # Establish the complete map - self.map = mapped + config_managers[class_name] = conf_mgr + self.config_managers[class_name] = conf_mgr + + self.named_templates: dict[str, NamedTemplate] = { + name: NamedTemplate( + name, + { + class_name: { + "config": self.config_managers[class_name], + "layouts": layouts, + } + for class_name, layouts in classes.items() + }, + self, + ) + for name, classes in data.templates.items() + } + self.manifest_map: ManifestTemplateMap = data.templates """ * Template Metadata """ @cached_property - def name(self) -> str: - """str: Name of the template displayed in download manager menus.""" - return self._info.get("name", self.generate_template_name()) + def id(self) -> str: + """A unique identifier for the template.""" + return str(self.path_psd) @cached_property - def file_name(self) -> str: - """str: File name of the template PSD/PSB file.""" - file_name = self._info.get("file") - if not file_name: - raise ValueError(f"Template '{self.name}' did not provide a file name!") - return file_name + def name(self) -> str: + """str: Name of the template displayed in download manager menus.""" + return ( + self._info.name + if self._info.name is not None + else self.generate_template_name() + ) @cached_property def google_drive_id(self) -> str | None: - """Optional[str]: The template's Google Drive file ID, fallback to None.""" - return self._info.get("id", None) + """The template's Google Drive file ID, fallback to None.""" + return self._info.id @cached_property def description(self) -> str | None: - """Optional[str]: The template's displayed description, fallback to None.""" - return self._info.get("desc", None) + """The template's displayed description, fallback to None.""" + return self._info.desc @property def version(self) -> str | None: - """Optional[str]: The template's currently logged version.""" - if not self.google_drive_id: - return - return self.con.versions.get(self.google_drive_id) + """The version of the template's installed PSD file.""" + if self.google_drive_id: + return self.versions.get(self.google_drive_id, None) """ * Template Update Data @@ -974,24 +1087,21 @@ def update_version(self) -> str | None: @property def is_installed(self) -> bool: - """bool: Whether PSD/PSB file for this template is installed.""" - # Todo: Add version checking - if not self.path_psd.is_file(): - return False - for required_template in self.requirements.get("templates", []): - if not Path(self.path_psd.parent, required_template).is_file(): - return False - return True + """Whether PSD/PSB file for this template is installed.""" + return self.path_psd.is_file() """ * Template Paths """ + @cached_property + def path_templates(self) -> Path: + return self.plugin.path_templates if self.plugin else PATH.TEMPLATES + @cached_property def path_psd(self) -> Path: """Path: Path to the PSD/PSB file.""" - root = self.plugin.path_templates if self.plugin else PATH.TEMPLATES - return root / self.file_name + return self.path_templates / self.file_name @cached_property def path_7z(self) -> Path: @@ -1002,19 +1112,9 @@ def path_7z(self) -> Path: def path_download(self) -> Path | None: """Optional[Path]: Path the file should be saved to when downloaded, if update ready.""" if self.update_file: - root = self.plugin.path_templates if self.plugin else PATH.TEMPLATES - return root / self.update_file + return self.path_templates / self.update_file return - """ - * Template Requirements - """ - - @property - def requirements(self) -> TemplateRequirements: - """TemplateRequirements: Requirements that must be met for this template to be supported.""" - return self._info.get("requires", {}) - """ * Template URLs """ @@ -1047,25 +1147,46 @@ def url_google_drive(self) -> yarl.URL | None: """ @cached_property - def types_supported(self) -> list[str]: - """set[str]: A set of all types supported by this template.""" - return list( + def supported_layout_types(self) -> list[LayoutType]: + """All the types supported by this template.""" + types = { + t + for class_map in self.manifest_map.values() + for types in class_map.values() + for t in types + } + + # Start the list with "normal" if it is supported + supports_normal = LayoutType.Normal in types + if supports_normal: + types.remove(LayoutType.Normal) + + sorted_types = list(types) + sorted_types.sort() + + if supports_normal: + sorted_types.insert(0, LayoutType.Normal) + + return sorted_types + + @cached_property + def supported_layout_categories(self) -> list[LayoutCategory]: + return sort_layout_categories( { - t - for class_map in self.manifest_map.values() - for types in class_map.values() - for t in types - } + layout_map_types[layout_type] + for layout_type in self.supported_layout_types + }, + place_first=LayoutCategory.Normal, ) @cached_property def all_names(self) -> list[str]: - """set[str]: A set of all display names used by this template.""" + """All display names used by this template.""" return list({name for name in self.manifest_map.keys()}) @cached_property def all_classes(self) -> list[str]: - """set[str]: A set of all python classes used by this template.""" + """All python classes used by this template.""" return list( { cls_name @@ -1074,54 +1195,31 @@ def all_classes(self) -> list[str]: } ) + @cached_property + def all_classes_and_layouts(self) -> dict[str, list[LayoutType]]: + classes_and_layouts: dict[str, list[LayoutType]] = {} + for class_map in self.manifest_map.values(): + for cls_name, layouts in class_map.items(): + classes_and_layouts.setdefault(cls_name, []) + classes_and_layouts[cls_name].extend(layouts) + return classes_and_layouts + """ * Template Utils """ - def get_template_class(self, class_name: str) -> Any: - """Loads the template class for a template of a given class name. - - Args: - class_name: Name of the template's python class. - - Returns: - The template's loaded python class. - """ - - # Try loading local module - if not self.plugin: - try: - module = get_local_module( - module_path="src.templates", hotswap=self.env.FORCE_RELOAD - ) - return getattr(module, class_name) - except Exception as e: - # Failed to load module - print_tb(e.__traceback__) - return print(e) - - # Try loading plugin module - try: - if self.env.FORCE_RELOAD: - self.plugin.load_module(hotswap=True) - return getattr(self.plugin.module, class_name) - except Exception as e: - # Failed to load module - print_tb(e.__traceback__) - return print(e) - def generate_template_name(self) -> str: """Generate an automatic name when name isn't manually defined.""" # Use first name on the list and look for types supported for that name name = self.all_names[0] supported = ( - self.types_supported.copy() + self.supported_layout_types.copy() if len(self.all_names) == 1 else {t for types in self.manifest_map[name].values() for t in types} ) - if "normal" in supported: - supported.remove("normal") + if LayoutType.Normal in supported: + supported.remove(LayoutType.Normal) # When 'normal' type is present, just use the name if not supported: @@ -1155,12 +1253,13 @@ def check_for_update(self) -> bool: Returns: True if Template needs to be updated, otherwise False. """ + if not self.google_drive_id: + print(f"{self.name} ({self.file_name}) does not have a Google Drive ID") + return False + # Get our metadata - if not self.google_drive_id or not ( - data := gdrive_get_metadata(self.google_drive_id, self.env.API_GOOGLE) - ): - # File couldn't be located on Google Drive - print(f"{self.name} ({self.file_name}) not found on Google Drive!") + if not (data := gdrive_get_metadata(self.google_drive_id, self.env.API_GOOGLE)): + print(f"{self.name} ({self.file_name}) not found on Google Drive") return False # Cache update data @@ -1171,7 +1270,7 @@ def check_for_update(self) -> bool: } # Compare the versions - self.validate_version() + self._validate_version() if not self.version: return True if self.update_version and normalize_ver(self.version) == normalize_ver( @@ -1182,22 +1281,22 @@ def check_for_update(self) -> bool: # Template needs an update return True - def validate_version(self) -> None: + def _validate_version(self) -> None: """Checks the current on-file version of this template and if the template is installed, updates the version tracker accordingly.""" if self.google_drive_id: # If installed but version is not logged, log default version if self.is_installed and not self.version: - self.con.versions[self.google_drive_id] = "1.0.0" - self.con.update_version_tracker() + self.versions[self.google_drive_id] = "1.0.0" return # If installed but version mistakenly logged, reset if not self.is_installed and self.version: - del self.con.versions[self.google_drive_id] - self.con.update_version_tracker() + del self.versions[self.google_drive_id] - def update_template(self, callback: Callable[[int, int], None]) -> bool: + def update_template( + self, callback: Callable[[int, int], None] | None = None + ) -> bool: """Update a given template to the latest version. Args: @@ -1209,6 +1308,7 @@ def update_template(self, callback: Callable[[int, int], None]) -> bool: if self.path_download: try: result: bool = False + will_install = not self.is_installed if self.google_drive_id and self.url_google_drive: # Download using Google Drive @@ -1233,36 +1333,29 @@ def update_template(self, callback: Callable[[int, int], None]) -> bool: # If downloaded file is a 7z archive, extract the template from it if result and self.path_download.suffix == ".7z": with SevenZipFile(self.path_7z, "r") as archive: - root = ( - self.plugin.path_templates - if self.plugin - else PATH.TEMPLATES - ) - archive.extractall(root) + archive.extractall(self.path_templates) self.path_7z.unlink() - # Return result status + if self.google_drive_id and self.update_version: + self.versions[self.google_drive_id] = self.update_version + + if will_install: + self.template_installed.trigger(None) + return result # Exception caught while downloading / unpacking - except Exception as e: - print(f"Failed to update template: {self.name}", e, format_exc()) + except Exception: + print(f"Failed to update template: {self.name}", format_exc()) else: print("Template update failed. Download path isn't specified.") return False - def mark_updated(self) -> None: - """Update the version tracker with the currently logged update version and clear the update data.""" - if self.google_drive_id and self.update_version: - self.con.versions[self.google_drive_id] = self.update_version - self.con.update_version_tracker() - self._update = {} - """ * Pathing Utils """ - def get_path_preview(self, class_name: str, class_type: str) -> Path: + def get_path_preview(self, class_name: str, class_type: LayoutType) -> Path: """Gets the path to a preview image for a given template name and type. Args: @@ -1314,43 +1407,188 @@ def get_path_ini(self, class_name: str) -> Path: return (PATH.SRC_DATA_CONFIG_INI / class_name).with_suffix(".ini") -""" -* Plugin Utils -""" +class AssembledTemplateInstalledArgs(TypedDict): + sender: AssembledTemplate + origin: AppTemplate -def get_all_plugins(con: AppConstants, env: AppEnvironment) -> dict[str, AppPlugin]: - """Gets a dict of 'AppPlugin' objects mapped by their name attribute. +class AssembledTemplateConfigChangedArgs(TypedDict): + sender: AssembledTemplate + class_name: str + config: ConfigHandler + has_config: bool - Args: - con: Global constants object. - env: Global environment object. - Returns: - A mapping of plugin names to their respective 'AppPlugin' object. - """ - plugins: dict[str, AppPlugin] = {} +class AssembledTemplate(RenderableTemplate): + def __init__( + self, name: str, templates: list[NamedTemplate], plugin: AppPlugin | None = None + ) -> None: + self.name = name + self.templates = templates + self.plugin = plugin - # Load all plugins and plugin templates - for folder in [p for p in PATH.PLUGINS.iterdir() if p.is_dir()]: - if folder.stem.startswith("__") or folder.stem.startswith("!"): - continue - try: - plugin = AppPlugin(con=con, env=env, path=folder) - plugins[plugin.name] = plugin - except Exception as e: - print_tb(e.__traceback__) - print(e) - return dict(sorted(plugins.items())) + self.template_installed: SubscribableEvent[AssembledTemplateInstalledArgs] = ( + SubscribableEvent() + ) + for parent_template in set([templ.parent for templ in templates]): + parent_template.template_installed.add_listener( + lambda _: self._on_child_template_installed(parent_template) + ) + self.config_state_changed: SubscribableEvent[ + AssembledTemplateConfigChangedArgs + ] = SubscribableEvent() + confs_for_classes: dict[ConfigHandler, str] = {} + for template in self.templates: + for class_name, template_details in template.template_classes.items(): + confs_for_classes.setdefault(template_details["config"], class_name) + for config, class_name in confs_for_classes.items(): + config.config_added.add_listener( + lambda _: self._on_config_state_changed( + class_name=class_name, config=config, has_config=True + ) + ) + config.config_deleted.add_listener( + lambda _: self._on_config_state_changed( + class_name=class_name, config=config, has_config=False + ) + ) -""" -* Template Utils -""" + @cached_property + def layout_categories(self) -> set[LayoutCategory]: + categories: set[LayoutCategory] = set() + return categories.union( + *[template.layout_categories for template in self.templates] + ) + + @property + def installed_template_files(self) -> list[str]: + return [ + parent_template.file_name + for parent_template in set([templ.parent for templ in self.templates]) + if parent_template.is_installed + ] + + @property + def missing_template_files(self) -> list[str]: + return [ + parent_template.file_name + for parent_template in set([templ.parent for templ in self.templates]) + if not parent_template.is_installed + ] + + def is_installed(self, layout_category: LayoutCategory | None = None) -> bool: + for template in self.templates: + if layout_category and layout_category not in template.layout_categories: + continue + + if not template.parent.is_installed: + return False + return True + + def has_config(self, layout_category: LayoutCategory | None = None) -> bool: + for template in self.templates: + if layout_category and layout_category not in template.layout_categories: + continue + + if template.has_config(layout_category): + return True + return False + + def get_config(self, class_name: str) -> ConfigHandler | None: + for template in self.templates: + if conf_mgr := template.get_config(class_name): + return conf_mgr + + def get_preview_image_path(self, layout: LayoutCategory) -> Path | None: + for template in self.templates: + if preview_path := template.get_preview_image_path(layout): + return preview_path + + def get_class_name_and_file_for_layout( + self, layout_type: LayoutType + ) -> tuple[str, Path] | None: + for template in self.templates: + if class_name := template.get_class_name_for_layout(layout_type): + return (class_name, template.parent.path_psd) + return None + + def _on_child_template_installed(self, template: AppTemplate) -> None: + self.template_installed.trigger( + AssembledTemplateInstalledArgs(sender=self, origin=template) + ) + + def _on_config_state_changed( + self, class_name: str, config: ConfigHandler, has_config: bool + ) -> None: + self.config_state_changed.trigger( + AssembledTemplateConfigChangedArgs( + sender=self, class_name=class_name, config=config, has_config=has_config + ) + ) + + +class TemplateLibrary: + def __init__( + self, + con: AppConstants, + env: AppEnvironment, + initial_template_file_versions: TemplateFileVersionsModel, + template_file_versions: TemplateFileVersionsModel, + plugins: dict[str, AppPlugin], + ) -> None: + self.initial_versions = initial_template_file_versions + self.versions = template_file_versions + self.templates: list[AppTemplate] = get_all_templates( + con, env, plugins, template_file_versions.root + ) + + built_in, grouped_by_plugin = self._group_templates_by_plugin(self.templates) + + self.built_in_templates_by_name = self._group_templates_by_name(built_in) + self.plugin_templates_by_name: dict[str, dict[str, AssembledTemplate]] = {} + + for plugin, templates in grouped_by_plugin.items(): + self.plugin_templates_by_name[plugin.id] = self._group_templates_by_name( + templates, plugin + ) + + def save_template_versions(self) -> None: + if self.initial_versions != self.versions: + dump_model(PATH.SRC_DATA_VERSIONS, self.versions) + + def _group_templates_by_plugin( + self, templates: list[AppTemplate] + ) -> tuple[list[AppTemplate], dict[AppPlugin, list[AppTemplate]]]: + built_in: list[AppTemplate] = [] + grouped_by_plugin: dict[AppPlugin, list[AppTemplate]] = {} + for template in templates: + if template.plugin: + grouped_by_plugin.setdefault(template.plugin, []) + grouped_by_plugin[template.plugin].append(template) + else: + built_in.append(template) + return (built_in, grouped_by_plugin) + + def _group_templates_by_name( + self, templates: list[AppTemplate], plugin: AppPlugin | None = None + ) -> dict[str, AssembledTemplate]: + grouped: dict[str, list[NamedTemplate]] = {} + for root_template in templates: + for named_template in root_template.named_templates.values(): + grouped.setdefault(named_template.name, []) + grouped[named_template.name].append(named_template) + return { + name: AssembledTemplate(name, templates, plugin) + for name, templates in grouped.items() + } def get_all_templates( - con: AppConstants, env: AppEnvironment, plugins: dict[str, AppPlugin] + con: AppConstants, + env: AppEnvironment, + plugins: dict[str, AppPlugin], + template_file_versions: dict[str, str], ) -> list[AppTemplate]: """Gets a list of all 'AppTemplate' objects. @@ -1366,14 +1604,25 @@ def get_all_templates( templates: list[AppTemplate] = [] # Load the built-in templates - manifest: dict[str, ManifestTemplateDetails] = load_data_file( - PATH.SRC_DATA_MANIFEST + manifest: TemplateFileInfoRoot = parse_model( + PATH.SRC_DATA_MANIFEST, TemplateFileInfoRoot ) + # Use one config manager per python template class + conifg_managers: dict[str, ConfigHandler] = {} + # Build a TemplateDetails for each template - for file_name, data in manifest.items(): - data["file"] = file_name - templates.append(AppTemplate(con=con, env=env, data=data)) + for file_name, data in manifest.root.items(): + templates.append( + AppTemplate( + con=con, + env=env, + data=data, + file_name=file_name, + config_managers=conifg_managers, + versions=template_file_versions, + ) + ) # Load all plugins and plugin templates for p in plugins.values(): @@ -1381,140 +1630,7 @@ def get_all_templates( return templates -def get_template_map(templates: list[AppTemplate]) -> dict[str, TemplateCategoryMap]: - """Gets a 'TemplateCategoryMap' mapping all templates to their respective categories and types. - - Args: - templates: A unordered list of all 'AppTemplate' objects. - - Returns: - A 'TemplateCategoryMap' mapping all templates to their categories and types. - """ - # Establish our core category map - d: dict[str, TemplateCategoryMap] = { - type_named: {"names": [], "map": {}} - for type_named in layout_map_category.keys() - } - - # Track names for uniqueness - names: dict[str, list[str]] = {} - - # Iterate over template list and add to the category map - for template in templates: - for t, class_map in template.map.items(): - for name, details in class_map.items(): - if t not in layout_map_types: - continue - cat = str(layout_map_types[t]) - - # Ensure name is unique - if name in names.get(t, []): - # Add plugin to name if plugin provided - if details["object"].plugin: - # Swap this objects name - name = f"{name} ({details['object'].plugin.name})" - - # Iterate over name until it is unique - i, n = 1, name - while name in names.get(t, []): - name, i = f"{n} ({i})", i + 1 - - # Add template to map and tracked names - d[cat]["map"].setdefault(t, {})[name] = details - if name not in d[cat]["names"]: - d[cat]["names"].append(name) - names.setdefault(t, []).append(name) - - # Sort names - for t, tcm in d.items(): - sorted_names: list[str] = [] - if "Normal" in tcm["names"]: - sorted_names.append("Normal") - tcm["names"].remove("Normal") - d[t]["names"] = [*sorted_names, *sorted(tcm["names"])] - return d - - -def get_template_map_defaults(d: dict[str, TemplateCategoryMap]) -> TemplateSelectedMap: - """Returns a map of selected defaults for a given 'TemplateCategoryMap'. - - Args: - d: A 'TemplateCategoryMap' object mapping all templates to their respective types. - - Returns: - A 'TemplateSelectedMap' mapping the default selected template for each existing template type. - """ - sel: TemplateSelectedMap = {t: None for t in layout_map_types.keys()} - first_items: dict[str, TemplateDetails] = {} - for template_category_map in d.values(): - for template, name_map in template_category_map["map"].items(): - for name, details in name_map.items(): - if template not in first_items: - first_items[template] = details - if sel.get(template): - continue - if name == "Normal": - sel[template] = details.copy() - - # Ensure each type has a value, fallback on 'first' appearing items - for template, details in sel.items(): - if not details: - if first_item := first_items.get(template): - sel[template] = first_item.copy() - return sel - - -def get_template_map_selected( - selected: TemplateSelectedMap, defaults: TemplateSelectedMap -) -> TemplateSelectedMap: - """Merges the template's selected by the users with a provided mapping of default templates. - - Args: - selected: A mapping of selected templates to template types. - defaults: A mapping of each default template for every existing template type. - - Returns: - A combined mapping of templates to every existing template type. - """ - combined = defaults.copy() - for layout, details in selected.items(): - combined[layout] = details.copy() if details else details - return combined - - -""" -* Updating Templates -""" - - -def check_for_updates(templates: list[AppTemplate]) -> list[AppTemplate]: - """Check our app and plugin manifests for template updates. - - Args: - templates: List of all AppTemplate objects. - - Returns: - List of AppTemplate objects needing an update, sorted by template name. - """ - # Set up our list of templates needing an update - updates: list[AppTemplate] = [] - - # Perform threaded version check requests - with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor: - results: list[tuple[Future[bool], AppTemplate]] = [] - - # Check each template for updates - for t in templates: - results.append((executor.submit(t.check_for_update), t)) - - # Add templates requiring updates - for r in results: - if r[0].result(): - updates.append(r[1]) - - # Return templates needing updates - return sorted(updates, key=lambda x: x.name) - +# endregion Templates # region Symbols diff --git a/src/_state.py b/src/_state.py index cd29a475..93456d71 100644 --- a/src/_state.py +++ b/src/_state.py @@ -1,19 +1,16 @@ """ * Manage Global State (non-GUI) -* Only local imports should be `enums`, `utils`, or `cards`. """ + import os import sys from contextlib import suppress -from os import environ from pathlib import Path from threading import Lock -from typing import Any +from typing import TypedDict from hexproof.hexapi.schema.meta import Meta -from omnitils.exceptions import return_on_exception -from omnitils.files import dump_data_file, get_project_version, load_data_file -from omnitils.metaclass import Singleton +from omnitils.files import get_project_version from omnitils.properties import tracked_prop from pydantic import BaseModel, RootModel from pydantic_settings import ( @@ -26,17 +23,20 @@ from src.enums.layers import LAYERS from src.enums.mtg import CardFonts, mana_symbol_map from src.schema.colors import ColorObject, SymbolColorMap +from src.utils.data_structures import parse_model from src.utils.mtg import get_symbol_colors class HexproofSet(BaseModel): """Cached 'Set' object data from Hexproof.io.""" + code_symbol: str = "default" code_parent: str | None = None count_cards: int count_tokens: int count_printed: int | None = None + HexproofSets = RootModel[dict[str, HexproofSet]] HexproofMetas = RootModel[dict[str, Meta]] @@ -47,18 +47,19 @@ class HexproofSet(BaseModel): # Establish global root, based on frozen executable or Python __PATH_CWD__: Path = Path(os.getcwd()) -__PATH_ROOT__: Path | None = ( +__PATH_ROOT__: Path = ( # Path handling for PyInstalller build Path(sys.executable).parent if (getattr(sys, "frozen", False)) # Path handling for regular Python - else (Path(__file__).parent.parent if __file__ else __PATH_CWD__) + else (Path(__file__).parent.parent) ) # Switch to root directory if current directory differs if str(__PATH_CWD__) != str(__PATH_ROOT__): os.chdir(__PATH_ROOT__) + class DefinedPaths: """Class for defining reusable named Path objects.""" @@ -83,83 +84,86 @@ def ensure_paths(cls): class PATH(DefinedPaths): """Define app-wide paths that are always relational to the root project path.""" - __metaclass__ = Singleton + CWD = __PATH_ROOT__ # Root Level Directories - SRC = CWD / 'src' - OUT = CWD / 'out' - ART = CWD / 'art' - LOGS = CWD / 'logs' - FONTS = CWD / 'fonts' - PLUGINS = CWD / 'plugins' - TEMPLATES = CWD / 'templates' - PROJECT_FILE = CWD / 'pyproject.toml' + SRC = CWD / "src" + OUT = CWD / "out" + LOGS = CWD / "logs" + FONTS = CWD / "fonts" + PLUGINS = CWD / "plugins" + TEMPLATES = CWD / "templates" + PROJECT_FILE = CWD / "pyproject.toml" # Source Level Directories - SRC_IMG = SRC / 'img' - SRC_DATA = SRC / 'data' + SRC_IMG = SRC / "img" + SRC_DATA = SRC / "data" # Data Level Directories - SRC_DATA_KV = SRC_DATA / 'kv' - SRC_DATA_TESTS = SRC_DATA / 'tests' - SRC_DATA_CONFIG = SRC_DATA / 'config' - SRC_DATA_HEXPROOF = SRC_DATA / 'hexproof' - SRC_DATA_CONFIG_INI = SRC_DATA / 'config_ini' + SRC_DATA_TESTS = SRC_DATA / "tests" + SRC_DATA_CONFIG = SRC_DATA / "config" + SRC_DATA_HEXPROOF = SRC_DATA / "hexproof" + SRC_DATA_CONFIG_INI = SRC_DATA / "config_ini" + SRC_DATA_PREFERENCES = SRC_DATA / "preferences" # Data Level Files - SRC_DATA_ENV = SRC_DATA / 'env.yml' - SRC_DATA_ENV_DEFAULT = SRC_DATA / 'env.default.yml' - SRC_DATA_WATERMARKS = SRC_DATA / 'watermarks.yml' - SRC_DATA_MANIFEST = SRC_DATA / 'manifest.yml' - SRC_DATA_HEXPROOF_SET = (SRC_DATA_HEXPROOF / 'set').with_suffix('.json') - SRC_DATA_HEXPROOF_META = (SRC_DATA_HEXPROOF / 'meta').with_suffix('.json') + SRC_DATA_ENV = SRC_DATA / "env.yml" + SRC_DATA_ENV_DEFAULT = SRC_DATA / "env.default.yml" + SRC_DATA_WATERMARKS = SRC_DATA / "watermarks.yml" + SRC_DATA_MANIFEST = SRC_DATA / "manifest.yml" + SRC_DATA_HEXPROOF_SET = SRC_DATA_HEXPROOF / "set.json" + SRC_DATA_HEXPROOF_META = SRC_DATA_HEXPROOF / "meta.json" + + # Test Data Files + SRC_DATA_TEMPLATE_RENDER_TEST_CASES = SRC_DATA_TESTS / "template_renders.toml" # Image Level Directories - SRC_IMG_SYMBOLS = SRC_IMG / 'symbols' - SRC_IMG_PREVIEWS = SRC_IMG / 'previews' + SRC_IMG_SYMBOLS = SRC_IMG / "symbols" + SRC_IMG_PREVIEWS = SRC_IMG / "previews" # Image Level Files - SRC_IMG_SYMBOLS_PACKAGE = (SRC_IMG_SYMBOLS / 'package').with_suffix('.zip') + SRC_IMG_SYMBOLS_PACKAGE = SRC_IMG_SYMBOLS / "package.zip" SRC_IMG_SYMBOLS_MANIFEST = SRC_IMG_SYMBOLS / "manifest.json" - SRC_IMG_OVERLAY = (SRC_IMG / 'overlay').with_suffix('.jpg') - SRC_IMG_NOTFOUND = (SRC_IMG / 'notfound').with_suffix('.jpg') + SRC_IMG_OVERLAY = SRC_IMG / "overlay.jpg" + SRC_IMG_NOTFOUND = SRC_IMG / "notfound.jpg" + SRC_IMG_TEST = SRC_IMG / "test.jpg" + SRC_IMG_TEST_FULL_ART = SRC_IMG / "test-fa.jpg" # Config Level Files - SRC_DATA_CONFIG_APP = (SRC_DATA_CONFIG / 'app').with_suffix('.toml') - SRC_DATA_CONFIG_BASE = (SRC_DATA_CONFIG / 'base').with_suffix('.toml') - SRC_DATA_CONFIG_INI_APP = (SRC_DATA_CONFIG_INI / 'app').with_suffix('.ini') - SRC_DATA_CONFIG_INI_BASE = (SRC_DATA_CONFIG_INI / 'base').with_suffix('.ini') + SRC_DATA_CONFIG_APP = SRC_DATA_CONFIG / "app.toml" + SRC_DATA_CONFIG_BASE = SRC_DATA_CONFIG / "base.toml" + SRC_DATA_CONFIG_INI_APP = SRC_DATA_CONFIG_INI / "app.ini" + SRC_DATA_CONFIG_INI_BASE = SRC_DATA_CONFIG_INI / "base.ini" # Logs Level Files - LOGS_SCAN = (LOGS / 'scan').with_suffix('.jpg') - LOGS_ERROR = (LOGS / 'error').with_suffix('.txt') - LOGS_FAILED = (LOGS / 'failed').with_suffix('.txt') - LOGS_COOKIES = (LOGS / 'cookies').with_suffix('.json') + LOGS_SCAN = LOGS / "scan.jpg" + LOGS_ERROR = LOGS / "error.txt" + LOGS_FAILED = LOGS / "failed.txt" + LOGS_COOKIES = LOGS / "cookies.json" # Generated user data files - SRC_DATA_USER = SRC_DATA / 'user.yml' - SRC_DATA_VERSIONS = SRC_DATA / 'versions.yml' + SRC_DATA_USER = SRC_DATA / "user.yml" + SRC_DATA_VERSIONS = SRC_DATA / "versions.yml" """ * App Environment """ -# KIVY Environment -environ.setdefault('KIVY_LOG_MODE', 'PYTHON') -environ.setdefault('KIVY_NO_FILELOG', '1') def _get_proj_version(path: Path) -> str: - if hasattr(sys, '_MEIPASS'): + if hasattr(sys, "_MEIPASS"): # Build with Pyinstaller generated module import __VERSION__ + return str(__VERSION__.version) try: return get_project_version(path) except Exception: return "0.0.0" + class AppEnvironment(BaseSettings): API_GOOGLE: str = "" API_AMAZON: str = "" @@ -197,21 +201,39 @@ def settings_customise_sources( """ +class WatermarkFormat(TypedDict): + scale: float + + +WatermarkFormats = RootModel[dict[str, WatermarkFormat]] + + +class CustomUserDefinitions(BaseModel): + # Fonts + font_rules_text_italic: str | None = None + font_rules_text_bold: str | None = None + font_rules_text: str | None = None + font_collector: str | None = None + font_artist: str | None = None + font_title: str | None = None + font_mana: str | None = None + font_pt: str | None = None + # Numeric font values + flavor_text_lead_divider: float | None = None + flavor_text_lead: float | None = None + line_break_lead: float | None = None + modal_indent: float | None = None + + class AppConstants: """Stores global constants that control app behavior.""" - __metaclass__ = Singleton + _changes: set[str] = set() # Thread locking handlers lock_decompress = Lock() lock_file_save = Lock() - # Standard HTTP request header - http_header = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/39.0.2171.95 Safari/537.36"} - def __init__(self): """Load initial values.""" self.load_defaults() @@ -224,20 +246,20 @@ def load_defaults(self): """Loads default values. Called at launch and between renders to remove any changes made by templates.""" # Define text layer formatting - self.modal_indent = 5.7 - self.line_break_lead = 2.4 - self.flavor_text_lead = 4.4 - self.flavor_text_lead_divider = 7 + self.modal_indent: float = 5.7 + self.line_break_lead: float = 2.4 + self.flavor_text_lead: float = 4.4 + self.flavor_text_lead_divider: float = 7 # Define currently selected fonts as defaults - self.font_rules_text_italic = CardFonts.RULES_ITALIC - self.font_rules_text_bold = CardFonts.RULES_BOLD - self.font_rules_text = CardFonts.RULES - self.font_collector = CardFonts.COLLECTOR - self.font_artist = CardFonts.ARTIST - self.font_title = CardFonts.TITLES - self.font_pt = CardFonts.TITLES - self.font_mana = CardFonts.MANA + self.font_rules_text_italic: str = CardFonts.RULES_ITALIC + self.font_rules_text_bold: str = CardFonts.RULES_BOLD + self.font_rules_text: str = CardFonts.RULES + self.font_collector: str = CardFonts.COLLECTOR + self.font_artist: str = CardFonts.ARTIST + self.font_title: str = CardFonts.TITLES + self.font_pt: str = CardFonts.TITLES + self.font_mana: str = CardFonts.MANA # Load changed values if self._changes: @@ -264,13 +286,12 @@ def mana_symbols(self) -> dict[str, str]: """ @tracked_prop - def watermarks(self) -> dict[str, dict[str,Any]]: + def watermarks(self) -> dict[str, WatermarkFormat]: """dict[str, dict]: Maps watermark names to defined formatting rules.""" with suppress(): # Ensure file exists if not PATH.SRC_DATA_WATERMARKS.is_file(): - dump_data_file({}, PATH.SRC_DATA_WATERMARKS) - return load_data_file(PATH.SRC_DATA_WATERMARKS) + return parse_model(PATH.SRC_DATA_WATERMARKS, WatermarkFormats).root return {} """ @@ -278,13 +299,13 @@ def watermarks(self) -> dict[str, dict[str,Any]]: """ @tracked_prop - def colors(self) -> dict[str, tuple[float,float,float]]: + def colors(self) -> dict[str, tuple[float, float, float]]: """dict[str, list[int]]: Named reusable colors.""" return { - 'black': (0, 0, 0), - 'white': (255, 255, 255), - 'silver': (167, 177, 186), - 'gold': (166, 135, 75) + "black": (0, 0, 0), + "white": (255, 255, 255), + "silver": (167, 177, 186), + "gold": (166, 135, 75), } @tracked_prop @@ -294,18 +315,22 @@ def mana_colors(self) -> SymbolColorMap: @tracked_prop def symbol_map(self) -> dict[str, tuple[str, list[ColorObject | None]]]: - """dict[str, tuple[str, list[ColorObject]]]: Uses the symbol map and mana_colors to map - symbol character strings and colors to their Scryfall symbol string.""" - return {sym: (n, get_symbol_colors(sym, n, self.mana_colors)) for sym, n in self.mana_symbols.items()} + """Uses the symbol map and mana_colors to map + symbol character strings and colors to their Scryfall symbol string.""" + return { + sym: (n, get_symbol_colors(sym, n, self.mana_colors)) + for sym, n in self.mana_symbols.items() + } def build_symbol_map( - self, colors: SymbolColorMap | None = None, - symbols: dict[str, str] | None = None + self, + colors: SymbolColorMap | None = None, + symbols: dict[str, str] | None = None, ) -> None: - """Establishes a new `symbol_color_map` using a provided color map and symbol map. + """Sets a new mapping for symbol colors. Affects e.g. mana colors. Args: - colors: A `SymbolColorMap`, uses default if not provided. + colors: A model mapping color names to colors, uses default if not provided. symbols: A dictionary mapping font characters to their Scryfall symbol string. Uses default if not provided. """ @@ -315,7 +340,8 @@ def build_symbol_map( self.mana_symbols = symbols self.symbol_map = { sym: (n, get_symbol_colors(sym, n, self.mana_colors)) - for sym, n in self.mana_symbols.items()} + for sym, n in self.mana_symbols.items() + } """ * Tracked Properties: Masks and Gradients @@ -328,41 +354,25 @@ def masks(self) -> dict[int, list[str]]: 2: [LAYERS.HALF], 3: [LAYERS.THIRD, LAYERS.TWO_THIRDS], 4: [LAYERS.QUARTER, LAYERS.HALF, LAYERS.THREE_QUARTERS], - 5: [LAYERS.FIFTH, LAYERS.TWO_FIFTHS, LAYERS.THREE_FIFTHS, LAYERS.FOUR_FIFTHS] + 5: [ + LAYERS.FIFTH, + LAYERS.TWO_FIFTHS, + LAYERS.THREE_FIFTHS, + LAYERS.FOUR_FIFTHS, + ], } @tracked_prop def gradient_locations(self) -> dict[int, list[int | float]]: """dict[int, list[Union[int, float]]: Maps gradient locations to a number representing the number - of color splits.""" + of color splits.""" return { - 2: [.40, .60], - 3: [.26, .36, .64, .74], - 4: [.20, .30, .45, .55, .70, .80], - 5: [.20, .25, .35, .45, .55, .65, .75, .80] + 2: [0.40, 0.60], + 3: [0.26, 0.36, 0.64, 0.74], + 4: [0.20, 0.30, 0.45, 0.55, 0.70, 0.80], + 5: [0.20, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.80], } - """ - * Tracked Properties: Template Versions - """ - - @tracked_prop - def versions(self) -> dict[str,str]: - """dict[str, str]: Maps version numbers to template file identifiers.""" - with suppress(): - # Ensure file exists - if not PATH.SRC_DATA_VERSIONS.is_file(): - dump_data_file({}, PATH.SRC_DATA_VERSIONS) - return load_data_file(PATH.SRC_DATA_VERSIONS) - return {} - - def update_version_tracker(self): - """Updates the version tracker JSON file with current values.""" - dump_data_file(self.versions, PATH.SRC_DATA_VERSIONS) - if 'versions' in self._changes: - self._changes.remove('versions') - delattr(self, 'versions') - """ * Tracked Properties: Hexproof.io Data """ @@ -383,53 +393,38 @@ def metadata(self) -> dict[str, Meta]: def get_user_data(self): """Loads the user data file and replaces any necessary data.""" - # Write a blank data file if not found - if not PATH.SRC_DATA_USER.is_file(): - dump_data_file({}, PATH.SRC_DATA_USER) - - # Pull the version tracker - f = load_data_file(PATH.SRC_DATA_USER) - - # Load user data - [self.__setattr__(name, f[name]) for name in [ - # Fonts - 'font_rules_text_italic', - 'font_rules_text_bold', - 'font_rules_text', - 'font_collector', - 'font_artist', - 'font_title', - 'font_mana', - 'font_pt', - - # Numeric font values - 'flavor_text_lead_divider', - 'flavor_text_lead', - 'line_break_lead', - 'modal_indent' - ] if f.get(name)] + if PATH.SRC_DATA_USER.is_file(): + data = parse_model(PATH.SRC_DATA_USER, CustomUserDefinitions) + + for field in data.model_fields_set: + if (value := getattr(data, field, None)) is not None: + setattr(self, field, value) """ * Import: Hexproof API Data """ - @return_on_exception({}) def get_set_data(self) -> dict[str, HexproofSet]: """Loaded data from the 'set' data file.""" - if not PATH.SRC_DATA_HEXPROOF_SET.is_file(): + try: + if not PATH.SRC_DATA_HEXPROOF_SET.is_file(): + return {} + + # Read set data + return HexproofSets.model_validate_json( + PATH.SRC_DATA_HEXPROOF_SET.read_bytes() + ).root + except Exception: return {} - # Read set data - return HexproofSets.model_validate_json( - PATH.SRC_DATA_HEXPROOF_SET.read_bytes() - ).root - - @return_on_exception({}) def get_meta_data(self) -> dict[str, Meta]: """Loaded data from the 'meta' data file.""" - if not PATH.SRC_DATA_HEXPROOF_META.is_file(): + try: + if not PATH.SRC_DATA_HEXPROOF_META.is_file(): + return {} + + return HexproofMetas.model_validate_json( + PATH.SRC_DATA_HEXPROOF_META.read_bytes() + ).root + except Exception: return {} - - return HexproofMetas.model_validate_json( - PATH.SRC_DATA_HEXPROOF_META.read_bytes() - ).root diff --git a/src/cards.py b/src/cards.py index eb58b47a..29bbd65e 100644 --- a/src/cards.py +++ b/src/cards.py @@ -2,21 +2,29 @@ * Card Data Module * Handles raw card data fetching and processing """ -# Standard Library Imports + from contextlib import suppress +from logging import Logger, getLogger from pathlib import Path -from typing import Any, TypedDict +from typing import TypedDict -# Third Party Imports from omnitils.strings import normalize_str +from pathvalidate import sanitize_filename -# Local Imports from src._config import AppConfig -from src.console import TerminalConsole, msg_warn from src.enums.mtg import CardTextPatterns, TransformIcons, non_italics_abilities -from src.gui.console import GUIConsole from src.schema.colors import ColorObject -from src.utils import scryfall +from src.utils.scryfall import ( + ScryfallCard, + ScryfallCardFace, + ScryfallException, + ScryfallRelatedCard, + get_card_search, + get_card_unique, + get_card_via_url, +) + +_logger = getLogger(__name__) """ * Types @@ -36,7 +44,7 @@ class CardDetails(TypedDict): number: str artist: str creator: str - file: str | Path + file: Path class FrameDetails(TypedDict): @@ -49,6 +57,29 @@ class FrameDetails(TypedDict): is_hybrid: bool +_filename_character_replacements: dict[str,str] = { + "<": "<", + ">": ">", + ":": ":", + """: '"', + "/": "/", + "\": "\\", + "│": "|", + "?": "?", + "*": "*" +} +""" +Windows filenames disallow many characters which might be needed in some +card or artist names, so we can use similar fullwidth unicode characters, +which otherwise won't likely see much or any use when proxying cards, in the +filenames and then replace them with the disallowed characters after reading the +names into Proxyshop. +""" + +_reverse_filename_character_replacements: dict[str,str] = { + value: key for key, value in _filename_character_replacements.items() +} + """ * Handling Data Requests """ @@ -57,8 +88,7 @@ class FrameDetails(TypedDict): def get_card_data( card: CardDetails, cfg: AppConfig, - logger: GUIConsole | TerminalConsole | None = None, -) -> scryfall.ScryfallCard | None: +) -> ScryfallCard | None: """Fetch card data from the Scryfall API. Args: @@ -83,31 +113,32 @@ def get_card_data( } if not number else {} # Establish Scryfall fetch action - action = scryfall.get_card_unique if number else scryfall.get_card_search + action = get_card_unique if number else get_card_search params = [code, number] if number else [name, code] # Is this an alternate language request? if cfg.lang != "en": # Pull the alternate language card - with suppress(Exception): - data = action(*params, lang=cfg.lang, **kwargs) - return data - # Language couldn't be found - if logger: - logger.update(msg_warn(f'Reverting to English: [b]{name}[/b]')) + try: + return action(*params, lang=cfg.lang, **kwargs) + except ScryfallException as exc: + _logger.warning( + f"Couldn't find language {cfg.lang} for {name}. Reverting to English.", + exc_info=exc + ) # Query the card in English, retry with extras if failed with suppress(Exception): - data = action(*params, **kwargs) - return data + return action(*params, **kwargs) if not number and not cfg.scry_extras: # Retry with extras included, case: Planar cards - with suppress(Exception): + try: kwargs['include_extras'] = 'True' data = action(*params, **kwargs) return data - return + except ScryfallException: + _logger.exception("Couldn't retrieve card from Scryfall even with include_extras enabled.") """ @@ -115,7 +146,7 @@ def get_card_data( """ -def parse_card_info(file_path: Path) -> CardDetails: +def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDetails: """Retrieve card name from the input file, and optional tags (artist, set, number). Args: @@ -125,7 +156,10 @@ def parse_card_info(file_path: Path) -> CardDetails: Dict of card details. """ # Extract just the card name - file_name = file_path.stem + file_name = name_override or file_path.stem + + for substitute, replacement in _filename_character_replacements.items(): + file_name = file_name.replace(substitute, replacement) # Match pattern and format data name_split = CardTextPatterns.PATH_SPLIT.split(file_name) @@ -149,7 +183,7 @@ def parse_card_info(file_path: Path) -> CardDetails: """ -def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfall.ScryfallCard: +def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: """Process any additional required data before sending it to the layout object. Args: @@ -165,8 +199,8 @@ def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfal # Modify meld card data to fit transform layout if data.layout == 'meld': # Ignore tokens and other objects - front: list[scryfall.ScryfallRelatedCard] = [] - back: scryfall.ScryfallRelatedCard | None = None + front: list[ScryfallRelatedCard] = [] + back: ScryfallRelatedCard | None = None for part in data.all_parts if data.all_parts else []: if part.component == 'meld_part': front.append(part) @@ -176,28 +210,32 @@ def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfal # Figure out if card is a front or a back is_back = back and name_normalized == normalize_str(back.name, no_space=True) - faces: list[scryfall.ScryfallRelatedCard | None] = [front[0], back] if ( + faces: list[ScryfallRelatedCard | None] = [front[0], back] if ( is_back or name_normalized == normalize_str(front[0].name, no_space=True) ) else [front[1], back] - if is_back and back: - data = scryfall.get_card_via_url(str(back.uri)) - data.layout = "normal" - else: - # Pull JSON data for each face and set object to card_face - data.card_faces = [] - for face in faces: - if face: - face_data = scryfall.get_card_via_url(str(face.uri)) - face_data_dict = face_data.model_dump() - face_data_dict["object"] = "card_face" - data.card_faces.append(scryfall.ScryfallCardFace(**face_data_dict)) - - # Add meld transform icon if none provided - if not data.frame_effects or not any([bool(n in TransformIcons) for n in data.frame_effects]): - data.frame_effects = ["meld"] - data.layout = "transform" + try: + if is_back and back: + data = get_card_via_url(str(back.uri)) + data.layout = "normal" + else: + # Pull JSON data for each face and set object to card_face + data.card_faces = [] + for face in faces: + if face: + face_data = get_card_via_url(str(face.uri)) + face_data_dict = face_data.model_dump() + face_data_dict["object"] = "card_face" + data.card_faces.append(ScryfallCardFace(**face_data_dict)) + + # Add meld transform icon if none provided + if not data.frame_effects or not any([bool(n in TransformIcons) for n in data.frame_effects]): + data.frame_effects = ["meld"] + data.layout = "transform" + except ScryfallException: + _logger.exception("Couldn't retrieve additional card details for a meld card.") + raise # Check for alternate MDFC / Transform layouts if data.card_faces: @@ -263,7 +301,7 @@ def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfal return data # Check for Station layout - if data.oracle_text and 'STATION ' in data.oracle_text: + if data.keywords and "Station" in data.keywords: data.layout = 'station' return data @@ -271,6 +309,12 @@ def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfal return data +def sanitize_card_filename(name: str) -> str: + for illegal, substitute in _reverse_filename_character_replacements.items(): + name = name.replace(illegal, substitute) + return sanitize_filename(name) + + """ * Card Text Utilities """ @@ -279,7 +323,7 @@ def process_card_data(data: scryfall.ScryfallCard, card: CardDetails) -> scryfal def locate_symbols( text: str, symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Any | None = None + logger: Logger | None = None ) -> tuple[str, list[CardSymbolString]]: """Locate symbols in the input string, replace them with the proper characters from the mana font, and determine the colors those characters need to be. @@ -311,7 +355,7 @@ def locate_symbols( symbol_indices.append((start, symbol_color)) except (KeyError, IndexError): if logger: - logger.update(f'Symbol not recognized: {symbol}') + logger.warning(f'Symbol not recognized: {symbol}') text = text.replace(symbol, symbol.strip('{}')) # Move to the next symbols start, end = text.find('{'), text.find('}') @@ -322,7 +366,7 @@ def locate_italics( st: str, italics_strings: list[str], symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Any | None = None + logger: Logger | None = None ) -> list[CardItalicString]: """Locate all instances of italic strings in the input string and record their start and end indices. @@ -349,7 +393,7 @@ def locate_italics( italic = italic.replace(symbol, symbol_map[symbol][0]) except (KeyError, IndexError): if logger: - logger.update(f'Symbol not recognized: {symbol}') + logger.warning(f'Symbol not recognized: {symbol}') st = st.replace(symbol, symbol.strip('{}')) # Move to the next symbol start, end = italic.find('{'), italic.find('}') diff --git a/src/commands/render.py b/src/commands/render.py index 6a33f0fd..81c5b6df 100644 --- a/src/commands/render.py +++ b/src/commands/render.py @@ -11,7 +11,7 @@ # Local Imports from src import CON, TEMPLATE_DEFAULTS from src._loader import TemplateDetails -from src.cards import CardDetails +from src.cards import CardDetails, parse_card_info from src.layouts import layout_map """ @@ -54,10 +54,7 @@ def render_target(data_file: str = None): # Todo: Make custom card data an optional filepath argument data_file = Path(CON.cwd, 'customs', data_file).with_suffix('.json') card = load_data_file(data_file) - file_details: CardDetails = { - 'file': art_file, 'name': card.get('name', ''), - 'set': '', 'artist': '', 'creator': '', 'number': '' - } + file_details: CardDetails = parse_card_info(art_path) # Get appropriate layout class and initialize it # Todo: Use the appropriate layout class provided @@ -66,7 +63,7 @@ def render_target(data_file: str = None): # Get appropriate template for this layout # Todo: Use default, support optional template name argument or custom defined - template: TemplateDetails = TEMPLATE_DEFAULTS.get(layout.card_class) + template: TemplateDetails = TEMPLATE_DEFAULTS.get(layout.type) template_class = template['object'].get_template_class(template['class_name']) layout.template_file = template['object'].path_psd diff --git a/src/commands/test/__init__.py b/src/commands/test/__init__.py index aaf1fb6a..7743c5c1 100644 --- a/src/commands/test/__init__.py +++ b/src/commands/test/__init__.py @@ -1,13 +1,16 @@ """ * CLI Commands: Testing """ -# Third Party + +from logging import getLogger + import click -# Local Imports -from src import CONSOLE, PATH +from src._state import PATH from src.commands.test import frame_logic, text_logic +_logger = getLogger(__name__) + """ * Commands """ @@ -20,7 +23,7 @@ @click.option('-T', '--target', is_flag=True, default=False, help="Evaluate a specific test case.") def test_frame_logic(target: bool = False): """Run Frame Logic test on all cases.""" - CONSOLE.info(f"Test Utility: Frame Logic ({PATH.CWD})") + _logger.info(f"Test Utility: Frame Logic ({PATH.CWD})") cases = frame_logic.get_frame_logic_cases() # Was this a targeted case? @@ -38,7 +41,7 @@ def test_frame_logic(target: bool = False): # Run the test case = options[choice] - CONSOLE.info(f"CASE: {case}") + _logger.info(f"CASE: {case}") frame_logic.test_target_case(cases[case]) diff --git a/src/commands/test/compression.py b/src/commands/test/compression.py index aa6f9d64..621702ea 100644 --- a/src/commands/test/compression.py +++ b/src/commands/test/compression.py @@ -1,18 +1,17 @@ """ * Tests: Compression """ -# Standard Library Imports + +from logging import getLogger from pathlib import Path from time import perf_counter -# Third Party Imports import matplotlib.pyplot as plt -from PIL.Image import Resampling -from omnitils.files.archive import WordSize, DictionarySize, compress_7z +from omnitils.files.archive import DictionarySize, WordSize, compress_7z from omnitils.img import downscale_image_by_width +from PIL.Image import Resampling -# Local Imports -from src import CONSOLE +_logger = getLogger(__name__) """ * Test Funcs @@ -95,9 +94,9 @@ def test_7z_compression(path: Path) -> dict: def test_jpeg_compression( path: Path, - test_dpi=True, - test_resample=True, - test_optimize=True, + test_dpi: bool =True, + test_resample: bool =True, + test_optimize: bool =True, test_quality: list[int] | None = None ) -> None: """ @@ -116,7 +115,7 @@ def test_jpeg_compression( QUALITY, i = test_quality or [95, 90, 85, 80], 0 # Loop through each required test - CONSOLE.info("=" * 50) + _logger.info("=" * 50) for _W in WIDTH: for _R in RESAMPLE: for _Q in QUALITY: @@ -127,7 +126,7 @@ def test_jpeg_compression( CURRENT = (f"{i}. {'800' if _W < 3264 else '1200'} " f"{'lanczos' if _R == Resampling.LANCZOS else 'bicubic'} " f"{_Q} {'optimize_YES' if _O else 'optimize_NO'}") - CONSOLE.info(f"TESTING: {CURRENT}") + _logger.info(f"TESTING: {CURRENT}") SAVE_TO = path.parent / 'compressed' / f'{CURRENT}.jpg' downscale_image_by_width( path_img=path, @@ -139,9 +138,9 @@ def test_jpeg_compression( ) # Print the time of execution - CONSOLE.info(f"TIME COMPLETED: {perf_counter() - s} SECONDS") - CONSOLE.info("=" * 50) + _logger.info(f"TIME COMPLETED: {perf_counter() - s} SECONDS") + _logger.info("=" * 50) i += 1 # Test completed - CONSOLE.info("ALL TESTS COMPLETED!") + _logger.info("ALL TESTS COMPLETED!") diff --git a/src/commands/test/frame_logic.py b/src/commands/test/frame_logic.py index 6e9d1f17..69a9c589 100644 --- a/src/commands/test/frame_logic.py +++ b/src/commands/test/frame_logic.py @@ -2,24 +2,24 @@ * Tests: Frame Logic * Credit to Chilli: https://tinyurl.com/chilli-frame-logic-tests """ -# Standard Library Imports +from concurrent.futures import Future, as_completed +from concurrent.futures import ThreadPoolExecutor as Pool +from logging import getLogger from multiprocessing import cpu_count -from concurrent.futures import ( - ThreadPoolExecutor as Pool, - as_completed, - Future) from pathlib import Path -# Third Part Imports -from tqdm import tqdm from colorama import Fore, Style from omnitils.files import load_data_file +from tqdm import tqdm -# Local Imports -from src import CONSOLE as LOGR, PATH, CFG +from src import CFG +from src._state import PATH +from src.cards import CardDetails, get_card_data, process_card_data +from src.console import LogColors from src.enums.mtg import CardTextPatterns -from src.layouts import layout_map, CardLayout -from src.cards import get_card_data, process_card_data +from src.layouts import CardLayout, layout_map + +_logger = getLogger(__name__) """ * TYPES @@ -75,31 +75,31 @@ def test_case(card_name: str, card_data: FrameData) -> tuple[str, FrameData, Fra # Check if a set code was provided set_code = None if all([n in card_name for n in ['[', ']']]): - set_code = CardTextPatterns.PATH_SET.search(card_name).group(1) - card_name = card_name.replace(f'[{set_code}]', '').strip() + if set_match := CardTextPatterns.PATH_SET.search(card_name): + set_code = set_match.group(1) + card_name = card_name.replace(f'[{set_code}]', '').strip() # Create a fake card details object - details = { + details: CardDetails = { 'name': card_name, - 'set': set_code, + 'set': set_code or "", 'number': '', 'creator': '', - 'file': '', + 'file': Path(), 'artist': '' } # Pull Scryfall data scryfall = get_card_data( card=details, - cfg=CFG, - logger=LOGR) + cfg=CFG) if not scryfall: raise OSError('Did not return valid data from Scryfall.') # Process the Scryfall data scryfall = process_card_data(scryfall, details) except Exception as e: # Exception occurred during Scryfall lookup - return LOGR.failed(f"Scryfall error occurred at card: '{card_name}'", exc_info=e) + return _logger.error(f"Scryfall error occurred at card: '{card_name}'", exc_info=e) # Pull layout data for the card try: @@ -113,7 +113,7 @@ def test_case(card_name: str, card_data: FrameData) -> tuple[str, FrameData, Fra 'creator': '', 'filename': ''})) except Exception as e: # Exception occurred during layout generation - return LOGR.failed(f"Layout error occurred at card: '{card_name}'", exc_info=e) + return _logger.error(f"Layout error occurred at card: '{card_name}'", exc_info=e) # Compare the results if not result_data == card_data: @@ -132,7 +132,7 @@ def test_target_case(cards: dict[str, FrameData]) -> None: """ # Submit tests to a pool with Pool(max_workers=cpu_count()) as executor: - tests_submitted: list[Future] = [] + tests_submitted: list[Future[tuple[str, FrameData, FrameData] | None]] = [] tests_failed: list[tuple[str, FrameData, FrameData]] = [] # Submit tasks to executor @@ -143,9 +143,9 @@ def test_target_case(cards: dict[str, FrameData]) -> None: # Create a progress bar pbar = tqdm( total=len(tests_submitted), - bar_format=f'{LOGR.COLORS.BLUE}' + bar_format=f'{LogColors.BLUE}' '{l_bar}{bar}{r_bar}' - f'{LOGR.COLORS.RESET}') + f'{LogColors.RESET}') # Iterate over completed tasks, update progress bar, add failed tasks for task in as_completed(tests_submitted): @@ -168,17 +168,17 @@ def test_target_case(cards: dict[str, FrameData]) -> None: # Log failed results if tests_failed: - LOGR.critical("=" * 40) + _logger.error("=" * 40) for name, actual, correct in tests_failed: - LOGR.warning(f'NAME: {name}') - LOGR.warning(f'RESULT [Actual / Expected]:\n' - f'{LOGR.COLORS.RESET}{LOGR.COLORS.WHITE}{actual}\n{correct}') - LOGR.critical("=" * 40) - LOGR.critical("SOME TESTS FAILED!") + _logger.warning(f'NAME: {name}') + _logger.warning(f'RESULT [Actual / Expected]:\n' + f'{LogColors.RESET}{LogColors.WHITE}{actual}\n{correct}') + _logger.error("=" * 40) + _logger.error("SOME TESTS FAILED!") return # All tests successful - LOGR.info("ALL TESTS SUCCESSFUL!") + _logger.info("ALL TESTS SUCCESSFUL!") def test_all_cases() -> None: @@ -187,5 +187,5 @@ def test_all_cases() -> None: # Submit tests to a pool for case, cards in cases.items(): - LOGR.debug(f"CASE: {case}") + _logger.info(f"CASE: {case}") test_target_case(cards) diff --git a/src/commands/test/text_logic.py b/src/commands/test/text_logic.py index 154e7d4d..84320a51 100644 --- a/src/commands/test/text_logic.py +++ b/src/commands/test/text_logic.py @@ -1,19 +1,16 @@ """ * Tests: Card Text Logic """ -# Standard Library Imports +from logging import getLogger from pathlib import Path from typing import TypedDict -# Third Party Imports from omnitils.files import load_data_file -# Local Imports -from src import CONSOLE, PATH +from src._state import PATH from src.cards import generate_italics -# Use loguru logger -logr = CONSOLE.logger.opt(colors=True) +_logger = getLogger(__name__) """ * Types @@ -36,10 +33,10 @@ def test_all_cases() -> bool: """Test all Text Logic cases.""" # Load our test cases - SUCCESS = True + success = True test_file: Path = Path(PATH.SRC_DATA_TESTS, 'text_italic.toml') test_cases: dict[str, TestCaseTextItalic] = load_data_file(test_file) - logr.info(f"Testing > Card Text Logic ({test_file.name})") + _logger.info(f"Testing > Card Text Logic ({test_file.name})") # Check each test case for success for name, case in test_cases.items(): @@ -47,24 +44,24 @@ def test_all_cases() -> bool: # Compare actual test results VS expected test results result_actual, result_expected = generate_italics(case.get('text', '')), case.get('result', []) if not sorted(result_actual) == sorted(result_expected): - SUCCESS = False - msg_actual = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_actual, start=1)) - msg_expected = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_expected, start=1)) - logr.error(f"Case: {name} ({case.get('scenario', '')})") + success = False + msg_actual = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_actual, start=1)) + msg_expected = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_expected, start=1)) + _logger.error(f"Case: {name} ({case.get('scenario', '')})") # Log what we expect if not result_expected: - logr.warning("This card doesn't have italic text!") + _logger.warning("This card doesn't have italic text!") else: - logr.warning(f"Italic strings expected: {msg_expected}") + _logger.warning(f"Italic strings expected: {msg_expected}") # Log what we found if not result_actual: - logr.warning("No italic strings were found!") + _logger.warning("No italic strings were found!") else: - logr.warning(f"Italic strings found: {msg_actual}") + _logger.warning(f"Italic strings found: {msg_actual}") # Did any tests fail? - if SUCCESS: - logr.success('All tests successful!') - return SUCCESS + if success: + _logger.info('All tests successful!') + return success diff --git a/src/console.py b/src/console.py index 79da00fc..158c2293 100644 --- a/src/console.py +++ b/src/console.py @@ -1,444 +1,101 @@ """ * Console Module """ -from __future__ import annotations -# Standard Library -import os -import time -from threading import Lock, Event, Thread -from datetime import datetime as dt -from functools import cached_property -from typing import Any, TYPE_CHECKING - -# Third Party Imports -from omnitils.enums import StrConstant -from omnitils.logs import logger -from omnitils.metaclass import Singleton -from loguru._logger import Logger - -# Local Imports -from src._config import AppConfig -from src._state import AppEnvironment, PATH -from src.utils.threading import ThreadInitializedInstance -from src.utils.windows import WindowState - -if TYPE_CHECKING: - from src.utils.adobe import PhotoshopHandler - -""" -* Enums -""" - - -class LogColors (StrConstant): +from collections.abc import Callable +from enum import IntEnum, StrEnum +from logging import ( + CRITICAL, + DEBUG, + ERROR, + INFO, + WARNING, + FileHandler, + Formatter, + Handler, + LogRecord, + StreamHandler, + getLogger, +) + +from src._state import PATH + +DEFAULT_LOG_FORMAT = "[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s" +DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" +DEFAULT_LOG_FORMATTER = Formatter( + fmt=DEFAULT_LOG_FORMAT, + datefmt=DEFAULT_LOG_DATE_FORMAT, +) + +_logger = getLogger() +_logger.setLevel(INFO) +_default_console_handler = StreamHandler() +_default_console_handler.setLevel(DEBUG) +_default_console_handler.setFormatter(DEFAULT_LOG_FORMATTER) +_logger.addHandler(_default_console_handler) +_error_log_file_handler = FileHandler(PATH.LOGS_ERROR, "a", encoding="utf-8") +_error_log_file_handler.setLevel(ERROR) +_error_log_file_handler.setFormatter(DEFAULT_LOG_FORMATTER) +_logger.addHandler(_error_log_file_handler) + +# region Enums + + +class MessageSeverity(IntEnum): + DEBUG = DEBUG + INFO = INFO + WARNING = WARNING + ERROR = ERROR + CRITICAL = CRITICAL + + +class LogColors(StrEnum): """Logging message colors.""" - GRAY = '\x1b[97m' - BLUE = '\x1b[38;5;39m' - YELLOW = '\x1b[38;5;226m' - ORANGE = '\x1b[38;5;202m' - RED = '\x1b[38;5;196m' - RED_BOLD = '\x1b[31;1m' - WHITE = '\x1b[97m' - RESET = '\x1b[0m' - - -class ConsoleMessages(StrConstant): - error = "#a84747" - warning = "#d4c53d" - success = "#59d461" - info = "#6bbcfa" - - -""" -* Console Format Utils -""" - - -def msg_bold(msg: str) -> str: - """Wraps a console string with a bold tag. - - Args: - msg: Text to wrap in a bold tag. - - Returns: - Wrapped message. - """ - return f"[b]{msg}[/b]" - - -def msg_italics(msg: str) -> str: - """Wraps a console string with an italics tag. - - Args: - msg: Message to wrap in an italics tag. - - Returns: - Wrapped message. - """ - return f"[i]{msg}[/i]" - -def msg_error(msg: str, reason: str | None = None) -> str: - """Adds a defined 'error' color tag to Proxyshop console message. + GRAY = "\x1b[97m" + BLUE = "\x1b[38;5;39m" + YELLOW = "\x1b[38;5;226m" + ORANGE = "\x1b[38;5;202m" + RED = "\x1b[38;5;196m" + RED_BOLD = "\x1b[31;1m" + WHITE = "\x1b[97m" + RESET = "\x1b[0m" - Args: - msg: String wrap in color tag. - reason: Reason for the error to include in the message, if provided. - Returns: - Formatted string. - """ - msg = f'[color={ConsoleMessages.error}]{msg}[/color]' - return f"{msg_bold(msg)} - {msg_italics(reason)}" if reason else msg +class LogMessageColor(StrEnum): + DEBUG = "#d8d8d8" + SUCCESS = "#59d461" + INFO = "#6bbcfa" + WARNING = "#d4c53d" + ERROR = "#ff3a3a" + CRITICAL = "#ba86c0" -def msg_warn(msg: str, reason: str | None = None) -> str: - """Adds a defined 'warning' color tag to Proxyshop console message. +LOG_MESSAGE_COLORS_MAP: dict[int, LogMessageColor] = { + MessageSeverity.DEBUG: LogMessageColor.DEBUG, + MessageSeverity.INFO: LogMessageColor.INFO, + MessageSeverity.WARNING: LogMessageColor.WARNING, + MessageSeverity.ERROR: LogMessageColor.ERROR, + MessageSeverity.CRITICAL: LogMessageColor.CRITICAL, +} - Args: - msg: String to wrap in color tag. - reason: Reason for the warning to include in the message, if provided. +# endregion Enums - Returns: - Formatted string. - """ - msg = f'[color={ConsoleMessages.warning}]{msg}[/color]' - return f"{msg_bold(msg)} - {msg_italics(reason)}" if reason else msg +# region Handlers -def msg_success(msg: str) -> str: - """Adds a defined 'success' color tag to Proxyshop console message. - - Args: - msg: String to wrap in color tag. - - Returns: - Formatted string. - """ - return f'[color={ConsoleMessages.success}]{msg}[/color]' - - -def msg_info(msg: str) -> str: - """Adds defined 'info' color tag to Proxyshop console message. - - Args: - msg: String to wrap in color tag. - - Returns: - Formatted string. - """ - return f'[color={ConsoleMessages.info}]{msg}[/color]' - - -def get_bullet_points(text: list[str], char: str = '•') -> str: - """Turns a list of strings into a joined bullet point list string. - - Args: - text: List of strings. - char: Character to use as bullet. - - Returns: - Joined string with bullet points and newlines. - """ - if not text: - return "" - bullet = f"\n{char} " - return str(bullet + bullet.join(text)) - - -""" -* Base Terminal Class -""" - - -class TerminalConsole: - """Wrapper to return the correct global console object.""" - __metaclass__ = Singleton - - # Managing threaded operations - await_lock = Lock() - running = True - waiting = False - continue_next_line = False - +class CustomLogHandler(Handler): def __init__( - self, - cfg: AppConfig, - env: AppEnvironment, - app: ThreadInitializedInstance[PhotoshopHandler], - ): - - # Establish global objects - self.cfg: AppConfig = cfg - self.env: AppEnvironment = env - self.app = app - - """ - Logger Object Properties - """ - - @cached_property - def logger(self) -> Logger: - """Logger interface handling console output.""" - return logger - - """ - Logger Object Methods - """ - - def debug(self, msg: str, *args: Any, **kwargs: Any): - return self.logger.debug(msg, *args, **kwargs) - - def info(self, msg: str, *args: Any, **kwargs: Any): - return self.logger.info(msg, *args, **kwargs) - - def warning(self, msg: str, *args: Any, **kwargs: Any): - return self.logger.warning(msg, *args, **kwargs) - - def failed(self, msg: str, *args: Any, **kwargs: Any): - return self.logger.error(msg, *args, **kwargs) - - def critical(self, msg: str, *args: Any, **kwargs: Any): - return self.logger.critical(msg, *args, **kwargs) - - """ - Reusable Strings - """ - - @property - def message_cancel(self) -> str: - """Boilerplate message for canceling the render process.""" - return "Understood! Canceling render operation.\n" - - @property - def message_waiting(self) -> str: - """Boilerplate message for awaiting a user response.""" - return "Manual editing enabled!\nClick continue to proceed..." - - @property - def message_skipping(self): - """Boilerplate message for skipping a render process.""" - return "Skipping this card!" - - @property - def time(self) -> str: - """Current date and time in human-readable format.""" - return dt.now().strftime('%m-%d-%Y %H:%M') - - """ - Utility Methods - """ - - def log_exception(self, error: Exception, *args: Any) -> None: - """Log python exception. - - Args: - error: Exception object to log. - """ - self.logger.exception(error) - - @staticmethod - def clear() -> None: - """Clear the console output.""" - os.system('cls') - - """ - Console Operations - """ - - def update( - self, - msg: str = "", - exception: Exception | None = None, - end: str = "\n" + self, on_log: Callable[[str, MessageSeverity], None], level: int | str = 0 ) -> None: - """ - Add text to console output. - @param msg: Message to add to the console output, blank if not provided. - @param exception: Exception object to log, if provided. - @param end: String to append at the end of the message, adds a newline if not provided. - """ - - # Check if line is a continuation - if self.continue_next_line: - msg = '[>]' + msg - self.continue_next_line = False - - # Check if line has alternate ending - if end.endswith('\n'): - msg = msg + end[:-1] - else: - msg = msg + end + '[>]' - self.continue_next_line = True - self.logger.info(msg) - if exception: - self.log_exception(exception) - - def log_error( - self, - thr: Event, - card: str, - template: str | None = None, - msg: str = "Encountered a general error!", - e: Exception | None = None - ) -> bool: - """ - Log failed card and exception if provided, then prompt user to make a decision. - @param thr: Event object representing the status of the render thread. - @param card: Card to log in /logs/failed.txt. - @param template: Template to log in /logs/failed.txt. - @param msg: Message to add to the console output. - @param e: Exception to log in /logs/error.txt. - """ - with open(PATH.LOGS_FAILED, 'a', encoding='utf-8') as log: - log.write(f"{card}{f' ({template})' if template else ''} [{self.time}]\n") - return self.error(thr=thr, msg=msg, exception=e) - - def error( - self, - thr: Event | None = None, - msg: str = 'Encountered a general error!', - exception: Exception | None = None, - end: str = '\nShould I continue?\n' - ) -> bool: - """Display error, wait for user to cancel or continue. - - Args: - thr: Event object representing the status of the render thread. - msg: Message to add to the console output. - exception: Exception to log in /logs/error.txt. - end: String to append to the end of the message. - """ - # Stop awaiting any signals - self.end_await() - - # Log exception if provided - if exception: - self.log_exception(exception) - - # Skip the prompts for Dev Mode - if self.env.TEST_MODE: - return False - - # Notify the user, then wait for a continue signal if needed - if self.cfg.skip_failed: - self.update(f"{msg}\n{self.message_skipping}") - return True - - # Previous error already handled - if thr and thr.is_set(): - return False - - # Wait for user response - if self.await_choice(thr, msg, end): - return True - - # Relay the cancellation - self.update(self.message_cancel) - return False - - """ - User Prompt Signals - """ - - def await_choice(self, thr: Event |None, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: - """ - Prompt the user to either continue or cancel. - @param thr: Event object representing the status of the render thread. - @param msg: Message to prompt the user with, uses boilerplate waiting message if not provided. - @param end: String to append to the end of a message, adds a newline if not provided. - @return: True if continued, False if canceled. - """ - # Clear other await procedures, then begin awaiting a user signal - self.end_await() - self.update(msg=msg or self.message_waiting, end=end) - if self.cfg.minimize_photoshop and show_photoshop: - # Show Photoshop in case it is minimized - self.app.instance.set_window_state(WindowState.SHOWDEFAULT) - response = input("[Y / Enter] Continue — [N] Cancel") - - # Signal the choice - choice = True if not response or response == 'Y' else False - self.signal(choice) - - # Cancel the current thread or continue based on user signal - if thr: - self.cancel_thread(thr) if not choice else self.start_await_cancel(thr) - - if choice and self.cfg.minimize_photoshop: - self.app.instance.set_window_state(WindowState.MINIMIZE) - - return choice - - def signal(self, choice: bool): - """ - Signal the user decision to any prompts awaiting a response. - @param choice: True if continuing, False if canceling. - """ - # Continue if True, otherwise False - self.running = choice - - # End await - self.end_await() - - """ - Await Cancelling Procedures - """ - - def start_await_cancel(self, thr: Event) -> None: - """ - Starts an await_cancel loop in a separate thread. - @param thr: Event object representing the status of the render thread. - """ - Thread(target=self.await_cancel, args=(thr,), daemon=True).start() - - def await_cancel(self, thr: Event): - """ - Await a signal from the user to cancel the operation. - @param thr: Event object representing the status of the render thread. - """ - # Await a signal from the user to cancel the operation - self.start_await() - - # Cancel the current thread if running flag set to False - if not self.running: - self.cancel_thread(thr) - - """ - Await Loop Handlers - """ - - def end_await(self) -> None: - """Clears the console of any procedures awaiting a response.""" - # Set the waiting flag - self.waiting = False - - # Ensure await is complete before returning - with self.await_lock: - return - - def start_await(self) -> None: - """Starts an await loop that finishes when waiting is flagged as False.""" - # Set initial running and waiting flags - self.waiting = True - self.running = True - - # Await can only start if others have finished - with self.await_lock: - while self.waiting: - time.sleep(.1) + super().__init__(level) + self.on_log = on_log - """ - Thread Management - """ + def emit(self, record: LogRecord) -> None: + try: + self.on_log(self.format(record), MessageSeverity(record.levelno)) + except Exception: + self.handleError(record) - def cancel_thread(self, thr: Event) -> None: - """Initiate cancellation of a given thread operation. - Args: - thr: Thread event to signal the cancellation. - """ - # Kill the thread and notify - thr.set() - self.update(self.message_cancel) +# endregion Handlers diff --git a/src/data/build/Proxyshop-console.spec b/src/data/build/Proxyshop-console.spec deleted file mode 100644 index 6b14e308..00000000 --- a/src/data/build/Proxyshop-console.spec +++ /dev/null @@ -1,58 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- -# Standard Library Imports -import os -from pathlib import Path - -# Third Party Imports -from kivy_deps import sdl2, glew - -# Configure Build -CWD = Path(os.getcwd()) -IMG = CWD / 'src' / 'img' -block_cipher = None - - -a = Analysis( - [os.path.join(CWD, 'main.py')], - binaries=[], - datas=[], - hiddenimports=[ - 'src.templates', - 'src.gui', - 'kivy', - 'svglib.svglib', - 'reportlab.graphics', - 'requests' - ], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) - -pyz = PYZ( - a.pure, - a.zipped_data, - cipher=block_cipher) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], - name='Proxyshop', - debug=True, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True, - disable_windowed_traceback=False, - target_arch=None, - codesign_identity=None, - icon=os.path.join(IMG, 'favicon.ico')) diff --git a/src/data/build/Proxyshop.spec b/src/data/build/Proxyshop.spec deleted file mode 100644 index 3ff409e6..00000000 --- a/src/data/build/Proxyshop.spec +++ /dev/null @@ -1,58 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- -# Standard Library Imports -import os -from pathlib import Path - -# Third Party Imports -from kivy_deps import sdl2, glew - -# Configure Build -CWD = Path(os.getcwd()) -IMG = CWD / 'src' / 'img' -block_cipher = None - - -a = Analysis( - [os.path.join(CWD, 'main.py')], - binaries=[], - datas=[], - hiddenimports=[ - 'src.templates', - 'src.gui', - 'kivy', - 'svglib.svglib', - 'reportlab.graphics', - 'requests' - ], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) - -pyz = PYZ( - a.pure, - a.zipped_data, - cipher=block_cipher) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)], - name='Proxyshop', - debug=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - target_arch=None, - codesign_identity=None, - icon=os.path.join(IMG, 'favicon.ico')) diff --git a/src/data/config/app.toml b/src/data/config/app.toml index d0384b9c..6fdaeecb 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -84,7 +84,7 @@ default = 0 [DATA."Manual.Text.Editor"] title = "Manual Text Editor" -desc = """Text editor to use for the manual card data editing step. Add curly brackets {} where you wish for the file path to be slotted in the command, e.g. Visual Studio Code can be used with [b]C:\\full\\path\\to\\Code.exe -w "{}"[/b].""" +desc = """Text editor to use for the manual card data editing step. Add curly brackets {} where you wish for the file path to be slotted in the command, e.g. Visual Studio Code can be used with C:\\full\\path\\to\\Code.exe -w "{}".""" type = "string" default = 'notepad "{}"' @@ -108,12 +108,6 @@ default = 0 [RENDER] title = "Render Settings" -[RENDER."Skip.Failed"] -title = "Skip Failed Cards" -desc = """Automatically skip failed cards without asking for confirmation.""" -type = "bool" -default = 0 - [RENDER."Generative.Fill"] title = "Enable Generative Fill" desc = """When enabled, fullart and extended templates will fill empty space using Generative Fill instead of Content Aware Fill. This feature will not work unless running the Photoshop BETA version.""" diff --git a/src/data/config/base.toml b/src/data/config/base.toml index c7d1edc0..7cdc6b71 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -71,8 +71,8 @@ default = 0 [SYMBOLS."Force.Rarity"] title = "Force Use of Specific Rarity" -desc = """Forces Proxyshop to try to use a symbol of a specified rarity. The card's real rarity will be used as a fallback if the specified rarity can't be found. -Possible rarities include: [b]80[/b], [b]B[/b], [b]C[/b], [b]H[/b], [b]M[/b], [b]R[/b], [b]S[/b], [b]T[/b], [b]U[/b], [b]WM[/b] +desc = """Forces Proxyshop to try to use a symbol of a specified rarity. The card's real rarity will be used as a fallback if the specified rarity can't be found.
+Possible rarities include:
80, B, C, H, M, R, S, T, U, WM
You may also specify a code for any extra symbol variants that you might have copied to your installation. Extra variants are matched case sensitively by their filename without the suffix (.svg). Empty string disables this feature.""" type = "string" @@ -87,9 +87,9 @@ title = "Watermarks" [WATERMARKS."Watermark.Mode"] title = "Watermark Mode" -desc = """[b]Automatic[/b]: Generates a watermark when present in Scryfall data. -[b]Fallback[/b]: Automatic, but use Default defined below as a fallback for Scryfall data. -[b]Forced[/b]: Always use Default watermark defined below.""" +desc = """Automatic: Generates a watermark when present in Scryfall data.
+Fallback: Automatic, but use Default defined below as a fallback for Scryfall data.
+Forced: Always use Default watermark defined below.""" type = "options" default = "Disabled" options = ["Disabled", "Automatic", "Fallback", "Forced"] @@ -103,7 +103,7 @@ default = "WOTC" [WATERMARKS."Watermark.Opacity"] title = "Watermark Default Opacity" desc = """Transparency value of the watermark, 0 being invisible 100 being fully visible.""" -type = "numeric" +type = "float" default = 30 [WATERMARKS."Enable.Basic.Watermark"] diff --git a/src/data/kv/app.kv b/src/data/kv/app.kv deleted file mode 100644 index 6c3f23d6..00000000 --- a/src/data/kv/app.kv +++ /dev/null @@ -1,32 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex - -: - id: app_container - orientation: "vertical" - -: - id: app_tabs - do_default_tab: False - tab_width: dp(120) - tab_height: dp(30) - tab_spacing: 0 - padding: app.cont_padding - canvas: - Color: - rgba: get_color_from_hex('#2e2e2e') - Rectangle: - size: self.size - pos: self.pos - -: - id: tab_main - text: "Render Cards" - spacing: 0 - -: - id: tab_creator - text: "Custom Creator" - -: - id: tab_tools - text: "Tools" diff --git a/src/data/kv/console.kv b/src/data/kv/console.kv deleted file mode 100644 index 20382cae..00000000 --- a/src/data/kv/console.kv +++ /dev/null @@ -1,127 +0,0 @@ -#:import HoverButton src.gui.utils.HoverButton -#:import get_font src.gui.utils.get_font -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import ScrollView kivy.uix.scrollview.ScrollView -#:import webbrowser webbrowser -#:import ak asynckivy -#:import ENV src.ENV - -: - id: console - orientation: "horizontal" - padding: dp(10) - canvas: - Color: - rgba: get_color_from_hex("#666666") - Rectangle: - size: root.size - pos: root.pos - BoxLayout: - id: viewport_container - padding: dp(10),dp(10),dp(10),dp(10) - size_hint: 4, 1 - canvas: - Color: - rgba: get_color_from_hex("#151515") - Rectangle: - size: self.width-10,self.height - pos: self.pos - ScrollView: - id: viewport - always_overscroll: False - do_scroll_x: False - size_hint: 1, 1 - GridLayout: - id: output_container - cols: 1 - padding: 0 - spacing: 0 - size_hint: (1,None) - height: self.minimum_height - ConsoleControls: - id: console_controls - orientation: "vertical" - spacing: dp(4) - padding: dp(10) - canvas: - Color: - rgba: get_color_from_hex("#252525") - Rectangle: - size: self.width,self.height - pos: self.pos - BoxLayout: - size_hint_y: None - height: dp(30) - orientation: "horizontal" - ToggleButton: - text: "📌" - font_name: get_font("seguiemj.ttf") - on_press: app.toggle_window_locked() - HoverButton: - text: "📷" - options: [] - font_name: get_font("seguiemj.ttf") - on_release: app.screenshot_window() - HoverButton: - text: "🌍" - options: [] - font_name: get_font("seguiemj.ttf") - on_release: webbrowser.open(f'https://github.com/{ENV.APP_UPDATES_REPO}') - HoverButton: - text: "❔" - options: [] - font_name: get_font("seguiemj.ttf") - on_release: webbrowser.open('https://discord.gg/magicproxies') - HoverButton: - id: continue_btn - size_hint_y: None - height: dp(40) - options: ["Go for it!", "Hurry up!", "Do it!", "Let's go!", "Next!"] - hover_color: "#4a5f8a" - on_press: console.signal(True) - disabled: True - text: "Continue" - font_size: sp(20) - background_normal: "" - background_disabled_normal: "" - background_color: get_color_from_hex("#283c5e") - HoverButton: - id: cancel_btn - size_hint_y: None - height: dp(40) - options: ["STOP!", "End it now!", "No more!", "QUIT!", "Quiddit!", "Enough!"] - hover_color: "#8a4a50" - on_press: console.signal(False) - disabled: True - text: "Cancel" - font_size: sp(20) - background_normal: "" - background_disabled_normal: "" - background_color: get_color_from_hex("#63272d") - HoverButton: - id: update_btn - size_hint_y: None - height: dp(40) - options: ["UPDATE"] - hover_color: "#4a5f8a" - on_press: ak.start(console.main.check_for_updates()) - disabled: False - text: "Update" - font_size: sp(20) - background_normal: "" - background_disabled_normal: "" - background_color: get_color_from_hex("#283c5e") - Label: - text: "" - -: - id: console_output - padding: (0,0) - size_hint: (1,None) - markup: True - font_size: sp(20) - text_size: (self.width, None) - size: self.texture_size - text_autoupdate: True - halign: "left" - valign: "top" diff --git a/src/data/kv/creator.kv b/src/data/kv/creator.kv deleted file mode 100644 index 597eba03..00000000 --- a/src/data/kv/creator.kv +++ /dev/null @@ -1,350 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import get_font src.gui.utils.get_font -#:import HoverButton src.gui.utils.HoverButton -#:import Thread threading.Thread -#:import InputItem src.gui.utils.InputItem -#:import NoEnterInputItem src.gui.utils.NoEnterInputItem -#:import FourNumInput src.gui.utils.FourNumInput -#:import ThreeNumInput src.gui.utils.ThreeNumInput -#:import FourCharInput src.gui.utils.FourCharInput -#:import RaritySpinner src.gui.utils.RaritySpinner -#:import RenderCustomButton src.gui.utils.RenderCustomButton -#:import CollectorInput src.gui.utils.CollectorInput -#:import SetCodeInput src.gui.utils.SetCodeInput -#:import VerticalCenteredInput src.gui.utils.VerticalCenteredInput - -: - id: panel_creator - do_default_tab: False - tab_width: dp(120) - tab_height: dp(30) - padding: dp(5) - tab_spacing: 0 - canvas: - Color: - rgba: get_color_from_hex("#141414") - Rectangle: - size: self.size - pos: self.pos - -: - id: creator_norm - cols: 1 - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - valign: "middle" - VerticalCenteredInput: - id: name - hint_text: "Card Name" - size_hint_x: 2 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: mana_cost - hint_text: "{1}{W}{U}{B}{R}{G}" - font_name: get_font("PlantinMTProRg.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: color_identity - hint_text: "WUBRG" - size_hint_x: .5 - halign: "center" - font_name: get_font("PlantinMTProRg.ttf") - padding_x: dp(6), dp(6) - BoxLayout: - orientation: "horizontal" - size_hint_y: 2 - InputItem: - id: oracle_text - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "Rules text" - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint: (1, 2) - InputItem: - id: flavor_text - font_size: sp(20) - font_name: get_font("PlantinMTProRgIt.ttf", "Roboto-Italic") - hint_text: "Flavor text" - multiline: True - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - VerticalCenteredInput: - id: type_line - text: "Type — Subtype" - size_hint_x: 5 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - CollectorInput: - id: collector_number - size_hint_x: .12 - CollectorInput: - id: card_count - hint_text: "100" - size_hint_x: .12 - SetCodeInput: - id: set - size_hint_x: .12 - VerticalCenteredInput: - id: keywords - hint_text: "Keyword,Keyword 2" - padding_x: dp(6), dp(6) - PTInput: - id: power - size_hint_x: .08 - Label: - text: "/" - size_hint_x: .05 - PTInput: - id: toughness - size_hint_x: .08 - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - VerticalCenteredInput: - id: artist - hint_text: "Artist" - size_hint_x: 2.5 - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - RaritySpinner: - id: rarity - Spinner: - id: template - text: "Normal" - values: root._template_names - on_text: root.select_template(self) - RenderCustomButton: - id: render_btn - press_action: root.render - -: - id: creator_pw - cols: 1 - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - valign: "middle" - VerticalCenteredInput: - id: name - hint_text: "Card Name" - size_hint_x: 2 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding: dp(6), dp(6) - VerticalCenteredInput: - id: mana_cost - hint_text: "{1}{W}{U}{B}{R}{G}" - font_name: get_font("PlantinMTProRg.ttf") - padding: dp(6), dp(6) - VerticalCenteredInput: - id: color_identity - hint_text: "WUBRG" - size_hint_x: .5 - halign: "center" - font_name: get_font("PlantinMTProRg.ttf") - padding_x: dp(6), dp(6) - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_1 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "This is a static ability." - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_2 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "+1: This ability increases loyalty." - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_3 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "-1: This ability decreases loyalty." - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_4 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "0: Planeswalkers need at least 2 ability lines." - multiline: True - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - VerticalCenteredInput: - id: type_line - text: "Legendary Planeswalker — Jace" - size_hint_x: 4 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: keywords - hint_text: "Keyword,Keyword 2" - size_hint_x: 3.15 - halign: "center" - padding_x: dp(6), dp(6) - PTInput: - id: loyalty - hint_text: "6" - size_hint_x: .3 - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - CollectorInput: - id: collector_number - size_hint_x: .4 - CollectorInput: - id: card_count - hint_text: "100" - size_hint_x: .4 - SetCodeInput: - id: set - size_hint_x: .4 - VerticalCenteredInput: - id: artist - hint_text: "Artist" - size_hint_x: 2.5 - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - RaritySpinner: - id: rarity - Spinner: - id: template - text: "Normal" - values: root._template_names - on_text: root.select_template(self) - RenderCustomButton: - id: render_btn - press_action: root.render - size_hint_x: 1.2 - -: - id: creator_saga - cols: 1 - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - valign: "middle" - VerticalCenteredInput: - id: name - hint_text: "Card Name" - size_hint_x: 2 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: mana_cost - hint_text: "{1}{W}{U}{B}{R}{G}" - font_name: get_font("PlantinMTProRg.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: color_identity - hint_text: "WUBRG" - size_hint_x: .5 - halign: "center" - font_name: get_font("PlantinMTProRg.ttf") - padding_x: dp(6), dp(6) - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_1 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "First line" - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_2 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "Second line" - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_3 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "Third line [optional]" - multiline: True - BoxLayout: - orientation: "horizontal" - size_hint_y: 1 - NoEnterInputItem: - id: line_4 - font_size: sp(20) - font_name: get_font("PlantinMTProRg.ttf") - hint_text: "Fourth line [optional]" - multiline: True - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - VerticalCenteredInput: - id: type_line - text: "Enchantment — Saga" - size_hint_x: 5 - font_size: sp(20) - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - VerticalCenteredInput: - id: keywords - hint_text: "Keyword,Keyword 2" - size_hint_x: 2.34 - halign: "center" - padding_x: dp(6), dp(6) - BoxLayout: - size_hint_y: .75 - orientation: "horizontal" - CollectorInput: - id: collector_number - size_hint_x: .4 - CollectorInput: - id: card_count - hint_text: "100" - size_hint_x: .4 - SetCodeInput: - id: set - size_hint_x: .4 - VerticalCenteredInput: - id: artist - hint_text: "Artist" - size_hint_x: 2.5 - font_name: get_font("BelerenProxy-Bold.ttf") - padding_x: dp(6), dp(6) - RaritySpinner: - id: rarity - Spinner: - id: template - text: "Normal" - values: root._template_names - on_text: root.select_template(self) - RenderCustomButton: - id: render_btn - press_action: root.render - size_hint_x: 1.2 diff --git a/src/data/kv/main.kv b/src/data/kv/main.kv deleted file mode 100644 index ef7abef4..00000000 --- a/src/data/kv/main.kv +++ /dev/null @@ -1,135 +0,0 @@ -#:import ButtonImage src.gui.utils.ButtonImage -#:import HoverButton src.gui.utils.HoverButton -#:import get_font src.gui.utils.get_font -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import Thread threading.Thread -#:import ak asynckivy -#:import ScrollEffect kivy.effects.scroll.ScrollEffect - - -: - id: panel_main - orientation: "vertical" - padding: dp(5) - spacing: 0 - canvas: - Color: - rgba: get_color_from_hex("#141414") - Rectangle: - size: panel_main.size - pos: panel_main.pos - BoxLayout: - orientation: "horizontal" - size_hint: (1, None) - height: dp(40) - HoverButton: - id: rend_targ_btn - text: "Render Target" - options: ["RENDER TARGET"] - font_size: sp(18) - on_press: Thread(target=app.render_all, args=(True, []), daemon=True).start() - background_color: get_color_from_hex("#376aa3") - HoverButton: - id: rend_all_btn - text: "Render All" - options: ["RENDER ALL"] - font_size: sp(18) - on_press: Thread(target=app.render_all, daemon=True).start() - background_color: get_color_from_hex("#376aa3") - HoverButton: - id: app_settings_btn - text: "Global Settings" - options: ["GLOBAL SETTINGS"] - font_size: sp(18) - on_press: ak.start(app.open_app_settings()) - background_color: get_color_from_hex("#376aa3") - TemplateModule: - pos: (0, 0) - tab_pos: "top_mid" - tab_width: None - tab_height: dp(26) - tab_spacing: 0 - do_default_tab: False - size_hint: (1, 2) - -: - orientation: "horizontal" - size_hint: (1, 1) - pos: 0,0 - spacing: 0 - background_color: get_color_from_hex("#303030") - BoxLayout: - id: template_view_container - size_hint: (1, 1) - background_color: get_color_from_hex("#303030") - BoxLayout: - id: preview_container - orientation: "vertical" - size_hint: (None, 1) - width: 0.735294117*preview_image.height if preview_image.height <=612 else 450 - background_color: get_color_from_hex("#303030") - ButtonImage: - source: 'src/img/notfound.jpg' - id: preview_image - mipmap: True - fit_mode: "scale-down" - nocache: True - on_press: self.manager.toggle_preview_image() - canvas: - Rectangle: - source: "src/img/overlay.png" - size: preview_image.size - pos: preview_image.pos - BoxLayout: - size_hint: (1, None) - height: preview_container.height-612 if preview_container.height-612 >= 0 else 0 - -: - padding: 0 - spacing: 0 - size_hint: (1,None) - height: self.minimum_height - orientation: 'vertical' - -: - bar_width: dp(4) - always_overscroll: False - do_scroll_x: False - size_hint: (1,1) - scroll_wheel_distance: 40 - effect_cls: ScrollEffect - canvas: - Color: - rgba: get_color_from_hex("#111111") - Rectangle: - size: self.size - pos: self.pos - -: - orientation: "horizontal" - size_hint: (1, None) - height: dp(35) - ToggleButton: - id: toggle_button - on_press: app.select_template(self) - size_hint: (1, 1) - halign: "left" - TemplateSettingsButton: - id: settings_button - text: "⚙️" - options: [] - font_name: get_font("seguiemj.ttf") - background_color: get_color_from_hex("#598cc5") - size_hint: (None, 1) - width: dp(40) - on_press: ak.start(self.open_settings()) - TemplateResetDefaultButton: - id: reset_default_button - text: "🧹️" - options: [] - font_name: get_font("seguiemj.ttf") - hover_color: "#ede98a" - background_color: get_color_from_hex("#c5c059") - size_hint: (None, 1) - width: dp(40) - on_press: ak.start(self.reset_default()) diff --git a/src/data/kv/test.kv b/src/data/kv/test.kv deleted file mode 100644 index f98d6391..00000000 --- a/src/data/kv/test.kv +++ /dev/null @@ -1,46 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import ScrollView kivy.uix.scrollview.ScrollView -#:import Thread threading.Thread - -: - id: test_app - orientation: "vertical" - BoxLayout: - size_hint: 1, None - height: dp(60) - orientation: "horizontal" - spacing: dp(12) - padding: dp(12) - HoverButton: - id: test_all - font_size: sp(20) - text: "Test All" - font_name: "Roboto" - on_release: Thread(target=app.test_all, daemon=True).start() - HoverButton: - id: test_target - font_size: sp(20) - text: "Deep Test Target" - font_name: "Roboto" - on_release: test_app.select_template() - HoverButton: - id: test_all_deep - font_size: sp(20) - text: "Deep Test All" - font_name: "Roboto" - on_release: Thread(target=app.test_all, args=(True,), daemon=True).start() - -: - title: "Select a template to test!" - ScrollView: - id: viewport - always_overscroll: False - do_scroll_x: False - size_hint: (1, 1) - scroll_wheel_distance: dp(60) - BoxLayout: - id: content - title: "Select test template" - orientation: "vertical" - size_hint: (1, None) - height: self.minimum_height \ No newline at end of file diff --git a/src/data/kv/tools.kv b/src/data/kv/tools.kv deleted file mode 100644 index 0d4b9817..00000000 --- a/src/data/kv/tools.kv +++ /dev/null @@ -1,102 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import Range100NumInput src.gui.utils.Range100NumInput - -: - id: panel_tools - orientation: "vertical" - spacing: dp(8) - padding: dp(5) - canvas: - Color: - rgba: get_color_from_hex("#141414") - Rectangle: - size: self.size - pos: self.pos - BoxLayout: - id: tools_showcase - orientation: "horizontal" - size_hint: (1, None) - height: dp(40) - HoverButton: - id: generate_showcases_target - text: "Render All Showcases" - options: [] - font_size: sp(18) - background_color: get_color_from_hex("#376aa3") - font_name: get_font("Beleren Small Caps.ttf") - on_press: Thread(target=root.render_showcases, daemon=True).start() - HoverButton: - id: generate_showcases - text: "Render Target Showcase" - options: [] - font_size: sp(18) - background_color: get_color_from_hex("#376aa3") - font_name: get_font("Beleren Small Caps.ttf") - on_press: Thread(target=root.render_showcases, args=(True,), daemon=True).start() - BoxLayout: - id: tools_compress - size_hint: (1, None) - height: dp(40) - orientation: "horizontal" - spacing: dp(5) - Label: - text: "Quality:" - halign: 'center' - font_size: sp(18) - bold: True - Range100NumInput: - id: compress_quality - size_hint_x: None - width: dp(50) - halign: 'center' - padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0] - text: "95" - input_filter: 'int' - Label: - text: "800 DPI:" - halign: 'center' - font_size: sp(18) - bold: True - Switch: - id: compress_dpi - text: "800 DPI" - active: False - size_hint_x: None - width: dp(100) - Label: - text: "Compress:" - halign: 'center' - font_size: sp(18) - bold: True - HoverButton: - id: compress_arts - text: "Arts" - size_hint_x: None - width: dp(60) - options: [] - font_size: sp(18) - background_color: get_color_from_hex("#376aa3") - font_name: get_font("Beleren Small Caps.ttf") - on_press: Thread(target=root.compress_arts, daemon=True).start() - HoverButton: - id: compress_renders - text: "Renders" - size_hint_x: None - width: dp(90) - options: [] - font_size: sp(18) - background_color: get_color_from_hex("#376aa3") - font_name: get_font("Beleren Small Caps.ttf") - on_press: Thread(target=root.compress_renders, daemon=True).start() - HoverButton: - id: compress_renders_target - text: "Target" - size_hint_x: None - width: dp(90) - options: [] - font_size: sp(18) - background_color: get_color_from_hex("#376aa3") - font_name: get_font("Beleren Small Caps.ttf") - on_press: Thread(target=root.compress_target, daemon=True).start() - BoxLayout: - size_hint: (1, .8) \ No newline at end of file diff --git a/src/data/kv/updater.kv b/src/data/kv/updater.kv deleted file mode 100644 index 1bb5647b..00000000 --- a/src/data/kv/updater.kv +++ /dev/null @@ -1,106 +0,0 @@ -#:import HoverButton src.gui.utils.HoverButton -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import ScrollView kivy.uix.scrollview.ScrollView -#:import ak asynckivy - -: - title: "Proxyshop Updater" - BoxLayout: - orientation: "vertical" - BoxLayout: - canvas: - Color: - rgba: get_color_from_hex("#181818") - Rectangle: - size: self.width,self.height - pos: self.pos - orientation: "horizontal" - size_hint: (1,None) - height: dp(40) - padding: dp(5) - spacing: dp(10) - Label: - id: loading_text - text: " [i]Checking for updates...[/i]" - halign: "left" - valign: "center" - text_size: self.size - font_size: sp(18) - size_hint: (.85, 1) - markup: True - HoverButton: - options: ["CLOSE"] - text: "Close" - size_hint: (.15,1) - font_size: sp(20) - on_release: root.dismiss() - ScrollView: - id: viewport - always_overscroll: False - do_scroll_x: False - size_hint: (1, 1) - GridLayout: - id: container - cols: 1 - size_hint: (1,None) - height: self.minimum_height - padding: [0,dp(20),0,0] - Image: - id: loading - mipmap: True - source: "src/img/loading.gif" - allow_stretch: True - size_hint: 1, None - height: dp(180) - width: dp(180) - fit_mode: "scale-down" - anim_delay: .06 - -: - id: entry - size_hint: (1,None) - height: dp(40) - padding: dp(5) - spacing: dp(10) - canvas: - Color: - rgba: get_color_from_hex(self.bg_color) - Rectangle: - size: self.width,self.height - pos: self.pos - Label: - id: title - text: self.parent.name - font_size: sp(20) - text_size: self.size - halign: "left" - valign: "center" - size_hint: (.60, 1) - markup: True - Label: - id: status - text: self.parent.status - font_size: sp(18) - text_size: self.size - halign: "right" - valign: "center" - size_hint: (.10, 1) - markup: True - BoxLayout: - id: mark_updated - size_hint: (.15, 1) - HoverButton: - options: ["Dismiss"] - text: "Dismiss" - font_size: sp(18) - on_release: ak.start(entry.mark_updated()) - background_color: get_color_from_hex("#821923") - hover_color: "#b81d2c" - BoxLayout: - id: download - size_hint: (.15, 1) - HoverButton: - options: ["Download"] - text: "Download" - font_size: sp(20) - on_release: ak.start(entry.download_update(self.parent)) \ No newline at end of file diff --git a/src/data/manifest.yml b/src/data/manifest.yml index 528f3a7a..9def8328 100644 --- a/src/data/manifest.yml +++ b/src/data/manifest.yml @@ -1,215 +1,302 @@ normal.psd: - id: '1t0vFPIKctuGmLMvWd3UG8k_mC-_W8LYe' - desc: 'Normal M15-style frame, with additional support for Fullart and Stargazing treatments.' + id: "1t0vFPIKctuGmLMvWd3UG8k_mC-_W8LYe" + desc: "Normal M15-style frame, with additional support for Fullart and Stargazing treatments." templates: - Normal: M15Template - Fullart: FullartTemplate - Stargazing: StargazingTemplate + Normal: + M15Template: + - normal + Fullart: + FullartTemplate: + - normal + Stargazing: + StargazingTemplate: + - normal normal-classic.psd: - id: '1y5_W7Fr-TrRBIVaTdUmpa24Q9p-nPtg0' + id: "1y5_W7Fr-TrRBIVaTdUmpa24Q9p-nPtg0" desc: "Classic frame based on 1997-era MTG cards." - templates: { Classic: ClassicTemplate } + templates: + Classic: + ClassicTemplate: + - normal classic-remastered.psd: - id: '1DYbRMtKRZs5EeRYLl6i5lodzMyhTnIdI' + id: "1DYbRMtKRZs5EeRYLl6i5lodzMyhTnIdI" desc: "A remastered take on the classic 1997 frame treatment, assets provided by iDerp." templates: Classic Remastered: ClassicRemasteredTemplate: - - normal - - transform_front - - transform_back + - normal + - transform_front + - transform_back classic-modern.psd: - id: '1BgakdZ4pKk48LIReoOBN00DhyKX2PeOd' + id: "1BgakdZ4pKk48LIReoOBN00DhyKX2PeOd" desc: "A rendition of iDerp's 'Classic Remastered' template using modern frame structure." templates: Classic Modern: ClassicModernTemplate: - - normal - - transform_front - - transform_back - - mdfc_front - - mdfc_back + - normal + - transform_front + - transform_back + - mdfc_front + - mdfc_back normal-extended.psd: - id: '1iqMrsHlTOeOxCWPFcPaHlXK8x9yjfJ4d' - desc: 'Normal M15-style frame with an extended art border treatment.' - templates: { Extended: ExtendedTemplate } + id: "1iqMrsHlTOeOxCWPFcPaHlXK8x9yjfJ4d" + desc: "Normal M15-style frame with an extended art border treatment." + templates: + Extended: + ExtendedTemplate: + - normal etched.psd: - id: '13wJYra-Nx7NIvl78h6N8l7lEbxNj0V0y' + id: "13wJYra-Nx7NIvl78h6N8l7lEbxNj0V0y" desc: "Based on the 'Etched' showcase introduced in Commander Legends." - templates: { Etched: EtchedTemplate } + templates: + Etched: + EtchedTemplate: + - normal masterpiece.psd: - id: '1h15KM_BVNMxAAvqXkCfXksEwTyQPfgIP' + id: "1h15KM_BVNMxAAvqXkCfXksEwTyQPfgIP" desc: "Based on the 'Invention' showcase introduced in Kaladesh." - templates: { Invention: InventionTemplate } + templates: + Invention: + InventionTemplate: + - normal znrexp.psd: - id: '1b5y4wqNvLuvKeqyQb6QrXDJVxjpuVLHX' + id: "1b5y4wqNvLuvKeqyQb6QrXDJVxjpuVLHX" desc: "Based on the newer 'Expedition' showcase frame introduced in Zendikar Rising." - templates: { Expedition (ZNR): ExpeditionTemplate } + templates: + Expedition - ZNR: + ExpeditionTemplate: + - normal borderless-vector.psd: - id: '1mJ7JLD-aayTvhcrY9_Q21S3i3qAWeb7v' + id: "1mJ7JLD-aayTvhcrY9_Q21S3i3qAWeb7v" desc: "Based on the 'Borderless' box-topper showcase used in most masters sets." templates: Borderless: BorderlessVectorTemplate: - - normal - - transform_front - - transform_back - - mdfc_front - - mdfc_back + - normal + - transform_front + - transform_back + - mdfc_front + - mdfc_back miracle.psd: - id: '1WXfMLkIk4S75210k1l7Z9B6gpol4RqHj' + id: "1WXfMLkIk4S75210k1l7Z9B6gpol4RqHj" desc: "A normal M15-style frame for 'Miracle' cards." - templates: { Miracle: MiracleTemplate } + templates: + Miracle: + MiracleTemplate: + - normal snow.psd: - id: '1cqpVFgFnpOq9I43iy2OqnozuvullGCez' + id: "1cqpVFgFnpOq9I43iy2OqnozuvullGCez" desc: "A normal M15-style frame for 'Snow' cards." - templates: { Snow: SnowTemplate } + templates: + Snow: + SnowTemplate: + - normal universes-beyond.psd: - id: '1UufcIernCbNX0-xNxajVlTc2dLqUkqr6' + id: "1UufcIernCbNX0-xNxajVlTc2dLqUkqr6" desc: "Based on the 'Universes Beyond' frame treatment used in MTG crossover products." templates: Universes Beyond: UniversesBeyondTemplate: - - normal - - transform_front - - transform_back + - normal + - transform_front + - transform_back lord-of-the-rings.psd: - id: '1jFQgCYqev2LpHSFkraaWJIP9q51G8py-' + id: "1jFQgCYqev2LpHSFkraaWJIP9q51G8py-" desc: "Based on the 'Lord of the Rings' showcase introduced in Tales of Middle-earth." - templates: { Lord of the Rings: LOTRTemplate } + templates: + Lord of the Rings: + LOTRTemplate: + - normal tf-front.psd: - id: '13TCpRzvOHfxahpXu568AJ-EZzDDjehi7' + id: "13TCpRzvOHfxahpXu568AJ-EZzDDjehi7" templates: - Normal: { TransformTemplate: transform_front } + Normal: + TransformTemplate: + - transform_front tf-back.psd: - id: '1j__OIXyejP-3H7aWm3IyuILgcj3ASq_L' + id: "1j__OIXyejP-3H7aWm3IyuILgcj3ASq_L" templates: - Normal: { TransformTemplate: transform_back } + Normal: + TransformTemplate: + - transform_back mdfc-front.psd: - id: '1_4sinFiSjIN8ayW8gpsxkwgGAa-FPc6H' - templates: { Normal: { MDFCTemplate: mdfc_front } } + id: "1_4sinFiSjIN8ayW8gpsxkwgGAa-FPc6H" + templates: + Normal: + MDFCTemplate: + - mdfc_front mdfc-back.psd: - id: '189N6G0uPEPAoGQya6ov8g9ZDG4A6lZUA' - templates: { Normal: { MDFCTemplate: mdfc_back } } + id: "189N6G0uPEPAoGQya6ov8g9ZDG4A6lZUA" + templates: + Normal: + MDFCTemplate: + - mdfc_back ixalan.psd: - id: '1e_VX3nJ5c6bZMObMLt5c1jJGiaaw5-4d' - requires: - templates: - - tf-front.psd - - tf-back.psd + id: "1e_VX3nJ5c6bZMObMLt5c1jJGiaaw5-4d" templates: Ixalan: IxalanTemplate: - - transform_back - - transform_front + - normal + - transform_back + - transform_front mutate.psd: - id: '1A5YNZA9Z9eE_jdDIPFJCqp_AxcEiAcdL' - templates: { Normal: { MutateTemplate: mutate } } + id: "1A5YNZA9Z9eE_jdDIPFJCqp_AxcEiAcdL" + templates: + Normal: + MutateTemplate: + - mutate normal-vector.psd: - id: '16Ll3t_u9VwnNRfbhpvV12CL1iwWbyDG0' - templates: { Normal: { AdventureVectorTemplate: adventure } } + id: "16Ll3t_u9VwnNRfbhpvV12CL1iwWbyDG0" + templates: + Normal: + AdventureVectorTemplate: + - adventure leveler.psd: - id: '1tLrYbTphadzxxyICtaRSqzRUfpekJckf' - templates: { Normal: { LevelerTemplate: leveler } } + id: "1tLrYbTphadzxxyICtaRSqzRUfpekJckf" + templates: + Normal: + LevelerTemplate: + - leveler vertical-mode.psd: - id: '1ytlBqIywu7gyTrgM9-hKj-apKe6v18Ek' + id: "1ytlBqIywu7gyTrgM9-hKj-apKe6v18Ek" desc: "A powerful Vector template that can render both Saga and Class cards in Normal and Universes Beyond treatment." templates: Universes Beyond: - UniversesBeyondSagaTemplate: saga - UniversesBeyondClassTemplate: class + UniversesBeyondSagaTemplate: + - saga + UniversesBeyondClassTemplate: + - class Normal: - SagaVectorTemplate: saga - ClassVectorTemplate: class + SagaVectorTemplate: + - saga + ClassVectorTemplate: + - class split.psd: - id: '1J8CFOqxxiopOMBbEmNjayNJvGsH6uyvn' - templates: { Normal: { SplitTemplate: split } } + id: "1J8CFOqxxiopOMBbEmNjayNJvGsH6uyvn" + templates: + Normal: + SplitTemplate: + - split battle.psd: - id: '1_Je_F91VgancyC6EH9JAKc35cwB9HoqJ' + id: "1_Je_F91VgancyC6EH9JAKc35cwB9HoqJ" templates: - Normal: { BattleTemplate: battle } - Universes Beyond: { UniversesBeyondBattleTemplate: battle } + Normal: + BattleTemplate: + - battle + Universes Beyond: + UniversesBeyondBattleTemplate: + - battle prototype.psd: - id: '1XwUYcjX18nRUFcWR6OjEyE_H6pHz1uOE' - templates: { Normal: { PrototypeTemplate: prototype } } + id: "1XwUYcjX18nRUFcWR6OjEyE_H6pHz1uOE" + templates: + Normal: + PrototypeTemplate: + - prototype pw.psd: - id: '1hlXvORM3z7bqURPyDG3VYDUeqq94ek81' - templates: { Normal: { PlaneswalkerTemplate: planeswalker } } + id: "1hlXvORM3z7bqURPyDG3VYDUeqq94ek81" + templates: + Normal: + PlaneswalkerTemplate: + - planeswalker pw-extended.psd: - id: '1MjIF48qTyAzax7LRdtGAvZLcWTZBIzCB' + id: "1MjIF48qTyAzax7LRdtGAvZLcWTZBIzCB" templates: - Borderless: { PlaneswalkerBorderlessTemplate: planeswalker } + Borderless: + PlaneswalkerBorderlessTemplate: + - planeswalker pw-tf-front.psd: - id: '1fwhWb4BHRRjs2KoGZhLMGtQ3ngmeeQ-o' + id: "1fwhWb4BHRRjs2KoGZhLMGtQ3ngmeeQ-o" templates: - Normal: { PlaneswalkerTFTemplate: pw_tf_front } + Normal: + PlaneswalkerTFTemplate: + - pw_tf_front pw-tf-front-extended.psd: - id: '1TtHwTkDhWl5jJi3b_OiMa-E4izhfcTK7' + id: "1TtHwTkDhWl5jJi3b_OiMa-E4izhfcTK7" templates: - Borderless: { PlaneswalkerTFBorderlessTemplate: pw_tf_front } + Borderless: + PlaneswalkerTFBorderlessTemplate: + - pw_tf_front pw-tf-back.psd: - id: '10_rHVPO7iIbmSlGAxgK0zMz7_lhJeaMf' + id: "10_rHVPO7iIbmSlGAxgK0zMz7_lhJeaMf" templates: - Normal: { PlaneswalkerTFTemplate: pw_tf_back } + Normal: + PlaneswalkerTFTemplate: + - pw_tf_back pw-tf-back-extended.psd: - id: '1H1chKvKFiFYDrnN226zshNo0wQNDIQIX' + id: "1H1chKvKFiFYDrnN226zshNo0wQNDIQIX" templates: - Borderless: { PlaneswalkerTFBorderlessTemplate: pw_tf_back } + Borderless: + PlaneswalkerTFBorderlessTemplate: + - pw_tf_back pw-mdfc-front.psd: - id: '1hQBlgYaGIWIgCf4hkZRjcvMN9VJCkbVe' + id: "1hQBlgYaGIWIgCf4hkZRjcvMN9VJCkbVe" templates: - Normal: { PlaneswalkerMDFCTemplate: pw_mdfc_front } + Normal: + PlaneswalkerMDFCTemplate: + - pw_mdfc_front pw-mdfc-front-extended.psd: - id: '1ddzLx-ookhyrjjJnOGp8pzW29FHVSbYQ' + id: "1ddzLx-ookhyrjjJnOGp8pzW29FHVSbYQ" templates: - Borderless: { PlaneswalkerMDFCBorderlessTemplate: pw_mdfc_front } + Borderless: + PlaneswalkerMDFCBorderlessTemplate: + - pw_mdfc_front pw-mdfc-back.psd: - id: '1J_giOHB_07SK0LPpxH4n9Me6CyJqslvj' + id: "1J_giOHB_07SK0LPpxH4n9Me6CyJqslvj" templates: - Normal: { PlaneswalkerMDFCTemplate: pw_mdfc_back } + Normal: + PlaneswalkerMDFCTemplate: + - pw_mdfc_back pw-mdfc-back-extended.psd: - id: '1BwX6vwoH-HNnAjz8408_PujankJceFox' + id: "1BwX6vwoH-HNnAjz8408_PujankJceFox" templates: - Borderless: { PlaneswalkerMDFCBorderlessTemplate: pw_mdfc_back } + Borderless: + PlaneswalkerMDFCBorderlessTemplate: + - pw_mdfc_back token.psd: - id: '1oWzk6MSv5wQMVJJE3VossKWs1BylBvKP' - templates: { Chilli Token: { TokenTemplate: normal } } + id: "1oWzk6MSv5wQMVJJE3VossKWs1BylBvKP" + templates: + Chilli Token: + TokenTemplate: + - normal planar.psd: - id: '1MGSgSkvUGe6M8-TEI8cMby_eU3i1uR_E' - templates: { Normal: { PlanarTemplate: planar } } + id: "1MGSgSkvUGe6M8-TEI8cMby_eU3i1uR_E" + templates: + Normal: + PlanarTemplate: + - planar diff --git a/src/data/tests/template_renders.toml b/src/data/tests/template_renders.toml index b71b609c..19937120 100644 --- a/src/data/tests/template_renders.toml +++ b/src/data/tests/template_renders.toml @@ -11,8 +11,10 @@ Mountain = "Basic Land" "Ghost Ark" = "Vehicle" "Graven Lore" = "Snow" Terminus = "Miracle" -"Elemental [TCMM] {37}" = "Dual Color Token" +"Elemental [TCMM] {37}" = "Dual Color No Text Token" "Tuktuk the Returned [TCM2] {15}" = "Legendary Token" +"Treasure [TSNC]" = "One Line Token" +"Rock [TCMR]" = "Multiline Token" [transform_front] "Docent of Perfection [EMN]" = "Moon Eldrazi DFC" @@ -28,6 +30,8 @@ Terminus = "Miracle" "Havengul Mystery [SLX]" = "Land DFC" "Dragon-Kami's Egg [NEO]" = "Fan DFC" "Optimus Prime, Autobot Leader [BOT]" = "Convert DFC" +"Azcanta, the Sunken Ruin" = "Ixalan Legendary Land" +"Ragnarok, Divine Deliverance [FIN]" = "Dual Color Meld DFC" [mdfc_front] "Plargg, Dean of Chaos" = "Legendary Creature" @@ -37,9 +41,6 @@ Terminus = "Miracle" "Journey to the Oracle" = "Noncreature" "Harnfel, Horn of Bounty" = "Artifact" -[ixalan] -"Azcanta, the Sunken Ruin" = "Legendary Land" - [mutate] Gemrazer = "Creature" @@ -95,12 +96,6 @@ Gemrazer = "Creature" [planar] "Pools of Becoming" = "Planar" -[token] -"Zombie [TMBS]" = "No Text" -"Treasure [TSNC]" = "One Line" -"Rock [TCMR]" = "Multiline" -"Human Cleric [TELD]" = "Multicolor" - [split] "Fire [UMA]" = "Two Colors" "Integrity [GRN]" = "Gold" diff --git a/src/enums/adobe.py b/src/enums/adobe.py index 10d63e38..3831e95f 100644 --- a/src/enums/adobe.py +++ b/src/enums/adobe.py @@ -2,14 +2,9 @@ * Enums for Photoshop Actions """ -# Standard Library Imports -from enum import Enum +from enum import Enum, StrEnum from typing import Literal -# Third Party Imports -from omnitils.enums import StrConstant - -# Local Imports from src import APP """ @@ -26,7 +21,7 @@ def value(self) -> int: return int(APP.instance.sID(self._value_)) -class Dimensions(StrConstant): +class Dimensions(StrEnum): """Layer dimension descriptors.""" Width = "width" diff --git a/src/enums/layers.py b/src/enums/layers.py index 1ff6ed7c..0e2b572f 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -1,15 +1,15 @@ """ * Enums: Layer Names """ -# Third Party Imports -from omnitils.enums import StrConstant + +from enum import StrEnum """ * Enums """ -class LAYERS (StrConstant): +class LAYERS (StrEnum): """Layer name definitions.""" # Default art layer diff --git a/src/enums/mtg.py b/src/enums/mtg.py index d637df17..2fd2bfa5 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -1,19 +1,17 @@ """ * Enums: MTG Related Data """ -# Standard Library Imports + import re from dataclasses import dataclass - -# Third Party Imports -from omnitils.enums import StrConstant +from enum import StrEnum """ * Card Layouts """ -class LayoutCategory(StrConstant): +class LayoutCategory(StrEnum): """Card layout category, broad naming used for displaying on GUI elements.""" Adventure = 'Adventure' Battle = 'Battle' @@ -34,8 +32,12 @@ class LayoutCategory(StrConstant): Token = 'Token' Transform = 'Transform' + @classmethod + def matches_layout_type(cls, category: LayoutCategory, layout_type: LayoutType) -> bool: + return layout_type in layout_map_category[category] + -class LayoutType(StrConstant): +class LayoutType(StrEnum): """Card layout type, fine-grained naming separated by front/back where applicable.""" Adventure = 'adventure' Battle = 'battle' @@ -60,7 +62,7 @@ class LayoutType(StrConstant): TransformFront = 'transform_front' -class LayoutScryfall(StrConstant): +class LayoutScryfall(StrEnum): """Card layout type, according to Scryfall data.""" Normal = 'normal' Split = 'split' @@ -94,36 +96,36 @@ class LayoutScryfall(StrConstant): Station = 'station' -"""Maps Layout categories to a list of equivalent Layout types.""" -layout_map_category: dict[LayoutCategory, list[LayoutType]] = { - LayoutCategory.Normal: [LayoutType.Normal], - LayoutCategory.MDFC: [LayoutType.MDFCFront, LayoutType.MDFCBack], - LayoutCategory.Transform: [LayoutType.TransformFront, LayoutType.TransformBack], - LayoutCategory.Planeswalker: [LayoutType.Planeswalker], - LayoutCategory.PlaneswalkerMDFC: [LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack], - LayoutCategory.PlaneswalkerTransform: [LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack], - LayoutCategory.Saga: [LayoutType.Saga], - LayoutCategory.Case: [LayoutType.Case], - LayoutCategory.Class: [LayoutType.Class], - LayoutCategory.Mutate: [LayoutType.Mutate], - LayoutCategory.Prototype: [LayoutType.Prototype], - LayoutCategory.Adventure: [LayoutType.Adventure], - LayoutCategory.Leveler: [LayoutType.Leveler], - LayoutCategory.Split: [LayoutType.Split], - LayoutCategory.Battle: [LayoutType.Battle], - LayoutCategory.Planar: [LayoutType.Planar], - LayoutCategory.Station: [LayoutType.Station], +"""Maps Layout categories to tuples of equivalent Layout types.""" +layout_map_category: dict[LayoutCategory, tuple[LayoutType, ...]] = { + LayoutCategory.Normal: (LayoutType.Normal,), + LayoutCategory.MDFC: (LayoutType.MDFCFront, LayoutType.MDFCBack), + LayoutCategory.Transform: (LayoutType.TransformFront, LayoutType.TransformBack), + LayoutCategory.Planeswalker: (LayoutType.Planeswalker,), + LayoutCategory.PlaneswalkerMDFC: (LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack), + LayoutCategory.PlaneswalkerTransform: (LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack), + LayoutCategory.Saga: (LayoutType.Saga,), + LayoutCategory.Case: (LayoutType.Case,), + LayoutCategory.Class: (LayoutType.Class,), + LayoutCategory.Mutate: (LayoutType.Mutate,), + LayoutCategory.Prototype: (LayoutType.Prototype,), + LayoutCategory.Adventure: (LayoutType.Adventure,), + LayoutCategory.Leveler: (LayoutType.Leveler,), + LayoutCategory.Split: (LayoutType.Split,), + LayoutCategory.Battle: (LayoutType.Battle,), + LayoutCategory.Planar: (LayoutType.Planar,), + LayoutCategory.Station: (LayoutType.Station,), } """Maps Layout types to their equivalent Layout category.""" -layout_map_types: dict[str,LayoutCategory] = { +layout_map_types: dict[LayoutType, LayoutCategory] = { raw: named for named, raw in ( (k, n) for k, names in layout_map_category.items() for n in names ) } """Maps Layout types to a display formatted Layout category (with Back or Front).""" -layout_map_types_display = { +layout_map_types_display: dict[LayoutType, str] = { raw: ( f'{named} Front' if 'front' in raw else ( f'{named} Back' if 'back' in raw else named) @@ -131,7 +133,7 @@ class LayoutScryfall(StrConstant): } """Maps multimodal display formatted layout types to a singular layout type.""" -layout_map_display_condition = { +layout_map_display_condition: dict[str,LayoutType] = { f'{LayoutCategory.Transform} Front': LayoutType.TransformFront, f'{LayoutCategory.Transform} Back': LayoutType.TransformBack, f'{LayoutCategory.MDFC} Front': LayoutType.MDFCFront, @@ -143,23 +145,23 @@ class LayoutScryfall(StrConstant): } """Maps display formatted layout types to a combined group of two layout types.""" -layout_map_display_condition_dual = { - LayoutCategory.Transform: [ +layout_map_display_condition_dual: dict[LayoutCategory, tuple[LayoutType, LayoutType]] = { + LayoutCategory.Transform: ( LayoutType.TransformFront, LayoutType.TransformBack - ], - LayoutCategory.MDFC: [ + ), + LayoutCategory.MDFC: ( LayoutType.MDFCFront, LayoutType.MDFCBack - ], - LayoutCategory.PlaneswalkerTransform: [ + ), + LayoutCategory.PlaneswalkerTransform: ( LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack - ], - LayoutCategory.PlaneswalkerMDFC: [ + ), + LayoutCategory.PlaneswalkerMDFC: ( LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack - ], + ), } @@ -168,7 +170,7 @@ class LayoutScryfall(StrConstant): """ -class CardTypes(StrConstant): +class CardTypes(StrEnum): """Represents main card types.""" Artifact = 'Artifact' Battle = 'Battle' @@ -186,7 +188,7 @@ class CardTypes(StrConstant): Vanguard = 'Vanguard' -class CardTypesSuper(StrConstant): +class CardTypesSuper(StrEnum): """Represents card supertypes.""" Basic = 'Basic' Elite = 'Elite' @@ -278,7 +280,7 @@ class CardTypesSuper(StrConstant): """ -class Rarity(StrConstant): +class Rarity(StrEnum): """Card rarities.""" C = "common" U = "uncommon" @@ -289,7 +291,7 @@ class Rarity(StrConstant): T = "timeshifted" -class TransformIcons(StrConstant): +class TransformIcons(StrEnum): """Transform icon names.""" MOONELDRAZI = "mooneldrazidfc" COMPASSLAND = "compasslanddfc" @@ -325,7 +327,7 @@ class TransformIcons(StrConstant): """ -class CardFonts(StrConstant): +class CardFonts(StrEnum): """Fonts used for card text.""" RULES = "PlantinMTPro-Regular" RULES_BOLD = "PlantinMTPro-Bold" @@ -341,7 +343,7 @@ class CardFonts(StrConstant): SYMBOL = "Keyrune" -class MagicIcons(StrConstant): +class MagicIcons(StrEnum): # Proxyglyph font PAINTBRUSH_MODERN = "a" PAINTBRUSH_CLASSIC = "ýþ" diff --git a/src/enums/settings.py b/src/enums/settings.py index 536974dd..b37d5a1f 100644 --- a/src/enums/settings.py +++ b/src/enums/settings.py @@ -1,11 +1,14 @@ """ * Enums: Settings """ -# Standard Library Imports -from functools import cached_property -# Third Party Imports -from omnitils.enums import StrConstant +from enum import StrEnum, nonmember +from typing import Protocol, runtime_checkable + + +@runtime_checkable +class HasDefault(Protocol): + Default: nonmember[str] """ @@ -13,17 +16,15 @@ """ -class OutputFileType (StrConstant): +class OutputFileType(StrEnum): JPG = "jpg" PNG = "png" PSD = "psd" - @cached_property - def Default(self) -> str: - return self.JPG + Default = nonmember(JPG) -class ScryfallSorting (StrConstant): +class ScryfallSorting(StrEnum): Released = "released" Set = "set" Rarity = "rarity" @@ -32,18 +33,14 @@ class ScryfallSorting (StrConstant): EDHRec = "edhrec" Artist = "artist" - @cached_property - def Default(self) -> str: - return self.Released + Default = nonmember(Released) -class ScryfallUnique (StrConstant): +class ScryfallUnique(StrEnum): Prints = "prints" Arts = "arts" - @cached_property - def Default(self) -> str: - return self.Arts + Default = nonmember(Arts) """ @@ -51,47 +48,39 @@ def Default(self) -> str: """ -class CollectorMode (StrConstant): +class CollectorMode(StrEnum): Normal = "default" Modern = "modern" Minimal = "minimal" ArtistOnly = "artist" - @cached_property - def Default(self) -> str: - return self.Normal + Default = nonmember(Normal) -class BorderColor (StrConstant): +class BorderColor(StrEnum): Black = "black" White = "white" Silver = "silver" Gold = "gold" - @cached_property - def Default(self) -> str: - return self.Black + Default = nonmember(Black) -class CollectorPromo (StrConstant): +class CollectorPromo(StrEnum): Automatic = "automatic" Always = "always" Never = "never" - @cached_property - def Default(self) -> str: - return self.Automatic + Default = nonmember(Automatic) -class WatermarkMode (StrConstant): +class WatermarkMode(StrEnum): Disabled = "Disabled" Automatic = "Automatic" Fallback = "Fallback" Forced = "Forced" - @cached_property - def Default(self) -> str: - return self.Disabled + Default = nonmember(Disabled) """ @@ -99,7 +88,7 @@ def Default(self) -> str: """ -class BorderlessColorMode (StrConstant): +class BorderlessColorMode(StrEnum): All = "All" Twins_And_PT = "Twins and PT" Textbox = "Textbox" @@ -107,12 +96,10 @@ class BorderlessColorMode (StrConstant): PT = "PT Box" Disabled = "None" - @cached_property - def Default(self) -> str: - return self.Twins_And_PT + Default = nonmember(Twins_And_PT) -class BorderlessTextbox (StrConstant): +class BorderlessTextbox(StrEnum): Automatic = "Automatic" Textless = "Textless" Normal = "Normal" @@ -120,9 +107,7 @@ class BorderlessTextbox (StrConstant): Short = "Short" Tall = "Tall" - @cached_property - def Default(self) -> str: - return self.Automatic + Default = nonmember(Automatic) """ @@ -130,11 +115,9 @@ def Default(self) -> str: """ -class ModernClassicCrown (StrConstant): +class ModernClassicCrown(StrEnum): Pinlines = "Pinlines" TexturePinlines = "Texture Pinlines" TextureBackground = "Texture Background" - @cached_property - def Default(self) -> str: - return self.Pinlines + Default = nonmember(Pinlines) diff --git a/src/gui/__init__.py b/src/gui/__init__.py deleted file mode 100644 index 7815184e..00000000 --- a/src/gui/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -* GUI Module -""" -# Local Imports -from ._state import GlobalAccess, GUI - -# Export objects -__all__ = ['GlobalAccess', 'GUI'] diff --git a/src/gui/_state.py b/src/gui/_state.py deleted file mode 100644 index 2980ee2a..00000000 --- a/src/gui/_state.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -* Managing Global State (GUI) -""" -# Standard Library Imports -from dataclasses import dataclass -from functools import cache, cached_property -import os -from typing import Any - -# Third Party Imports -from kivy.app import App -from kivy.config import Config -from kivy.factory import Factory -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.layout import Layout -from kivy.uix.togglebutton import ToggleButton -from kivy.uix.button import Button -from kivy.metrics import dp -from kivy.core.window import Window - -# Local Imports -from src._config import AppConfig -from src._state import PATH, AppConstants, AppEnvironment -from src.enums.mtg import layout_map_category -from src.gui.utils import HoverBehavior -from src.utils.adobe import PhotoshopHandler - -""" -* Enums -""" - - -@dataclass -class KivyStyles: - """Define the `kv` stylesheet files to loaded at initialization.""" - App = os.path.join(PATH.SRC_DATA_KV, 'app.kv') - Main = os.path.join(PATH.SRC_DATA_KV, 'main.kv') - Console = os.path.join(PATH.SRC_DATA_KV, 'console.kv') - Creator = os.path.join(PATH.SRC_DATA_KV, 'creator.kv') - Test = os.path.join(PATH.SRC_DATA_KV, 'test.kv') - Tools = os.path.join(PATH.SRC_DATA_KV, 'tools.kv') - Updater = os.path.join(PATH.SRC_DATA_KV, 'updater.kv') - - -""" -* Cached Utils -""" - - -@cache -def get_root_app(): - """Cache and return the running application object.""" - return App.get_running_app() - - -class GlobalAccess(Layout): - """Utility class giving Kivy GUI elements access to the main app and its - global objects.""" - - def __init__(self, *args, **kwargs): - self.bind(on_kv_post=self.on_load) - super().__init__(**kwargs) - - """ - * Layout Methods - """ - - def on_load(self, *args) -> None: - """Fired when object is loaded into the GUI.""" - self.main.toggle_buttons.extend(self.toggle_buttons) - - """ - * Utility Properties - """ - - @cached_property - def toggle_buttons(self) -> list[Button]: - """Buttons that should be toggled during a locked operation.""" - return [] - - """ - * Global Object Properties - """ - - @cached_property - def main(self) -> Any: - """ProxyshopGUIApp: Get the running application.""" - return get_root_app() - - @property - def app(self) -> PhotoshopHandler: - """PhotoshopHandler: Global Photoshop application object.""" - return self.main.app - - @property - def cfg(self) -> AppConfig: - """AppConfig: Global settings object.""" - return self.main.cfg - - @property - def con(self) -> AppConstants: - """AppConstants: Global constants object.""" - return self.main.con - - @property - def env(self) -> AppEnvironment: - """AppEnvironment: Global environment object.""" - return self.main.env - - @property - def console(self) -> Any: - """GUIConsole: Console output object.""" - return self.main.console - - -""" -* GUI Loader Funcs -""" - - -def load_kv_config(): - """Loads app-wide Kivy configuration.""" - - # Kivy configuration - Config.set('input', 'mouse', 'mouse,multitouch_on_demand') - Config.remove_option('input', 'wm_touch') - Config.remove_option('input', 'wm_pen') - Config.set('kivy', 'log_level', 'error') - Config.write() - - # Window configuration - Window.minimum_width = dp(650) - Window.minimum_height = dp(540) - Window.size = (dp(840), dp(720)) - - -def register_kv_classes(): - """Loads app-wide GUI classes into Kivy Factory.""" - Factory.register('HoverBehavior', HoverBehavior) - - -""" -* Tracked Elements -""" - - -class GUIResources: - def __init__(self): - self.template_row: dict[str, dict[str, BoxLayout]] = {k: {} for k in layout_map_category} - self.template_btn: dict[str, dict[str, ToggleButton]] = {k: {} for k in layout_map_category} - self.template_btn_cfg: dict[str, dict[str, Button]] = {k: {} for k in layout_map_category} - self.template_list: dict[str, list[BoxLayout]] = {k: [] for k in layout_map_category} - - -GUI = GUIResources() diff --git a/src/gui/app.py b/src/gui/app.py deleted file mode 100644 index 69013c25..00000000 --- a/src/gui/app.py +++ /dev/null @@ -1,1145 +0,0 @@ -""" -* Proxyshop GUI Launcher -""" -# Standard Library Imports -from __future__ import annotations -import os -import json -from io import BytesIO -from pathlib import Path -from time import perf_counter -from concurrent.futures import ThreadPoolExecutor -from contextlib import suppress -from typing import Any, Concatenate, ParamSpec, TypeVar -import win32clipboard as clipboard -from datetime import datetime as dt -from functools import cached_property -from threading import Event, Thread, Lock -from multiprocessing import cpu_count -from collections.abc import Callable - -# Third-party Imports -import requests -from PIL import Image as PImage -from photoshop.api._document import Document -from photoshop.api import SaveOptions, PurgeTarget -from packaging.version import parse, InvalidVersion - -# Kivy Imports -from kivy.lang import Builder -from kivy.app import App -from kivy.metrics import dp -from kivy.uix.button import Button -from kivy.core.window import Window -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.togglebutton import ToggleButton -from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem -import asynckivy as ak -from omnitils.files import load_data_file - -# Local Imports -from src._state import AppConstants, AppEnvironment, PATH -from src._config import AppConfig -from src.console import get_bullet_points, msg_bold, msg_error, msg_info, msg_success, msg_warn -from src.enums.mtg import layout_map_types -from src.gui.console import GUIConsole, ConsoleOutput -from src.gui.popup.settings import SettingsPopup -from src._loader import ( - AppPlugin, - TemplateDetails, - TemplateSelectedMap, - TemplateCategoryMap, - get_template_map_selected, - AppTemplate) -from src.gui._state import GUI, GlobalAccess -from src.gui.popup.updater import UpdatePopup -from src.gui.tabs.creator import CreatorPanel -from src.gui.tabs.main import TemplateRow, MainPanel -from src.gui.tabs.tools import ToolsPanel -from src.gui.test import TestApp -from src.layouts import ( - SplitLayout, - layout_map, - assign_layout, - join_dual_card_layouts, - NormalLayout) -from src.templates import BaseTemplate -from src.utils.adobe import get_photoshop_error_message, PhotoshopHandler, PS_EXCEPTIONS -from src.utils.github import get_github_releases -from src.utils.hexapi import update_hexproof_cache, get_api_key -from src.utils.fonts import check_app_fonts -from src.utils.threading import ThreadInitializedInstance - -T = TypeVar("T") -P = ParamSpec("P") - - -""" -* App Tab Containers -""" - - -class AppContainer(BoxLayout, GlobalAccess): - """Container for overall app.""" - - @cached_property - def app_tabs(self) -> AppTabs: - return AppTabs() - - def on_load(self, *args) -> None: - """Add tab panel.""" - self.add_widget(self.app_tabs) - - -class AppTabs(TabbedPanel, GlobalAccess): - """Container for both render and creator tabs.""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._tab_layout.padding = ( - '0dp', '0dp', '0dp', '0dp') - - @cached_property - def main_tab(self) -> MainTab: - return MainTab() - - def on_load(self, *args) -> None: - """Add tabs.""" - self.add_widget(self.main_tab) - self.add_widget(CreatorTab()) - self.add_widget(ToolsTab()) - - -class MainTab(TabbedPanelItem, GlobalAccess): - """Main rendering tab.""" - - @cached_property - def main_panel(self) -> MainPanel: - return MainPanel() - - def on_load(self, *args) -> None: - """Add content.""" - self.content = self.main_panel - - -class CreatorTab(TabbedPanelItem, GlobalAccess): - """Custom card creator tab.""" - - def on_load(self, *args) -> None: - """Add content.""" - self.content = CreatorPanel() - - -class ToolsTab(TabbedPanelItem, GlobalAccess): - """Utility tools tab.""" - - def on_load(self, *args) -> None: - """Add content.""" - self.content = ToolsPanel() - - -""" -* Main GUI Application -""" - - -class ProxyshopGUIApp(App): - """Proxyshop's main Kivy App class that initiates render procedures and manages user settings.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "app.kv")) - - def __init__( - self, - app: ThreadInitializedInstance[PhotoshopHandler], - con: AppConstants, - cfg: AppConfig, - env: AppEnvironment, - console: GUIConsole, - plugins: dict[str, AppPlugin], - templates: list[AppTemplate], - template_map: dict[str, TemplateCategoryMap], - templates_default: TemplateSelectedMap, - **kwargs - ): - # Call super - super().__init__(**kwargs) - - # Data - self._app = app - self._cfg = cfg - self._con = con - self._env = env - self._console = console - self._plugins = plugins - self._templates = templates - self._template_map = template_map - self._templates_default = templates_default - self._templates_selected: TemplateSelectedMap = {} - self._current_render: BaseTemplate | None = None - self._result = False - - """ - * Kivy Window Properties - """ - - @property - def title(self) -> str: - """str: App name displayed at the top of the application window.""" - return f"Proxyshop v{self.env.VERSION}" - - @property - def icon(self) -> str: - """str: Path to icon displayed in the task bar and the corner of the window.""" - return str(PATH.SRC_IMG / 'proxyshop.png') - - @property - def cont_padding(self) -> float: - """float: Padding for the main app container.""" - return dp(10) - - """ - * Global Objects - """ - - @property - def app(self) -> PhotoshopHandler: - """PhotoshopHandler: Global Photoshop application object.""" - return self._app.instance - - @property - def cfg(self) -> AppConfig: - """AppConfig: Global settings object.""" - return self._cfg - - @property - def con(self) -> AppConstants: - """AppConstants: Global constants object.""" - return self._con - - @property - def env(self) -> AppEnvironment: - """AppEnvironment: Global environment object.""" - return self._env - - @property - def console(self) -> GUIConsole: - """GUIConsole: Console output object.""" - return self._console - - """ - * Rendering Properties - """ - - @cached_property - def _render_lock(self) -> Lock: - """Lock: Thread locking mechanism for render operations.""" - return Lock() - - @cached_property - def _dropped_files(self) -> list[Path]: - """list[Path]: Tracks files dragged and dropped onto the app window.""" - return [] - - @cached_property - def plugins(self) -> dict[str, AppPlugin]: - """dict[str, AppPlugin]: Tracks all plugins loaded by the app currently.""" - return self._plugins - - @cached_property - def templates(self) -> list[AppTemplate]: - """list[AppTemplate]: List of all templates available to the app.""" - return self._templates - - @cached_property - def template_map(self) -> dict[str, TemplateCategoryMap]: - """dict[str, TemplateCategoryMap]: Dictionary of category mapped templates available to the app.""" - return self._template_map - - @cached_property - def templates_selected(self) -> TemplateSelectedMap: - """TemplateSelectedMap: Tracks the templates currently selected for each type by the user.""" - return self._templates_selected - - @cached_property - def templates_default(self) -> TemplateSelectedMap: - """TemplateSelectedMap: Tracks the default template selections for every template type.""" - return self._templates_default - - @cached_property - def current_render(self) -> BaseTemplate | None: - """BaseTemplate: Tracks the current template class being used for rendering.""" - return self._current_render - - @property - def docref(self) -> Document | None: - """Optional[Document]: Tracks the currently open Photoshop document.""" - if self.current_render and hasattr(self.current_render, 'docref'): - return self.current_render.docref or None - return None - - @property - def thread(self) -> Event | None: - """Optional[Event]: Tracks the current render threading Event.""" - if self.current_render and hasattr(self.current_render, 'event'): - return self.current_render.event or None - return None - - @property - def timer(self) -> float: - """float: Returns the current system time as a float to use as a timer comparison.""" - return perf_counter() - - @property - def thread_cancelled(self) -> bool: - """bool: Whether the current render thread has been cancelled.""" - thr = self.thread - return bool(not isinstance(thr, Event) or thr.is_set()) - - """ - * UI Properties - """ - - @cached_property - def render_target_button(self) -> Button | None: - if isinstance(self._app_layout, AppContainer): - return self._app_layout.app_tabs.main_tab.main_panel.render_target_button - - @cached_property - def render_all_button(self) -> Button | None: - if isinstance(self._app_layout, AppContainer): - return self._app_layout.app_tabs.main_tab.main_panel.render_all_button - - @cached_property - def app_settings_button(self) -> Button | None: - if isinstance(self._app_layout, AppContainer): - return self._app_layout.app_tabs.main_tab.main_panel.app_settings_button - - @cached_property - def toggle_buttons(self) -> list[Button]: - """list[Button]: UI buttons to toggle when disable_buttons or enable_buttons is called.""" - return [self.console.update_btn] - - """ - * Wrappers - """ - - @staticmethod - def render_process_wrapper( - func: Callable[Concatenate[ProxyshopGUIApp, P], T], - ) -> Callable[Concatenate[ProxyshopGUIApp, P], T | None]: - """Decorator to handle state maintenance before and after an initiated render process. - - Args: - func: Function being wrapped. - - Returns: - The result of the wrapped function. - """ - - def wrapper( - self: ProxyshopGUIApp, *args: P.args, **kwargs: P.kwargs - ) -> T | None: - # Never render on two threads at once - if self._render_lock.locked(): - return - - # Lock the render thread - with self._render_lock: - # Disable buttons / clear console on enter - self.reset(disable_buttons=True, clear_console=True) - - # Ensure Photoshop is responsive - while check := self.app.refresh_app(): - if not self.console.await_choice( - thr=Event(), - msg=get_photoshop_error_message(check), - end="Hit Continue to try again, or Cancel to end the operation.\n", - ): - # Cancel this operation - return - - # Call the function - result = func(self, *args, **kwargs) - - # Enable buttons / close document on exit - self.reset(enable_buttons=True, close_document=True) - return result - - return wrapper - - """ - * Art Loading Utilities - """ - - def get_art_files(self, folder: Path = PATH.ART) -> list[Path]: - """Grab all supported image files within a given directory. - - Args: - folder: Path within the working directory containing images. - - Returns: - List of art file paths. - """ - # Folder, file list, supported extensions - all_files = os.listdir(folder) - ext = (".png", ".jpg", ".tif", ".jpeg", ".jpf") - - # Select all images in folder not prepended with ! - files = [Path(folder, f) for f in all_files if f.endswith(ext) and not f.startswith('!')] - - # Check for webp files - files_webp = [Path(folder, f) for f in all_files if f.endswith('.webp') and not f.startswith('!')] - - # Check if Photoshop version supports webp - if files_webp and not self.app.supports_webp: - self.console.update(msg_warn('Skipped WEBP image, WEBP requires Photoshop ^23.2.0')) - elif files_webp: - files.extend(files_webp) - return sorted(files) - - def select_art(self) -> list[Path]: - """Open file select dialog in Photoshop, return the paths of any selected files. - - Returns: - A list of Path objects. - """ - while True: - try: - # Open a file select dialog in Photoshop - if files := self.app.openDialog(): - return [Path(f) for f in files] - return [] - except PS_EXCEPTIONS as e: - # Photoshop is busy or unresponsive, try again? - if not self.console.await_choice( - Event(), get_photoshop_error_message(e), - end="Hit Continue to try again, or Cancel to end the operation.\n" - ): - # Cancel the operation - break - # Refresh Photoshop, try again - self.app.refresh_app() - # No files selected - return [] - - """ - * Photoshop Utilities - """ - - def close_document(self) -> None: - """Close Photoshop document if current document reference exists..""" - if isinstance(self.docref, Document): - try: - self.docref.close(SaveOptions.DoNotSaveChanges) - self.app.purge(PurgeTarget.AllCaches) - except Exception as e: - # Document wasn't available - print("Couldn't close corresponding document!") - self.console.log_exception(e) - self.current_render = None - - """ - * Render Methods - """ - - @render_process_wrapper - def render_all(self, target: bool = False, files: list[Path] | None = None) -> None: - """Render cards using all images located in the art folder. - - Args: - target: Whether to do a targeted render operation. - files: A lit of files to render instead of target or 'art' folder, if provided. - """ - # Get our templates - temps = get_template_map_selected(self.templates_selected, self.templates_default) - - # Get our art files - if not files: - files = self.select_art() if target else self.get_art_files() - - # No files provided - if not files and target: - return self.console.update( - "No art images found!" if target else "No art images selected!") - - # Run through each file, assigning layout - with ThreadPoolExecutor(max_workers=cpu_count()) as pool: - cards = pool.map(assign_layout, files) - - # Join dual card layouts - cards = join_dual_card_layouts(list(cards)) - - # Remove failed strings - layouts: dict[str, dict[str, list[NormalLayout]]] = {} - failed: list[str] = [] - for c in cards: - # Add failed card - if isinstance(c, str): - failed.append(c) - continue - - temp = temps.get(c.card_class, None) - - if not temp: - msg_error( - msg=c.display_name, - reason=f"No suitable template found for type '{c.card_class}'", - ) - continue - - # Assign card as failure if template isn't installed - if not temp["object"].is_installed: - msg_error( - msg=c.display_name, - reason=f"Template '{temp['name']}' with type " - f"'{c.card_class}' is not installed!", - ) - continue - - # Map card to its template path and layout type - layouts.setdefault( - str(temp["object"].path_psd), {} - ).setdefault(c.card_class, []).append(c) - - # Did any cards fail to find? - if failed: - - # If all failed alert the user and cancel - failure_list = '\n'.join(failed) - if not layouts: - return self.console.update( - f"\n{msg_bold(msg_error('Failed to render all cards!'))}" - f"\n{failure_list}") - - # Let the user choose to continue if only some failed - if not self.console.error( - msg=f"\n{msg_error('Unable to render these cards:')}" - f"{failure_list}" - ): - return - - # Render in batches separated by PSD file - self.console.update() - times: list[float] = [] - for (i), (_path, class_map) in enumerate(layouts.items()): - for layout, cards in class_map.items(): - if temp := temps[layout]: - # Initialize the template's python class module - loaded_class = temp['object'].get_template_class( - temp['class_name']) - if not loaded_class: - - # Failed to load module or python class - self.console.update(msg_error( - "Unable to load Python class: " - f"{msg_bold(temp['class_name'])}")) - - # If more templates in the queue, ask to continue - if (i + 1) < len(layouts): - - # Get a list of cards using this template - failed_cards = get_bullet_points( - text=[str(c.display_name) for c in cards], - char='-') - if self.console.error( - msg=f"{msg_error('The following cards have been cancelled:')}" - f"{failed_cards}" - ): - # Continue to next template - self.console.update() - continue - - # Cancel render process - return - - # Load constants and config for this template - self.cfg.load(temp['config']) - self.con.reload() - - # Render each card with this PSD and class - for c in cards: - result = self.start_render(c, temp, loaded_class) - if self.thread_cancelled: - return - if result is not None: - times.append(result) - - # Render group complete - self.close_document() - - # Report the average render time - self.console.update(msg_success('Renders Completed!')) - if times: - avg = round(sum(times) / len(times), 1) - self.console.update(f'Average time: {avg} seconds') - - @render_process_wrapper - def render_custom(self, template: TemplateDetails, scryfall: dict[str,Any]) -> None: - """Set up custom render job, then execute. - - Args: - template: Dict of template details. - scryfall: Dict of scryfall data. - """ - # Open file in PS - if not (files := self.select_art()): - return - art_file = files.pop() - - # Instantiate layout object - try: - c = layout_map[scryfall['layout']]( - scryfall=scryfall, - file={ - 'file': art_file, - 'name': scryfall.get('name', ''), - 'artist': scryfall.get('artist', ''), - 'set': scryfall.get('set', ''), - 'number': "", - 'creator': "" - }) - except Exception as e: - self.console.update( - msg='Unable to create a layout object, make sure you input valid information.\n' - 'Custom card failed!', - exception=e) - return - - # Ensure template is installed - if not template['object'].is_installed: - # Template is not installed - self.console.update( - f"Template '{template['name']}' for card type '{c.card_class}' is not installed!\n" - 'Custom card failed!') - return - - # Initialize a python class for the template - loaded_class = template['object'].get_template_class(template['class_name']) - if not loaded_class: - # Failed to load module or python class - self.console.update( - f"Unable to load Python class: {msg_bold(template['class_name'])}\n" - 'Custom card failed!') - return - - # Start render - self.console.update() - self.start_render(c, template, loaded_class) - - @render_process_wrapper - def test_all(self, deep: bool = False) -> None: - """Test all templates in series. - - Args: - deep: Tests every card case for each template if enabled. - """ - # Load data file and loop through test case sections - for card_type, cards in load_data_file( - (PATH.SRC_DATA_TESTS / 'template_renders').with_suffix('.toml') - ).items(): - - # If not a deep test, only use first entry - cards = {k: v for k, v in [next(iter(cards.items()))]} if not deep else cards - self.console.update(msg_success(f'\n— {card_type.upper()}')) - - if card_type in layout_map_types: - # Loop through templates to test - for template in self.template_map[layout_map_types[card_type]]['map'][card_type].values(): - self.console.update(f"{template['class_name']} ... ", end="") - - # Is this template installed? - if not template['object'].is_installed: - self.console.update(msg_warn('SKIPPED (Template not installed)')) - continue - - # Can this template's class be loaded? - loaded_class = template['object'].get_template_class(template['class_name']) - if not loaded_class: - self.console.update(msg_error('SKIPPED (Template class failed to load)')) - continue - - # Load constants and config for this template - self.cfg.load(config=template['config']) - self.con.reload() - - # Loop through cards to test - failures: list[tuple[str, str, str]] = [] - times: list[float] = [] - for card_name, card_case in cards.items(): - - # Attempt to assign a layout - layout = assign_layout(Path(card_name)) - if isinstance(layout, str): - failures.append((card_name, card_case, 'Failed to assign layout')) - continue - - # Grab the template class and start the render thread - test_image_path = PATH.SRC_IMG / 'test.jpg' - layout.art_file = test_image_path - if isinstance(layout, SplitLayout): - layout.art_files = [test_image_path, test_image_path] - result = self.start_render(layout, template, loaded_class) - if result is None: - failures.append((card_name, card_case, 'Failed to render')) - else: - times.append(result) - - # Was thread cancelled? - if self.thread_cancelled: - return - - # Create a summary message - if failures: - fail_log = get_bullet_points([ - f"{name} ({case}) — {reason}" - for name, case, reason in failures]) - summary = msg_error(f'FAILED{fail_log}') - else: - avg = round(sum(times) / len(times), 1) - summary = msg_success(f'{avg}s Avg.') - - # Log summary and continue to next template - self.console.update(summary) - self.close_document() - - @render_process_wrapper - def test_target(self, card_type: str, template: TemplateDetails) -> None: - """Tests a specific template, always tests every case. - - Args: - card_type: Type of card, corresponds to template type. - template: Specific template to test. - """ - # Load test case cards - cases = load_data_file((PATH.SRC_DATA_TESTS / 'template_renders').with_suffix('.toml')) - title = f"\n— {template['class_name']}" - self.console.update(msg_success(title)) - - # Is this template installed? - if not template['object'].is_installed: - self.console.update(msg_warn("SKIPPED (Template not installed)")) - return - - # Can this template's class be loaded? - loaded_class = template['object'].get_template_class(template['class_name']) - if not loaded_class: - self.console.update(msg_error('SKIPPED (Template class failed to load)')) - return - - # Render each test case - times: list[float] = [] - for card_name in cases[card_type].keys(): - self.console.update(f"{card_name} ... ", end="") - - # Attempt to assign a layout - layout = assign_layout(Path(card_name)) - if isinstance(layout, str): - self.console.update(msg_error('Failed to assign layout')) - continue - - # Start the render - test_image_path = PATH.SRC_IMG / 'test.jpg' - layout.art_file = test_image_path - if isinstance(layout, SplitLayout): - layout.art_files = [test_image_path, test_image_path] - result = self.start_render( - card=layout, - template=template, - loaded_class=loaded_class, - reload_config=True, - reload_constants=True) - - # Was thread cancelled? - if self.thread_cancelled: - return - if result is not None: - times.append(result) - - # Was render successful? - self.console.update( - msg_error('Failed to render') - if result is None else - msg_success(f'{result}s')) - - # Report the average render time - self.console.update(msg_success('\nTests Completed!')) - if times: - avg = round(sum(times) / len(times), 1) - self.console.update(f'Average time: {avg} seconds') - - def start_render( - self, card: NormalLayout, - template: TemplateDetails, - loaded_class: type[BaseTemplate], - reload_config: bool = False, - reload_constants: bool = False - ) -> float | None: - """Execute a render job using a given card layout, template, and template class. - - Args: - card: Layout object representing an MTG card with validated data. - template: Template details containing relevant data about the template. - loaded_class: Python class loaded from the template's module which executes the render operation. - reload_config: Whether to reload the config object for this render, defaults to False. - reload_constants: Whether to reload the constants object for this render, defaults to False. - - Returns: - True if render queue should continue, False if the remaining renders have been cancelled. - """ - # Notify the user - if not self.env.TEST_MODE: - self.console.update(msg_success(f"---- {card.display_name} ({card.artist}) [{card.set}] {card.collector_number_raw} ----")) - - # Reload config and/or constants - if reload_config: - self.cfg.load(config=template['config']) - if reload_constants: - self.con.reload() - - # Catch any unexpected render exceptions - try: - - # Set the PSD location of the template - card.template_file = template['object'].path_psd - - # Create the template class object - self.current_render = loaded_class(card) - - # Run a cancellation await in a separate thread using executor - with ThreadPoolExecutor() as executor: - executor.submit(self.console.start_await_cancel, self.current_render.event) - - # Render the card - start_time = self.timer - result = self.current_render.execute() - timed = round(self.timer - start_time, 1) - - # Return execution time if successful - if self.thread and not self.thread.is_set() and result: - if not self.env.TEST_MODE: - self.console.update(f"[i]Time completed: {timed} seconds[/i]\n") - return timed - - # General error outside Template render process - except Exception as error: - - # Reset document - if self.docref and self.current_render: - self.current_render.reset() - - # Log the error - self.console.log_error( - self.thread or Event(), - card=card.name, - template=template['name'], - msg=msg_error( - 'Encountered a general error!\n' - 'Check [b]/logs/error.txt[/b] for details.'), - e=error) - - # Card failed or thread cancelled - return - - """ - * UI Utilities - """ - - def select_template(self, btn: ToggleButton) -> None: - """Add selected template to the template dict. - - Args: - btn: Button that was pressed and represents a given template. - """ - # Set the preview image - btn.parent.set_preview_image() - - # Select the template - if btn.state == "down": - obj: TemplateRow = btn.parent - btn.disabled = True - - # Set the preview image - obj.set_preview_image() - - # Select the template for its respective types - for t in obj.types: - if _template := obj.template_map.get(t): - self.templates_selected[t] = _template - - # Enable all other buttons in this template list - for name, button in GUI.template_btn[btn.parent.category].items(): - if name != btn.text: - button.disabled = False - button.state = "normal" - - def reset( - self, - reload_config: bool = True, - reload_constants: bool = True, - close_document: bool = False, - enable_buttons: bool = False, - disable_buttons: bool = False, - clear_console: bool = False - ) -> None: - """Reset app config state to default. - - Args: - reload_config: Reload the configuration object using system and base settings. - reload_constants: Reload the global constants object. - close_document: Close the Photoshop document if still open. - enable_buttons: Enable UI buttons. - disable_buttons: Disable UI buttons. - clear_console: Clear the console of all output. - """ - # Reset current render thread - if reload_config: - self.cfg.load() - if reload_constants: - self.con.reload() - if close_document: - self.close_document() - if enable_buttons: - self.enable_buttons() - if disable_buttons: - self.disable_buttons() - if clear_console: - self.console.clear() - - def disable_buttons(self) -> None: - """Disable buttons while render process running.""" - for b in self.toggle_buttons: - b.disabled = True - - def enable_buttons(self) -> None: - """Enable buttons after render process completes.""" - for b in self.toggle_buttons: - b.disabled = False - - """ - GUI METHODS - """ - - def toggle_window_locked(self) -> None: - """Toggle whether to pin the window above all other windows.""" - window = self.root_window - window.always_on_top = not window.always_on_top - - def screenshot_window(self) -> None: - """Take a screenshot of the Kivy window.""" - path = (PATH.OUT / "screenshots" / dt.now().strftime( - "%m-%d-%Y_")).with_suffix('.jpg') - path.parent.mkdir(mode=711, parents=True, exist_ok=True) - img_path = self.root_window.screenshot(name=str(path)) - - # Copy image to clipboard - with suppress(Exception): - with PImage.open(img_path) as f: - with BytesIO() as bmp: - # Load image data - f.convert("RGB").save(bmp, "BMP") - data = bmp.getvalue()[14:] - - # Apply data to the clipboard - clipboard.OpenClipboard() - clipboard.EmptyClipboard() - clipboard.SetClipboardData(clipboard.CF_DIB, data) - clipboard.CloseClipboard() - - @staticmethod - async def open_app_settings() -> None: - """Open the settings panel for app system and base configs.""" - cfg_panel = SettingsPopup() - cfg_panel.open() - - def build(self) -> TestApp | AppContainer: - """Build the app for display. - - Returns: - The root app layout. - """ - self._app_layout = TestApp() if self.env.TEST_MODE else AppContainer() - Window.bind( - # Bind drop files event - on_drop_file=self.queue_dropped_file, - on_drop_end=self.render_dropped_files) - self.add_console() - return self._app_layout - - def queue_dropped_file(self, _window, path: bytes, _x, _y) -> None: - """Queue a dropped in file for rendering.""" - if self._render_lock.locked(): - return - self._dropped_files.append(Path(path.decode())) - - def render_dropped_files(self, _window, _x, _y) -> None: - """Initiate a separate thread to render last dropped in cards.""" - if self._render_lock.locked(): - return - files = [f for f in self._dropped_files if f.is_file()] - folders = [f for f in self._dropped_files if f.is_dir()] - [files.extend(self.get_art_files(f)) for f in folders] - del self._dropped_files - - # Set up render job in separate thread - Thread(target=self.render_all, args=(False, files), daemon=True).start() - - def on_start(self) -> None: - """Fired after build is fired. Run a diagnostic check to see what works.""" - if not self.env.TEST_MODE: - self.disable_buttons() - if self.app_settings_button: - self.app_settings_button.disabled = False - - self.console.update("Running startup checks...") - - def photoshop_checks(app: PhotoshopHandler) -> None: - try: - # Check Photoshop connection - result = app.refresh_app() - if isinstance(result, OSError): - # Photoshop test failed - self.console.log_exception(result) - self.console.update( - f"Photoshop ... {msg_error('Cannot make connection with Photoshop!')}\n" - f"Check [b]logs/error.txt[/b] for more details." - ) - self.console.update( - f"Fonts ... {msg_warn('Cannot test fonts without Photoshop.')}" - ) - return - # Photoshop test passed - self.console.update( - f"Photoshop ... {msg_success('Connection established!')}" - ) - - # Check for missing or outdated fonts - missing, outdated = check_app_fonts(app, [PATH.FONTS]) - - # Font test passed - if not missing and not outdated: - self.console.update( - f"Fonts ... {msg_success('All essential fonts installed!')}" - ) - return - - # Missing fonts - self.console.update( - f"Fonts ... {msg_warn('Missing or outdated fonts:')}", end="" - ) - if missing: - self.console.update( - get_bullet_points( - [ - f"{f['name']} — {msg_warn('Not Installed')}" - for f in missing.values() - ] - ), - end="", - ) - if outdated: - self.console.update( - get_bullet_points( - [ - f"{f['name']} — {msg_info('New Version')}" - for f in outdated.values() - ] - ), - end="", - ) - finally: - if self.render_target_button: - self.render_target_button.disabled = False - if self.render_all_button: - self.render_all_button.disabled = False - - if self._app.ready: - photoshop_checks(self.app) - else: - self._app.add_listener(photoshop_checks) - - def check_latest_version() -> None: - # Check if using latest version - self.console.update( - f"Proxyshop Version ... {msg_success('Using latest version!')}" - if (self.check_app_version()) - else f"Proxyshop Version ... {msg_info('New release available!')}" - ) - - def update_set_data() -> None: - # Update set data if needed - check, error = update_hexproof_cache() - if check: - self.con.reload() - message = ( - msg_error(error) - if error - else msg_success( - "Update was applied!" if check else "Using latest data!" - ) - ) - self.console.update(f"Hexproof API Data ... {message}") - - def check_api_keys() -> None: - # Check if API keys are valid - if not self.env.API_GOOGLE: - self.env.API_GOOGLE = get_api_key("proxyshop.google.drive") - if not self.env.API_AMAZON: - self.env.API_AMAZON = get_api_key("proxyshop.amazon.s3") - keys_missing = [ - k - for k, v in [ - ("Google Drive", self.env.API_GOOGLE), - ("Amazon S3", self.env.API_AMAZON), - ] - if not v - ] - message = ( - msg_warn(f"Keys disabled: {', '.join(keys_missing)}") - if keys_missing - else msg_success("Keys retrieved!") - ) - self.console.update(f"Updater API Keys ... {message}") - self.console.update_btn.disabled = False - - for check in (check_latest_version, update_set_data, check_api_keys): - Thread(target=check).start() - - def add_console(self) -> None: - """Adds the console to the app window. Label gets frozen if loaded beforehand.""" - output = ConsoleOutput() - self._app_layout.add_widget(self.console) - self.console.ids.output_container.add_widget(output) - self.console.output = output - - def on_stop(self) -> None: - """Called when the app is closed.""" - if self.thread: - self.thread.set() - - """ - * App Updates - """ - - @staticmethod - async def check_for_updates(): - """Open updater Popup.""" - Updater = UpdatePopup() - Updater.open() - await ak.run_in_thread(Updater.check_for_updates, daemon=True) - ak.start(Updater.populate_updates()) - - def check_app_version(self) -> bool: - """Check if app is the latest version. - - Returns: - Return True if up to date, otherwise False. - """ - if self.env.APP_UPDATES_REPO: - with suppress(requests.RequestException, json.JSONDecodeError): - releases = get_github_releases(self.env.APP_UPDATES_REPO, per_page=1) - if (len(releases) > 0): - latest = releases[0].tag_name - try: - return bool(parse(self.env.VERSION.lstrip('v')) >= parse(latest.lstrip('v'))) - except InvalidVersion: - return self.env.VERSION >= latest - return True diff --git a/src/gui/console.py b/src/gui/console.py deleted file mode 100644 index ee6d145f..00000000 --- a/src/gui/console.py +++ /dev/null @@ -1,374 +0,0 @@ -""" -CONSOLE MODULES -""" -from __future__ import annotations - -# Standard Library Imports -import os -import time -import traceback -from functools import cached_property -from threading import Thread, Event, Lock -from typing import Any, TYPE_CHECKING -from datetime import datetime as dt - -# Third Party Imports -from kivy.lang import Builder -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.label import Label -from kivy.logger import Logger - -# Local Imports -from src._config import AppConfig -from src._state import AppEnvironment, PATH -from src.gui._state import get_root_app -from src.gui.utils import HoverButton -from src.utils.threading import ThreadInitializedInstance -from src.utils.windows import WindowState - -if TYPE_CHECKING: - from src.utils.adobe import PhotoshopHandler - - -class GUIConsole(BoxLayout): - """GUI console handler layout.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "console.kv")) - max_lines = 250 - running = True - waiting = False - lock = Lock() - - def __init__( - self, - cfg: AppConfig, - env: AppEnvironment, - app: ThreadInitializedInstance[PhotoshopHandler], - **kwargs - ): - # Establish global objects - super().__init__(**kwargs) - self.cfg = cfg - self.env = env - self.app = app - - # Test mode uses larger console - if not self.env.TEST_MODE: - self.size_hint = (1, .58) - - @cached_property - def main(self) -> Any: - """ProxyshopGUIApp: Get the running application.""" - return get_root_app() - - """ - * Console GUI Objects - """ - - @cached_property - def output(self) -> ConsoleOutput: - """Label where console output is stored.""" - return self.ids.console_output - - @cached_property - def continue_btn(self) -> HoverButton: - """Button that continues to the next render operation.""" - return self.ids.continue_btn - - @cached_property - def cancel_btn(self) -> HoverButton: - """Button that cancels the current render operation.""" - return self.ids.cancel_btn - - @cached_property - def update_btn(self) -> HoverButton: - """Button that launches the updater popup.""" - return self.ids.update_btn - - """ - * Reusable Strings - """ - - @property - def message_cancel(self) -> str: - """Boilerplate message for canceling the render process.""" - return "Understood! Canceling render operation.\n" - - @property - def message_waiting(self) -> str: - """Boilerplate message for awaiting a user response.""" - return "Manual editing enabled! Click Continue to proceed ..." - - @property - def message_skipping(self): - """Boilerplate message for skipping a render process.""" - return "Skipping this card!" - - @property - def time(self) -> str: - """Current date and time in human-readable format.""" - return dt.now().strftime("%m/%d/%Y %H:%M") - - @property - def current_output(self) -> str: - """str: Text currently contained in the console output label. Remove lines from - the top when the total linebreaks exceed 250.""" - output = self.output.text - line_count = output.count('\n') + 1 - if line_count <= self.max_lines: - return output - return output.split('\n', line_count-self.max_lines)[-1] - - """ - * Utility Methods - """ - - def enable_buttons(self) -> None: - """Enable both user signal buttons (Continue and Cancel).""" - self.continue_btn.disabled = False - self.cancel_btn.disabled = False - - def disable_buttons(self) -> None: - """Enable both user signal buttons (Continue and Cancel).""" - self.continue_btn.disabled = True - self.cancel_btn.disabled = True - - def log_exception(self, error: Exception, log_file: str = "error.txt") -> None: - """Log python exception. - - Args: - error: Exception object to log. - log_file: Text file to log the exception to. - """ - # Is this a proper Exception object? - if not hasattr(error, '__traceback__'): - return - - # Print the error for dev testing - Logger.exception(error) - - # Choose the path - path = PATH.LOGS / log_file - - # Add to log file - with open(path, "a", encoding="utf-8") as log: - log.write("============================================================================\n") - log.write(f"> {self.time}\n") - log.write("============================================================================\n") - traceback.print_exception(error, file=log) # noqa - - def clear(self) -> None: - """Clear the console output.""" - self.output.text = '' - - """ - * Console Operations - """ - - def update( - self, - msg: str = "", - exception: Exception | None = None, - end: str = "\n" - ) -> None: - """Add text to console output. - - Args: - msg: Message to add to the console output, blank if not provided. - exception: Exception object to log, if provided. - end: String to append at the end of the message, adds a newline if not provided. - """ - # Add message to the output label - self.output.text = f"{self.current_output}{msg}{end}" - self.ids.viewport.scroll_y = 0 - if exception: - self.log_exception(exception) - - def log_error( - self, - thr: Event, - card: str, - template: str | None = None, - msg: str = 'Encountered a general error!', - e: Exception | None = None - ) -> bool: - """Log failed card and exception if provided, then prompt user to make a decision. - - Args: - thr: Event object representing the status of the render thread. - card: Card to log in /logs/failed.txt. - template: Template to log in /logs/failed.txt. - msg: Message to add to the console output. - e: Exception to log in /logs/error.txt. - - Returns: - True if user wishes to continue, otherwise False. - """ - with open(PATH.LOGS_FAILED, "a", encoding="utf-8") as log: - log.write(f"{card}{f' ({template})' if template else ''} [{self.time}]\n") - return self.error(thr=thr, msg=msg, exception=e) - - def error( - self, - thr: Event | None = None, - msg: str = 'Encountered a general error!', - exception: Exception | None = None, - end: str = '\nShould I continue?\n' - ) -> bool: - """Display error, wait for user to cancel or continue. - - Args: - thr: Event object representing the status of the render thread. - msg: Message to add to the console output. - exception: Exception to log in /logs/error.txt. - end: String to append to the end of the message. - - Returns: - True if user wishes to continue, otherwise False. - """ - # Stop awaiting any signals - self.end_await() - - # Log exception if provided - if exception: - self.log_exception(exception) - - # Skip the prompts for Dev Mode - if self.env.TEST_MODE: - return False - - # Notify the user, then wait for a continue signal if needed - if self.cfg.skip_failed: - self.update(f"{msg}\n{self.message_skipping}") - return True - - # Previous error already handled - if thr and thr.is_set(): - return False - - # Wait for user response - if self.await_choice(thr, msg, end): - return True - - # Relay the cancellation - self.update(self.message_cancel) - return False - - """ - * User Prompt Signals - """ - - def await_choice(self, thr: Event, msg: str | None = None, end: str = "\n", show_photoshop: bool = True) -> bool: - """Prompt the user to either continue or cancel. - - Args: - thr: Event object representing the status of the render thread. - msg: Message to prompt the user with, uses boilerplate waiting message if not provided. - end: String to append to the end of a message, adds a newline if not provided. - - Returns: - True if user wishes to continue, otherwise False. - """ - # Clear other await procedures, then begin awaiting a user signal - self.end_await() - self.update(msg=msg or self.message_waiting, end=end) - self.enable_buttons() - if self.cfg.minimize_photoshop and show_photoshop: - # Show Photoshop in case it is minimized - self.app.instance.set_window_state(WindowState.SHOWDEFAULT) - self.start_await() - - # Cancel the current thread or continue based on user signal - if thr: - self.cancel_thread(thr) if not self.running else self.start_await_cancel(thr) - - # Minimize Photoshop if the setting for that is active - if self.running and self.cfg.minimize_photoshop: - self.app.instance.set_window_state(WindowState.MINIMIZE) - - return self.running - - def signal(self, choice: bool) -> None: - """Signal the user decision to any prompts awaiting a response. - - Args: - choice: True if continuing, False if canceling. - """ - # Ensure buttons can't be spammed - self.disable_buttons() - - # Continue if True, otherwise False - self.running = choice - - # End await - self.end_await() - - """ - * Await Cancelling Procedures - """ - - def start_await_cancel(self, thr: Event) -> None: - """Starts an await_cancel loop in a separate thread. - @param thr: Event object representing the status of the render thread. - """ - Thread(target=self.await_cancel, args=(thr,), daemon=True).start() - - def await_cancel(self, thr: Event): - """Await a signal from the user to cancel the operation. - - Args: - thr: Event object representing the status of the render thread. - """ - # Await a signal from the user to cancel the operation - self.cancel_btn.disabled = False - self.start_await() - - # Cancel the current thread if running flag set to False - if not self.running: - self.cancel_thread(thr) - - """ - * Await Loop Handlers - """ - - def end_await(self) -> None: - """Clears the console of any procedures awaiting a response.""" - # Set the waiting flag - self.waiting = False - - # Ensure await is complete before returning - with self.lock: - return - - def start_await(self) -> None: - """Starts an await loop that finishes when waiting is flagged as False.""" - # Set initial running and waiting flags - self.waiting = True - self.running = True - - # Await can only start if others have finished - with self.lock: - while self.waiting: - time.sleep(.1) - self.disable_buttons() - - """ - * Thread Management - """ - - def cancel_thread(self, thr: Event) -> None: - """Initiate cancellation of a given thread operation. - - Args: - thr: Thread event to signal the cancellation. - """ - # Kill the thread and notify - thr.set() - self.update(self.message_cancel) - - -class ConsoleOutput(Label): - """Label displaying console output.""" - - -class ConsoleControls(BoxLayout): - """Layout containing console control.""" diff --git a/src/gui/popup/settings.py b/src/gui/popup/settings.py deleted file mode 100644 index a071f093..00000000 --- a/src/gui/popup/settings.py +++ /dev/null @@ -1,271 +0,0 @@ -""" -KIVY SETTINGS POPUPS -""" -# Standard Library Imports -from functools import cached_property -from os import PathLike - -# Third Party Imports -import kivy.utils as utils -from kivy.properties import ConfigParser -from kivy.uix.colorpicker import ColorPicker -from kivy.core.window import Window -from kivy.metrics import dp -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from kivy.uix.modalview import ModalView -from kivy.uix.popup import Popup -from kivy.uix.settings import ( - Settings, - SettingOptions, - SettingSpacer, - SettingNumeric, - SettingString, - SettingColor -) -from kivy.uix.textinput import TextInput -from kivy.uix.togglebutton import ToggleButton -from kivy.uix.widget import Widget - -# Local Imports -from src._loader import TemplateDetails, ConfigManager -from src.gui._state import GlobalAccess - -""" -AESTHETIC CLASSES -""" - - -class FormattedSettingColor(SettingColor): - """Create custom SettingColor class to allow Markup in title.""" - def _create_popup(self, instance): - # create popup layout - content = BoxLayout(orientation='vertical', spacing='5dp') - popup_width = min(0.95 * Window.width, dp(500)) - self.popup = popup = Popup( - title=self.title, content=content, size_hint=(None, 0.9), - width=popup_width) - popup.children[0].children[-1].markup = True - - self.colorpicker = colorpicker = ColorPicker(color=utils.get_color_from_hex(self.value)) - colorpicker.bind(on_color=self._validate) - - self.colorpicker = colorpicker - content.add_widget(colorpicker) - content.add_widget(SettingSpacer()) - - # 2 buttons are created for accept or cancel the current value - btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp') - btn = Button(text='Ok') - btn.bind(on_release=self._validate) - btnlayout.add_widget(btn) - btn = Button(text='Cancel') - btn.bind(on_release=self._dismiss) - btnlayout.add_widget(btn) - content.add_widget(btnlayout) - - # all done, open the popup ! - popup.open() - - -class FormattedSettingString(SettingString): - """Create custom SettingString class to allow Markup in title.""" - def _create_popup(self, instance): - # create popup layout - content = BoxLayout(orientation='vertical', spacing='5dp') - popup_width = min(0.95 * Window.width, dp(500)) - self.popup = popup = Popup( - title=self.title, content=content, size_hint=(None, None), - size=(popup_width, '250dp')) - popup.children[0].children[-1].markup = True - - # create the textinput used for numeric input - self.textinput = textinput = TextInput( - text=self.value, font_size='24sp', multiline=False, - size_hint_y=None, height='42sp') - textinput.bind(on_text_validate=self._validate) - self.textinput = textinput - - # construct the content, widget are used as a spacer - content.add_widget(Widget()) - content.add_widget(textinput) - content.add_widget(Widget()) - content.add_widget(SettingSpacer()) - - # 2 buttons are created for accept or cancel the current value - btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp') - btn = Button(text='Ok') - btn.bind(on_release=self._validate) - btnlayout.add_widget(btn) - btn = Button(text='Cancel') - btn.bind(on_release=self._dismiss) - btnlayout.add_widget(btn) - content.add_widget(btnlayout) - - # all done, open the popup ! - popup.open() - - -class FormattedSettingNumeric(SettingNumeric): - """Create custom SettingNumeric class to allow Markup in title.""" - def _create_popup(self, instance): - # create popup layout - content = BoxLayout(orientation='vertical', spacing='5dp') - popup_width = min(0.95 * Window.width, dp(500)) - self.popup = popup = Popup( - title=self.title, content=content, size_hint=(None, None), - size=(popup_width, '250dp')) - popup.children[0].children[-1].markup = True - - # create the textinput used for numeric input - self.textinput = textinput = TextInput( - text=self.value, font_size='24sp', multiline=False, - size_hint_y=None, height='42sp') - textinput.bind(on_text_validate=self._validate) - self.textinput = textinput - - # construct the content, widget are used as a spacer - content.add_widget(Widget()) - content.add_widget(textinput) - content.add_widget(Widget()) - content.add_widget(SettingSpacer()) - - # 2 buttons are created for accept or cancel the current value - btnlayout = BoxLayout(size_hint_y=None, height='50dp', spacing='5dp') - btn = Button(text='Ok') - btn.bind(on_release=self._validate) - btnlayout.add_widget(btn) - btn = Button(text='Cancel') - btn.bind(on_release=self._dismiss) - btnlayout.add_widget(btn) - content.add_widget(btnlayout) - - # all done, open the popup ! - popup.open() - - def _validate(self, instance): - # Check float status by using the current input text. - # This allows converting between int and float. - is_float = '.' in str(self.textinput.text) - self._dismiss() - try: - if is_float: - self.value = str(float(self.textinput.text)) - else: - self.value = str(int(self.textinput.text)) - except ValueError: - return - - -class FormattedSettingOptions(SettingOptions): - """Create custom SettingOptions class to allow Markup in title.""" - def _create_popup(self, instance): - # create the popup - content = BoxLayout(orientation='vertical', spacing='5dp') - popup_width = min(0.95 * Window.width, dp(500)) - self.popup = popup = Popup( - content=content, title=self.title, size_hint=(None, None), - size=(popup_width, '400dp')) - popup.height = len(self.options) * dp(55) + dp(150) - popup.children[0].children[-1].markup = True - - # add all the options - content.add_widget(Widget(size_hint_y=None, height=1)) - uid = str(self.uid) - for option in self.options: - state = 'down' if option == self.value else 'normal' - btn = ToggleButton(text=option, state=state, group=uid) - btn.bind(on_release=self._set_option) - content.add_widget(btn) - - # finally, add a cancel button to return on the previous panel - content.add_widget(SettingSpacer()) - btn = Button(text='Cancel', size_hint_y=None, height=dp(50)) - btn.bind(on_release=popup.dismiss) - content.add_widget(btn) - - # and open the popup ! - popup.open() - - -""" -SETTINGS POPUP -""" - - -class SettingsPopup(ModalView, GlobalAccess): - """Popup menu for changing app or template settings.""" - - """ - * ConfigParser objects - """ - - @staticmethod - def get_config(ini_file: str | PathLike) -> ConfigParser: - config = ConfigParser(allow_no_value=True) - config.optionxform = str - config.read(str(ini_file)) - return config - - """ - * Settings Panel instance - """ - - @cached_property - def cfg_panel(self) -> Settings: - """Settings panel to load JSON validated config data into.""" - s = Settings() - s.bind(on_close=self.dismiss) - s.register_type('options', FormattedSettingOptions) - s.register_type('string', FormattedSettingString) - s.register_type('numeric', FormattedSettingNumeric) - s.register_type('color', FormattedSettingColor) - return s - - """ - * Generate Settings Page - """ - - def __init__(self, template: TemplateDetails = None, **kwargs): - super().__init__(**kwargs) - - # Load and validate ConfigManager - self.template = template - self.manager = template['config'] if template else ConfigManager() - self.manager.validate_configs() - if template: - self.manager.validate_template_configs() - - # Is this global or template specific? - self.load_template_config() if self.manager.has_template_ini else self.load_global_config() - self.add_widget(self.cfg_panel) - - def load_global_config(self) -> None: - """Load base and app settings into global config panel.""" - - # Add main settings - self.cfg_panel.add_json_panel( - title='Main Settings', - config=self.get_config(self.manager.base_path_ini), - data=self.manager.base_json) - - # Add system settings - self.cfg_panel.add_json_panel( - title='System Settings', - config=self.get_config(self.manager.app_path_ini), - data=self.manager.app_json) - - def load_template_config(self) -> None: - """Load a template-specific settings into config panel.""" - - # Add custom template settings if provided - config = self.get_config(self.manager.template_path_ini) - if self.manager.template_path_schema: - self.cfg_panel.add_json_panel( - title=f"{self.template['name']} Template", - config=config, data=self.manager.template_json) - - # Add main settings - self.cfg_panel.add_json_panel( - title='Main Settings', - config=config, data=self.manager.base_json) diff --git a/src/gui/popup/updater.py b/src/gui/popup/updater.py deleted file mode 100644 index 6cedfbea..00000000 --- a/src/gui/popup/updater.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -* GUI Popup: Updater -""" -# Standard Library Imports -import os - -# Third Party Imports -import asynckivy as ak -from kivy.lang import Builder -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.popup import Popup -from kivy.uix.progressbar import ProgressBar -from kivy.uix.label import Label - -# Local Imports -from src._state import PATH -from src._loader import AppTemplate, check_for_updates -from src.console import msg_success, msg_error, msg_italics -from src.gui._state import GlobalAccess, GUI - -""" -* GUI Classes -""" - - -class UpdatePopup(Popup, GlobalAccess): - """Popup modal for updating templates.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "updater.kv")) - updates: list[AppTemplate] = [] - update_downloaded = False - loading = True - categories = {} - entries = {} - - """ - * Update Utils - """ - - def on_dismiss(self): - """When this popup is dismissed, reload rows if updates were downloaded.""" - if self.update_downloaded: - for _, layouts in GUI.template_list.items(): - for TL in layouts: - TL.reload_template_rows() - - def check_for_updates(self): - """Runs the check_for_updates core function and fills the update dictionary.""" - self.updates: list[AppTemplate] = check_for_updates(self.main.templates) - - async def populate_updates(self): - """Load the list of updates available.""" - - # Track current background color - bg_color = "#181818" - - # Remove loading screen - if self.loading: - self.ids.container.remove_widget(self.ids.loading) - self.ids.container.padding = [0, 0, 0, 0] - self.loading = False - - # Loop through templates - for i, t in enumerate(self.updates): - - # Alternate table item color - bg_color = "#101010" if bg_color == "#181818" else "#181818" - update_entry = UpdateEntry(self, t, bg_color) - self.ids.container.add_widget(update_entry) - self.entries[str(t.path_psd)] = update_entry - - # Remove loading text - self.ids.loading_text.text = msg_italics(" No updates found!") if ( - len(self.updates) == 0 - ) else msg_italics(" Updates Available") - - -class UpdateEntry(BoxLayout, GlobalAccess): - def __init__(self, parent: UpdatePopup, template: AppTemplate, bg_color: str, **kwargs): - self.bg_color = bg_color - self.name = template.name - self.status = msg_success(template.update_version) - self.template: AppTemplate = template - self.root = parent - super().__init__(**kwargs) - - async def download_update(self, download: BoxLayout) -> None: - """Initiates a template update download. - - Args: - download: Layout object containing the download progress bar or status. - """ - self.progress = UpdateProgress(self.template.update_size) - download.clear_widgets() - download.add_widget(self.progress) - result = await ak.run_in_thread( - lambda: self.template.update_template( - self.progress.update_progress - ), daemon=True) - await ak.sleep(.5) - - # Success - if result: - self.root.update_downloaded = True - return await self.mark_updated() - - # Failed - download.clear_widgets() - download.add_widget(Label(text=msg_error("FAILED"), markup=True)) - - async def mark_updated(self): - """Update template version, remove pending update, and remove the template row.""" - - # Update version tracker and reset update data - self.template.mark_updated() - - # Remove this widget - entry = self.root.entries[str(self.template.path_psd)] - self.root.ids.container.remove_widget(entry) - del entry - - -class UpdateProgress(ProgressBar, GlobalAccess): - def __init__(self, size: int, **kwargs): - super().__init__(**kwargs) - self.download_size = int(size) - self.current = 0 - - def update_progress(self, tran: int, total: int) -> None: - """Update the download progress bar via callback. - - Args: - tran: Bytes transferred so far. - total: Total bytes to transfer. - """ - self.value = int((tran / total) * 100) diff --git a/src/gui/qml/App.qml b/src/gui/qml/App.qml new file mode 100644 index 00000000..014dd5b9 --- /dev/null +++ b/src/gui/qml/App.qml @@ -0,0 +1,608 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components +import qml.dialogs +import qml.views + +ApplicationWindow { + id: appWindow + + property QtObject currentRenderingModel: viewTabBar.currentIndex === 1 ? batchRenderModel : templateListModel + + title: "Proxyshop" + width: 800 + height: 640 + visible: true + color: systemPalette.window + + SystemPalette { + id: systemPalette + } + + FontLoader { + id: emojiFontLoader + source: "file:///C:/Windows/Fonts/seguiemj.ttf" + //source: "file:///C:/Windows/Fonts/seguisym.ttf" + } + + FontLoader { + id: monospaceFontLoader + source: "file:///C:/Windows/Fonts/CascadiaMono.ttf" + } + + Settings { + id: settings + + category: "App" + location: filePathModel.get_preferences_path("App.ini") + + // UI sizing preferences + property alias windowWidth: appWindow.width + property alias windowHeight: appWindow.height + property alias windowX: appWindow.x + property alias windowY: appWindow.y + property int currentRenderModeTab + property var rootSplitState + property var templateListSplitState + + // Model preferences + property string selectedTemplateName + property string batchModeTemplateSelections + } + + Component.onCompleted: { + if (settings.currentRenderModeTab) + viewTabBar.setCurrentIndex(settings.currentRenderModeTab); + + rootSplit.restoreState(settings.rootSplitState); + templateListSplit.restoreState(settings.templateListSplitState); + + if (settings.selectedTemplateName) + templateListModel.select_template(settings.selectedTemplateName); + if (settings.batchModeTemplateSelections) + batchRenderModel.restore_template_selections(settings.batchModeTemplateSelections); + } + Component.onDestruction: { + settings.currentRenderModeTab = viewTabBar.currentIndex; + + settings.rootSplitState = rootSplit.saveState(); + settings.templateListSplitState = templateListSplit.saveState(); + + settings.selectedTemplateName = templateListModel.selected_template_name(); + settings.batchModeTemplateSelections = batchRenderModel.get_template_selections_json(); + + settingsTreeModel.save_configs(); + templateUpdaterModel.save_versions(); + } + + DelegateModel { + id: templateListDelegateModel + model: templateListModel + } + + function updateRenderQueueSize() { + const rCount = renderOperationsModel.rowCount(); + if (rCount != renderOperationsConnections.renderQueueSize) + renderOperationsConnections.renderQueueSize = rCount; + } + + Connections { + id: renderOperationsConnections + + property int renderQueueSize: 0 + + target: renderOperationsModel + + function onRowsInserted() { + appWindow.updateRenderQueueSize(); + } + function onRowsRemoved() { + appWindow.updateRenderQueueSize(); + } + } + + DropArea { + anchors.fill: parent + onDropped: drop => { + if (drop.hasUrls) + appWindow.currentRenderingModel.render_targets(drop.urls); + } + } + + menuBar: MenuBar { + id: menuBar + + background.implicitHeight: 0 + background.implicitWidth: 0 + + delegate: MenuBarItem { + id: menuBarItem + + leftPadding: 8 + rightPadding: 8 + topPadding: 7 + bottomPadding: 7 + background: Rectangle { + color: menuBarItem.down || menuBarItem.highlighted ? systemPalette.text : "transparent" + opacity: menuBarItem.down ? 0.1 : 0.05 + } + } + + CustomMenu { + systemPalette: systemPalette + title: "File" + + Action { + text: "Render..." + onTriggered: appWindow.currentRenderingModel.render_selections() + } + MenuSeparator {} + Action { + text: "Open app directory" + onTriggered: Qt.openUrlExternally(filePathModel.app_root) + } + Action { + text: "Open output directory" + onTriggered: Qt.openUrlExternally(filePathModel.out_directory) + } + Action { + text: "Open templates directory" + onTriggered: Qt.openUrlExternally(filePathModel.templates_directory) + } + Action { + text: "Open plugins directory" + onTriggered: Qt.openUrlExternally(filePathModel.plugins_directory) + } + } + CustomMenu { + systemPalette: systemPalette + title: "Tools" + + Action { + text: "Transform images" + onTriggered: { + if (imageTransformLoader.active) { + imageTransformLoader.item?.show(); + } else { + imageTransformLoader.active = true; + } + } + } + Action { + text: "Preview system palette" + onTriggered: { + if (systemPaletteViewerLoader.active) { + systemPaletteViewerLoader.item?.show(); + } else { + systemPaletteViewerLoader.active = true; + } + } + } + Action { + text: "Preview fonts" + onTriggered: { + if (fontViewerLoader.active) { + fontViewerLoader.item?.show(); + } else { + fontViewerLoader.active = true; + } + } + } + } + CustomMenu { + systemPalette: systemPalette + title: "Tests" + implicitWidth: 270 + + Action { + text: "Test all" + onTriggered: testRendersModel.test_all("", false) + } + Action { + text: "Quick test all" + onTriggered: testRendersModel.test_all("", true) + } + + CustomMenu { + id: testAllLayoutsSubMenu + + systemPalette: systemPalette + title: "Test all with layout" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: testRendersModel.test_all(modelData, false) + } + + onObjectAdded: (index, object) => testAllLayoutsSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => testAllLayoutsSubMenu.removeItem(object) + } + } + CustomMenu { + id: quickTestAllLayoutsSubMenu + + systemPalette: systemPalette + title: "Quick test all with layout" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: testRendersModel.test_all(modelData, true) + } + + onObjectAdded: (index, object) => quickTestAllLayoutsSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => quickTestAllLayoutsSubMenu.removeItem(object) + } + } + + MenuSeparator {} + + Action { + text: "Test selected template(s)" + onTriggered: appWindow.currentRenderingModel.test_render("", false) + } + Action { + text: "Quick test selected template(s)" + onTriggered: appWindow.currentRenderingModel.test_render("", true) + } + + CustomMenu { + id: selectedTemplateLayoutsSubMenu + + systemPalette: systemPalette + title: "Test selected template(s) with layout" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: appWindow.currentRenderingModel.test_render(modelData, false) + } + + onObjectAdded: (index, object) => selectedTemplateLayoutsSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => selectedTemplateLayoutsSubMenu.removeItem(object) + } + } + CustomMenu { + id: selectedTemplateQuickLayoutsSubMenu + + systemPalette: systemPalette + title: "Quick test selected template(s) with layout" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: appWindow.currentRenderingModel.test_render(modelData, true) + } + + onObjectAdded: (index, object) => selectedTemplateQuickLayoutsSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => selectedTemplateQuickLayoutsSubMenu.removeItem(object) + } + } + } + + CustomMenu { + systemPalette: systemPalette + title: "Help" + + Action { + text: `🌐 Project GitHub` + onTriggered: Qt.openUrlExternally(github_url) + } + Action { + text: ` Frequently asked questions` + onTriggered: Qt.openUrlExternally(`${github_url}#-faq`) + } + Action { + text: `🪲 Report an issue` + onTriggered: Qt.openUrlExternally(`${github_url}/issues`) + } + } + + RowLayout { + parent: menuBar + anchors.right: parent.right + + CustomButton { + systemPalette: systemPalette + text: `⬇️ Updater` + onClicked: { + if (templateUpdaterLoader.active) { + templateUpdaterLoader.item?.show(); + } else { + templateUpdaterLoader.active = true; + } + } + } + CustomButton { + id: settingsButton + + systemPalette: systemPalette + text: `⚙️ Settings` + onClicked: openSettings() + + function openSettings(templateName = "", className = "", pluginName = ""): void { + settingsTreeModel.select_settings_section(templateName, className, pluginName); + if (settingsWindowLoader.active) { + settingsWindowLoader.item?.show(); + } else { + settingsWindowLoader.active = true; + } + } + } + } + } + + CustomMessageDialog { + id: renderMessageDialog + + dialogContentModel: renderMessageDialogContentModel + } + + Connections { + id: renderMessageDialogConnections + + target: renderMessageDialogContentModel + function onDialogRequested(): void { + if (renderMessageDialog.visible) { + renderMessageDialog.accept(); + } + renderMessageDialog.open(); + } + } + + CustomFileDialog { + dialogModel: fileDialogModel + settings: settings + } + + Loader { + id: settingsWindowLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("views/SettingsWindow.qml", { + systemPalette: systemPalette, + pathModel: filePathModel, + emojiFontName: emojiFontLoader.name, + settingsTreeModel: settingsTreeModel, + settingsModel: settingsModel + }); + } + } + + Loader { + id: templateUpdaterLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("views/TemplateUpdater.qml", { + systemPalette: systemPalette, + updaterModel: templateUpdaterModel, + pathModel: filePathModel + }); + } + } + + Loader { + id: renderQueueLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("views/RenderQueue.qml", { + systemPalette: systemPalette, + emojiFontName: emojiFontLoader.name, + queueModel: renderOperationsModel, + pathModel: filePathModel + }); + } + } + + Loader { + id: imageTransformLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("tools/ImageTransformWindow.qml", { + systemPalette: systemPalette, + transformModel: imageTransformModel + }); + } + } + + Loader { + id: systemPaletteViewerLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("tools/SystemPaletteViewer.qml"); + } + } + + Loader { + id: fontViewerLoader + + active: false + asynchronous: true + onLoaded: item.show() + + Component.onCompleted: { + setSource("tools/FontViewer.qml"); + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: viewTabBar + + Layout.fillWidth: true + + width: parent.width + implicitHeight: 30 + spacing: 0 + palette.window: systemPalette.mid + palette.dark: systemPalette.mid + palette.mid: systemPalette.midlight + + CustomTabButton { + systemPalette: systemPalette + text: "Templates" + implicitHeight: viewTabBar.implicitHeight + background.implicitHeight: viewTabBar.implicitHeight + } + CustomTabButton { + systemPalette: systemPalette + text: "Batch mode" + implicitHeight: viewTabBar.implicitHeight + background.implicitHeight: viewTabBar.implicitHeight + } + + RowLayout { + parent: viewTabBar + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + CustomButton { + systemPalette: systemPalette + implicitHeight: viewTabBar.implicitHeight + text: ` Queue (${renderOperationsConnections.renderQueueSize})` + onClicked: { + if (renderQueueLoader.active) { + renderQueueLoader.item?.show(); + } else { + renderQueueLoader.active = true; + } + } + } + CustomButton { + systemPalette: systemPalette + implicitHeight: viewTabBar.implicitHeight + text: renderOperationsModel.is_rendering ? `🚫 Cancel` : `⏯️ Resume` + enabled: renderOperationsConnections.renderQueueSize || renderOperationsModel.is_rendering + onClicked: renderOperationsModel.is_rendering ? renderOperationsModel.cancel() : renderOperationsModel.resume() + } + CustomButton { + id: renderButton + + systemPalette: systemPalette + implicitHeight: viewTabBar.implicitHeight + text: `▶️ Render` + onClicked: appWindow.currentRenderingModel.render_selections() + } + } + } + + SplitView { + id: rootSplit + + Layout.fillHeight: true + Layout.fillWidth: true + + orientation: Qt.Vertical + + StackLayout { + SplitView.fillHeight: true + SplitView.fillWidth: true + + currentIndex: viewTabBar.currentIndex + + Rectangle { + color: systemPalette.window + Layout.fillHeight: true + Layout.fillWidth: true + + SplitView { + id: templateListSplit + + anchors.fill: parent + orientation: Qt.Horizontal + + TemplateList { + SplitView.fillWidth: true + SplitView.fillHeight: true + + systemPalette: systemPalette + templateListMdl: templateListModel + templateListDelegateMdl: templateListDelegateModel + openSettings: settingsButton.openSettings + } + TemplateDetails { + SplitView.minimumWidth: 100 + SplitView.preferredWidth: 200 + + systemPalette: systemPalette + templateListMdl: templateListModel + templateListDelegateMdl: templateListDelegateModel + pathModel: filePathModel + } + } + } + BatchRenderView { + Layout.fillHeight: true + Layout.fillWidth: true + + systemPalette: systemPalette + batchModel: batchRenderModel + pathModel: filePathModel + } + } + Console { + SplitView.preferredHeight: 200 + SplitView.fillWidth: true + + systemPalette: systemPalette + logModel: consoleModel + pathModel: filePathModel + emojiFontFamily: emojiFontLoader.name + monospaceFontFamily: monospaceFontLoader.name + } + } + } +} diff --git a/src/gui/qml/components/Badge.qml b/src/gui/qml/components/Badge.qml new file mode 100644 index 00000000..a9728c2f --- /dev/null +++ b/src/gui/qml/components/Badge.qml @@ -0,0 +1,36 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts + +Rectangle { + id: badge + + required property string text + required property color textColor + required property color bgColor + property double fontSize: 8 + property bool hovered: hoverHandler.hovered + + implicitWidth: layout.implicitWidth + 15 + implicitHeight: layout.implicitHeight + 5 + radius: 10 + color: bgColor + + RowLayout { + id: layout + + anchors.centerIn: parent + + Text { + Layout.fillWidth: true + + text: badge.text + font.pointSize: badge.fontSize + color: badge.textColor + } + } + + HoverHandler { + id: hoverHandler + } +} diff --git a/src/gui/qml/components/CustomButton.qml b/src/gui/qml/components/CustomButton.qml new file mode 100644 index 00000000..80da6507 --- /dev/null +++ b/src/gui/qml/components/CustomButton.qml @@ -0,0 +1,23 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +Button { + id: control + + required property SystemPalette systemPalette + property color bgColor: "transparent" + + background: Rectangle { + implicitHeight: 30 + implicitWidth: control.contentItem.implicitWidth + color: control.bgColor + + Rectangle { + anchors.fill: parent + visible: control.enabled && (control.down || control.hovered) + opacity: control.down ? 0.1 : 0.05 + color: control.systemPalette.text + } + } +} diff --git a/src/gui/qml/components/CustomCheckBox.qml b/src/gui/qml/components/CustomCheckBox.qml new file mode 100644 index 00000000..343e3bdf --- /dev/null +++ b/src/gui/qml/components/CustomCheckBox.qml @@ -0,0 +1,32 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +CheckBox { + id: control + + required property SystemPalette systemPalette + + padding: 0 + spacing: 0 + implicitWidth: 25 + implicitHeight: 25 + indicator: Rectangle { + anchors.fill: parent + color: control.systemPalette.button + + Rectangle { + anchors.fill: parent + visible: control.enabled && (control.down || control.hovered) + opacity: control.down ? 0.1 : 0.05 + color: control.systemPalette.text + } + Text { + anchors.centerIn: parent + text: "✔" + font.pointSize: 11 + color: control.systemPalette.text + visible: control.checked + } + } +} diff --git a/src/gui/qml/components/CustomComboBox.qml b/src/gui/qml/components/CustomComboBox.qml new file mode 100644 index 00000000..78df602f --- /dev/null +++ b/src/gui/qml/components/CustomComboBox.qml @@ -0,0 +1,41 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +ComboBox { + id: control + + required property SystemPalette systemPalette + property var itemTextColor + property color _selectedTextColor: itemTextColor ? itemTextColor(control.currentIndex) : systemPalette.buttonText + property var tooltipModel: undefined + + background.implicitHeight: 30 + palette.buttonText: _selectedTextColor + delegate: CustomMenuItem { + id: controlDelegate + + required property var model + required property int index + + systemPalette: control.systemPalette + + palette.windowText: control.itemTextColor ? control.itemTextColor(index) : systemPalette.windowText + width: ListView.view.width + text: model[control.textRole] + font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal + highlighted: control.highlightedIndex === index + hoverEnabled: control.hoverEnabled + + ToolTip { + parent: controlDelegate + visible: controlDelegate.hovered && Boolean(control.tooltipModel) + delay: 50 + text: control.tooltipModel ? control.tooltipModel[controlDelegate.index] : "" + } + } + + Component.onCompleted: { + indicator.color = systemPalette.text; + } +} diff --git a/src/gui/qml/components/CustomItemDelegate.qml b/src/gui/qml/components/CustomItemDelegate.qml new file mode 100644 index 00000000..ac14e943 --- /dev/null +++ b/src/gui/qml/components/CustomItemDelegate.qml @@ -0,0 +1,26 @@ +import QtQuick +import QtQuick.Controls + +ItemDelegate { + id: control + + required property SystemPalette systemPalette + property color bgColor: "transparent" + + implicitHeight: 30 + padding: 0 + spacing: 0 + + background: Rectangle { + implicitHeight: 30 + implicitWidth: control.contentItem.implicitWidth + color: control.highlighted ? control.systemPalette.highlight : control.bgColor + + Rectangle { + anchors.fill: parent + visible: control.enabled && (control.down || control.hovered) + opacity: control.down ? 0.1 : 0.05 + color: control.systemPalette.text + } + } +} diff --git a/src/gui/qml/components/CustomMenu.qml b/src/gui/qml/components/CustomMenu.qml new file mode 100644 index 00000000..e9ab2f98 --- /dev/null +++ b/src/gui/qml/components/CustomMenu.qml @@ -0,0 +1,20 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +Menu { + id: control + + required property SystemPalette systemPalette + + delegate: CustomMenuItem { + systemPalette: control.systemPalette + } + background: Rectangle { + implicitWidth: 200 + implicitHeight: 40 + color: control.systemPalette.button + border.color: control.systemPalette.mid + border.width: 1 + } +} diff --git a/src/gui/qml/components/CustomMenuItem.qml b/src/gui/qml/components/CustomMenuItem.qml new file mode 100644 index 00000000..dc57a7ef --- /dev/null +++ b/src/gui/qml/components/CustomMenuItem.qml @@ -0,0 +1,16 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +MenuItem { + id: control + + required property SystemPalette systemPalette + + background: Rectangle { + implicitHeight: 30 + + color: control.highlighted ? control.systemPalette.light : "transparent" + visible: control.down || control.highlighted + } +} diff --git a/src/gui/qml/components/CustomSpinBox.qml b/src/gui/qml/components/CustomSpinBox.qml new file mode 100644 index 00000000..943d5d0d --- /dev/null +++ b/src/gui/qml/components/CustomSpinBox.qml @@ -0,0 +1,6 @@ +import QtQuick.Controls + +SpinBox { + implicitHeight: 30 + padding: 0 +} \ No newline at end of file diff --git a/src/gui/qml/components/CustomTabButton.qml b/src/gui/qml/components/CustomTabButton.qml new file mode 100644 index 00000000..bda8aee5 --- /dev/null +++ b/src/gui/qml/components/CustomTabButton.qml @@ -0,0 +1,27 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: control + + required property SystemPalette systemPalette + property color bgColor: "transparent" + + leftPadding: 8 + rightPadding: 8 + spacing: 0 + width: implicitWidth + palette.brightText: systemPalette.placeholderText + background: Rectangle { + implicitHeight: 30 + implicitWidth: control.contentItem.implicitWidth + color: control.bgColor + + Rectangle { + anchors.fill: parent + visible: control.enabled && (control.down || control.hovered) + opacity: control.down ? 0.1 : 0.05 + color: control.systemPalette.text + } + } +} diff --git a/src/gui/qml/components/CustomTextField.qml b/src/gui/qml/components/CustomTextField.qml new file mode 100644 index 00000000..fe64461a --- /dev/null +++ b/src/gui/qml/components/CustomTextField.qml @@ -0,0 +1,9 @@ +import QtQuick +import QtQuick.Controls + +TextField { + implicitHeight: 30 + leftPadding: 4 + rightPadding: 4 + background.implicitWidth: 0 +} \ No newline at end of file diff --git a/src/gui/qml/components/SelectableText.qml b/src/gui/qml/components/SelectableText.qml new file mode 100644 index 00000000..349f4bc7 --- /dev/null +++ b/src/gui/qml/components/SelectableText.qml @@ -0,0 +1,8 @@ +import QtQuick + +TextEdit { + readOnly: true + textFormat: TextEdit.AutoText + wrapMode: Text.WordWrap + selectByMouse: true +} diff --git a/src/gui/qml/components/qmldir b/src/gui/qml/components/qmldir new file mode 100644 index 00000000..3e55cab6 --- /dev/null +++ b/src/gui/qml/components/qmldir @@ -0,0 +1,12 @@ +module qml.components +Badge Badge.qml +CustomButton CustomButton.qml +CustomCheckBox CustomCheckBox.qml +CustomComboBox CustomComboBox.qml +CustomItemDelegate CustomItemDelegate.qml +CustomMenu CustomMenu.qml +CustomMenuItem CustomMenuItem.qml +CustomSpinBox CustomSpinBox.qml +CustomTabButton CustomTabButton.qml +CustomTextField CustomTextField.qml +SelectableText SelectableText.qml diff --git a/src/gui/qml/dialogs/CustomFileDialog.qml b/src/gui/qml/dialogs/CustomFileDialog.qml new file mode 100644 index 00000000..5a1d5a5f --- /dev/null +++ b/src/gui/qml/dialogs/CustomFileDialog.qml @@ -0,0 +1,32 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQuick +import QtQuick.Dialogs + +FileDialog { + id: fileDialog + + required property QtObject dialogModel + required property Settings settings + property string dialogId: "custom_file_dialog_id__" + dialogModel.dialog_id + + modality: dialogModel.modality + title: dialogModel.title + fileMode: dialogModel.file_mode + nameFilters: dialogModel.name_filters + onAccepted: { + settings.setValue(dialogId, fileDialog.currentFolder); + dialogModel.on_accepted(fileDialog.selectedFiles); + } + onRejected: dialogModel.on_rejected() + onCurrentFolderChanged: dialogModel.current_folder = fileDialog.currentFolder + + Connections { + target: fileDialog.dialogModel + + function onSelectFiles(): void { + fileDialog.currentFolder = fileDialog.settings.value(fileDialog.dialogId, fileDialog.dialogModel.current_folder); + fileDialog.open(); + } + } +} diff --git a/src/gui/qml/dialogs/CustomMessageDialog.qml b/src/gui/qml/dialogs/CustomMessageDialog.qml new file mode 100644 index 00000000..5168d522 --- /dev/null +++ b/src/gui/qml/dialogs/CustomMessageDialog.qml @@ -0,0 +1,38 @@ +import QtQml +import QtQuick.Controls +import QtQuick.Dialogs + +MessageDialog { + id: customMessageDialog + + required property QtObject dialogContentModel + + title: dialogContentModel?.title ?? "" + text: dialogContentModel?.text ?? "" + informativeText: dialogContentModel?.informative_text ?? "" + detailedText: dialogContentModel?.detailed_text ?? "" + buttons: MessageDialog.Cancel | MessageDialog.Ok + modality: Qt.NonModal + popupType: Popup.Window + + onButtonClicked: function (button: int, role: int): void { + switch (button) { + case MessageDialog.Ok: + dialogContentModel.ok(); + break; + case MessageDialog.Cancel: + dialogContentModel.cancel(); + break; + } + } + + Connections { + target: customMessageDialog.dialogContentModel + + function onDismissed() { + if (customMessageDialog.visible) { + customMessageDialog.reject(); + } + } + } +} diff --git a/src/gui/qml/dialogs/qmldir b/src/gui/qml/dialogs/qmldir new file mode 100644 index 00000000..dbacb617 --- /dev/null +++ b/src/gui/qml/dialogs/qmldir @@ -0,0 +1,3 @@ +module qml.dialogs +CustomFileDialog CustomFileDialog.qml +CustomMessageDialog CustomMessageDialog.qml \ No newline at end of file diff --git a/src/gui/qml/models/__init__.py b/src/gui/qml/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/gui/qml/models/base_dialog_model.py b/src/gui/qml/models/base_dialog_model.py new file mode 100644 index 00000000..0964bc0e --- /dev/null +++ b/src/gui/qml/models/base_dialog_model.py @@ -0,0 +1,34 @@ +from PySide6.QtCore import Property, QObject, Qt, Signal + + +class BaseDialogModel(QObject): + def __init__( + self, /, parent: QObject | None = None, *, objectName: str | None = None + ) -> None: + super().__init__(parent, objectName=objectName) + self._modality: Qt.WindowModality = Qt.WindowModality.WindowModal + self._title = "" + + _modality_changed = Signal() + + @Property(Qt.WindowModality, notify=_modality_changed) + def modality(self) -> Qt.WindowModality: # pyright: ignore[reportRedeclaration] + return self._modality + + @modality.setter + def modality(self, value: Qt.WindowModality) -> None: + if value != self._modality: + self._modality = value + self._modality_changed.emit() + + _title_changed = Signal() + + @Property(str, notify=_title_changed) + def title(self) -> str: # pyright: ignore[reportRedeclaration] + return self._title + + @title.setter + def title(self, value: str) -> None: + if value != self._title: + self._title = value + self._title_changed.emit() diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py new file mode 100644 index 00000000..4a11e4d6 --- /dev/null +++ b/src/gui/qml/models/batch_rendering_model.py @@ -0,0 +1,292 @@ +from asyncio import Task, create_task, ensure_future, gather, to_thread +from functools import cached_property +from logging import getLogger +from pathlib import Path +from typing import override +from urllib.request import url2pathname + +from pydantic import BaseModel, RootModel, ValidationError +from PySide6.QtCore import QModelIndex, QObject, QPersistentModelIndex, QUrl, Slot + +from src._loader import ( + AppPlugin, + AssembledTemplate, + AssembledTemplateInstalledArgs, + RenderableTemplate, + TemplateLibrary, +) +from src.enums.mtg import LayoutCategory +from src.gui.qml.models.file_dialog_model import FileDialogModel +from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel +from src.gui.qml.models.test_renders_model import TestRendersModel +from src.render.render_queue import RenderQueue, cancel_with_render +from src.render.setup import prepare_render_operations +from src.utils.images import match_images_with_data_files + +_logger = getLogger(__name__) + + +class TemplateOption(BaseModel): + name: str + plugin: str + is_installed: bool + preview_img_path: str + + @cached_property + def full_name(self) -> str: + return self.name + (f" ({self.plugin})" if self.plugin else "") + + +class LayoutCategoryItem(BaseModel): + name: str + options_details: list[TemplateOption] + options: list[str] + options_installed: list[bool] + options_preview_img_path: list[str] + selected: int + + +class _TemplateSelectionPreferences(RootModel[dict[str, str]]): + root: dict[str, str] = {} + + +class BatchRenderingModel(PydanticQListModel[LayoutCategoryItem]): + item_model = LayoutCategoryItem + + def __init__( + self, + file_dialog_model: FileDialogModel, + message_dialog_model: MessageDialogContentModel, + render_queue: RenderQueue, + plugins: dict[str, AppPlugin], + template_library: TemplateLibrary, + test_renders_model: TestRendersModel, + parent: QObject | None = None, + selected_index: int = -1, + ) -> None: + self._file_dialog_model = file_dialog_model + self._message_dialog_model = message_dialog_model + self._render_queue = render_queue + self._test_renders_model = test_renders_model + + self.built_in_templates_by_layout: dict[ + LayoutCategory, dict[str, AssembledTemplate] + ] = {} + self.plugin_templates_by_layout: dict[ + LayoutCategory, dict[str, dict[str, AssembledTemplate]] + ] = {} + """dict[layout, dict[plugin, dict[template_name, template]]]""" + + for layout_category in LayoutCategory: + self.built_in_templates_by_layout.setdefault(layout_category, {}) + self.plugin_templates_by_layout.setdefault( + layout_category, {plugin.id: {} for plugin in plugins.values()} + ) + + for name, template in template_library.built_in_templates_by_name.items(): + for layout_category in template.layout_categories: + self.built_in_templates_by_layout[layout_category][name] = template + + template.template_installed.add_listener(self._on_template_installed) + + for plugin, templates in template_library.plugin_templates_by_name.items(): + for name, template in templates.items(): + for layout_category in template.layout_categories: + self.plugin_templates_by_layout[layout_category].setdefault( + plugin, {} + ) + self.plugin_templates_by_layout[layout_category][plugin][name] = ( + template + ) + + template.template_installed.add_listener(self._on_template_installed) + + layout_cat_items: list[LayoutCategoryItem] = [] + for ( + layout_category, + template_options, + ) in self.built_in_templates_by_layout.items(): + opts = [ + TemplateOption( + name=opt_name, + plugin="", + is_installed=opt.is_installed(layout_category), + preview_img_path=path.as_uri() + if (path := opt.get_preview_image_path(layout_category)) + else "", + ) + for opt_name, opt in template_options.items() + ] + + for plugin, plugin_opts in self.plugin_templates_by_layout[ + layout_category + ].items(): + for opt_name, opt in plugin_opts.items(): + opts.append( + TemplateOption( + name=opt_name, + plugin=plugin, + is_installed=opt.is_installed(layout_category), + preview_img_path=path.as_uri() + if (path := opt.get_preview_image_path(layout_category)) + else "", + ) + ) + + layout_cat_item = LayoutCategoryItem( + name=layout_category, + selected=-1, + options_details=opts, + options=[opt.full_name for opt in opts], + options_installed=[opt.is_installed for opt in opts], + options_preview_img_path=[opt.preview_img_path for opt in opts], + ) + layout_cat_items.append(layout_cat_item) + + super().__init__(parent, layout_cat_items, selected_index) + + @override + def columnCount(self, parent: QModelIndex | QPersistentModelIndex) -> int: + return 2 + + @property + def template_choices(self) -> dict[LayoutCategory, RenderableTemplate]: + template_choices: dict[LayoutCategory, RenderableTemplate] = {} + for item in self.items: + if item.selected < 0: + continue + + selected_opt = item.options_details[item.selected] + + if not selected_opt.is_installed: + continue + + layout_category = LayoutCategory(item.name) + if selected_opt.plugin: + template_choices[layout_category] = self.plugin_templates_by_layout[ + layout_category + ][selected_opt.plugin][selected_opt.name] + else: + template_choices[layout_category] = self.built_in_templates_by_layout[ + layout_category + ][selected_opt.name] + return template_choices + + # layout, selected index + @Slot(int, int) + def select_template_for_layout( + self, layout_category_idx: int, selected_index: int + ) -> None: + item = self.items[layout_category_idx] + item.selected = selected_index + q_idx = self.createIndex(layout_category_idx, 0) + self.dataChanged.emit(q_idx, q_idx, [self.get_role("selected")]) + + def render_files(self, paths: list[Path]) -> None: + if paths: + _logger.info( + f"Queueing { + len(paths) + } batch mode entries for render. Do note that the actual amount of renders might be lower if you selected JSON files or art for split cards." + ) + if render_operations := prepare_render_operations( + self.template_choices, + match_images_with_data_files(paths), + file_dialog=self._file_dialog_model, + message_dialog=self._message_dialog_model, + ): + self._render_queue.enqueue(*render_operations) + + @Slot() + def render_selections(self) -> None: + async def action(): + # Choose images + selections = await self._file_dialog_model.select_images( + dialog_id="batch_mode_render", + filters=[ + FileDialogModel.IMAGES_AND_JSON_FILTER, + FileDialogModel.ALL_FILTER, + ], + ) + + self.render_files(selections) + + cancel_with_render(ensure_future(action()), self._render_queue) + + @Slot("QVariantList") + def render_targets(self, urls: list[QUrl]) -> None: + paths: list[Path] | None = None + try: + paths = [Path(url2pathname(url.path())) for url in urls] + except Exception: + _logger.exception("Failed to process drag & dropped files.") + if paths: + cancel_with_render( + ensure_future(to_thread(self.render_files, paths)), self._render_queue + ) + + @Slot(str, bool) + def test_render(self, layout: str | None = None, quick: bool = False) -> None: + async def action() -> None: + layout_category = LayoutCategory(layout) if layout else None + + preparation_routines: list[Task[None]] = [] + for category, template in self.template_choices.items(): + if layout_category and category != layout_category: + continue + + preparation_routines.append( + create_task( + self._test_renders_model.test_render(template, category, quick) + ) + ) + + if layout_category: + break + + await gather(*preparation_routines) + + cancel_with_render(ensure_future(action()), self._render_queue) + + @Slot(result=str) + def get_template_selections_json(self) -> str: + selections = _TemplateSelectionPreferences() + for item in self.items: + if item.selected > -1: + selections.root[item.name] = item.options[item.selected] + return selections.model_dump_json() if selections.root else "" + + @Slot(str) + def restore_template_selections(self, data: str) -> None: + try: + parsed_data = _TemplateSelectionPreferences.model_validate_json(data).root + for item_idx, item in enumerate(self.items): + if selection := parsed_data.get(item.name): + for idx, opt in enumerate(item.options): + if opt == selection: + item.selected = idx + q_idx = self.index(item_idx, 0) + self.dataChanged.emit(q_idx, q_idx, ["selected"]) + except ValidationError: + _logger.exception("Failed to restore batch model selection preferences.") + + @Slot(result="QVariantList") + def layout_names(self) -> list[str]: + return [item.name for item in self.items] + + def _on_template_installed( + self, event_args: AssembledTemplateInstalledArgs + ) -> None: + origin = event_args["origin"] + for idx, item in enumerate(self.items): + for opt_idx, opt in enumerate(item.options_details): + if ( + opt.plugin if origin.plugin else not opt.plugin + ) and opt.name in origin.named_templates: + opt.is_installed = True + item.options_installed[opt_idx] = True + q_idx = self.createIndex(idx, 0) + self.dataChanged.emit( + q_idx, q_idx, [self.get_role("options_installed")] + ) diff --git a/src/gui/qml/models/console_model.py b/src/gui/qml/models/console_model.py new file mode 100644 index 00000000..39abe7ef --- /dev/null +++ b/src/gui/qml/models/console_model.py @@ -0,0 +1,71 @@ +from logging import DEBUG, Formatter, LogRecord, getLogger +from re import Match, compile + +from pydantic import BaseModel +from PySide6.QtCore import QModelIndex, QObject, Slot +from PySide6.QtGui import QGuiApplication + +from src.console import ( + DEFAULT_LOG_DATE_FORMAT, + DEFAULT_LOG_FORMAT, + LOG_MESSAGE_COLORS_MAP, + CustomLogHandler, + MessageSeverity, +) +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel + +_logger = getLogger() + + +def _colorize_severity(match: Match[str], color: str) -> str: + return f'{match.group(1)}{match.group(2)}' + + +class _ColorizingFormatter(Formatter): + _severity_regex = compile(r"(\[[^\[\]]+\])(\[[^\s\[\]]+\])") + + def format(self, record: LogRecord) -> str: + return "
".join( + self._severity_regex.sub( + lambda match: _colorize_severity( + match, LOG_MESSAGE_COLORS_MAP[record.levelno] + ), + super().format(record), + 1, + ).splitlines() + ) + + +class LogEntry(BaseModel): + message: str + severity: int + + +class ConsoleModel(PydanticQListModel[LogEntry]): + item_model = LogEntry + + def __init__( + self, + parent: QObject | None = None, + items: list[LogEntry] = [], + selected_index: int = 0, + ) -> None: + log_handler = CustomLogHandler(self._add_to_log, level=DEBUG) + log_handler.setFormatter( + _ColorizingFormatter( + fmt=DEFAULT_LOG_FORMAT, datefmt=DEFAULT_LOG_DATE_FORMAT + ) + ) + _logger.addHandler(log_handler) + super().__init__(parent, items, selected_index) + + def _add_to_log(self, message: str, severity: MessageSeverity) -> None: + row_count = self.rowCount() + self.beginInsertRows(QModelIndex(), row_count, row_count) + self.items.append(LogEntry(message=message, severity=severity)) + self.endInsertRows() + + @Slot() + def copy_log(self) -> None: + clipboard = QGuiApplication.clipboard() + clipboard.setText("\n".join([item.message for item in self.items])) diff --git a/src/gui/qml/models/file_dialog_model.py b/src/gui/qml/models/file_dialog_model.py new file mode 100644 index 00000000..94151609 --- /dev/null +++ b/src/gui/qml/models/file_dialog_model.py @@ -0,0 +1,150 @@ +from asyncio import Future, get_running_loop +from enum import IntEnum +from os import PathLike +from pathlib import Path + +from PySide6.QtCore import Property, QObject, Qt, QUrl, Signal, Slot + +from src._state import PATH +from src.gui.qml.models.base_dialog_model import BaseDialogModel + + +class FileMode(IntEnum): + OpenFile = 0 + OpenFiles = 1 + SaveFile = 2 + + +class FileDialogModel(BaseDialogModel): + ALL_FILTER = "All (*)" + PSD_FILTER = "PSD (*.psd)" + IMAGES_FILTER = "Images (*.png *.jpg *.jpeg *.jxl *.avif *.webp)" + IMAGES_AND_JSON_FILTER = ( + "Images and data (*.png *.jpg *.jpeg *.jxl *.avif *.webp *.json)" + ) + + def __init__( + self, parent: QObject | None = None, *, objectName: str | None = None + ) -> None: + super().__init__(parent, objectName=objectName) + setattr(type(self), "instance", self) + self._response_future: Future[list[QUrl]] | None = None + self._current_folder: QUrl = QUrl.fromLocalFile(str(PATH.CWD)) + self._file_mode = FileMode.OpenFile + self._name_filters = [] + self._dialog_id = "default" + + # region Properties + + _current_folder_changed = Signal() + + @Property(QUrl, notify=_current_folder_changed) + def current_folder(self) -> QUrl: # pyright: ignore[reportRedeclaration] + return self._current_folder + + @current_folder.setter + def current_folder(self, value: QUrl) -> None: + if value != self._current_folder: + self._current_folder = value + self._current_folder_changed.emit() + + _file_mode_changed = Signal() + + @Property(int, notify=_file_mode_changed) + def file_mode(self) -> FileMode: # pyright: ignore[reportRedeclaration] + return self._file_mode + + @file_mode.setter + def file_mode(self, value: FileMode) -> None: + if value != self._file_mode: + self._file_mode = value + self._file_mode_changed.emit() + + _name_filters_changed = Signal() + + @Property("QVariantList", notify=_name_filters_changed) + def name_filters(self) -> list[str]: # pyright: ignore[reportRedeclaration] + return self._name_filters + + @name_filters.setter + def name_filters(self, value: list[str]) -> None: + if value != self._name_filters: + self._name_filters = value + self._name_filters_changed.emit() + + _dialog_id_changed = Signal() + + @Property(str, notify=_dialog_id_changed) + def dialog_id(self) -> str: # pyright: ignore[reportRedeclaration] + return self._dialog_id + + @dialog_id.setter + def dialog_id(self, value: str) -> None: + if value != self._dialog_id: + self._dialog_id = value + self._dialog_id_changed.emit() + + # endregion Properties + + # region Events + + @Slot("QVariantList") + def on_accepted(self, files: list[QUrl]) -> None: + if self._response_future: + self._response_future.get_loop().call_soon_threadsafe( + self._response_future.set_result, files + ) + + @Slot() + def on_rejected(self) -> None: + if self._response_future: + arg: list[QUrl] = [] + self._response_future.get_loop().call_soon_threadsafe( + self._response_future.set_result, arg + ) + + # endregion Events + + _select_files_called = Signal(name="selectFiles") + + async def select_files( + self, + title: str = "", + initial_dir: str | PathLike[str] = PATH.CWD, + file_mode: FileMode = FileMode.OpenFiles, + filters: list[str] = [], + modality: Qt.WindowModality = Qt.WindowModality.WindowModal, + dialog_id: str = "default", + ) -> list[QUrl]: + if self._response_future: + await self._response_future + + self.title = title # pyright: ignore[reportAttributeAccessIssue] + self.current_folder = QUrl.fromLocalFile(initial_dir) # pyright: ignore[reportAttributeAccessIssue] + self.file_mode = file_mode # pyright: ignore[reportAttributeAccessIssue] + self.name_filters = filters # pyright: ignore[reportAttributeAccessIssue] + self.modality = modality # pyright: ignore[reportAttributeAccessIssue] + self.dialog_id = dialog_id # pyright: ignore[reportAttributeAccessIssue] + + loop = get_running_loop() + self._response_future = loop.create_future() + self._select_files_called.emit() + return await self._response_future + + async def select_images( + self, + title: str = "Select images", + initial_dir: str | PathLike[str] = PATH.CWD, + filters: list[str] = [IMAGES_FILTER, ALL_FILTER], + dialog_id: str = "default", + ) -> list[Path]: + return [ + Path(path.toLocalFile()) + for path in await self.select_files( + title=title, + initial_dir=initial_dir, + file_mode=FileMode.OpenFiles, + filters=filters, + dialog_id=dialog_id, + ) + ] diff --git a/src/gui/qml/models/file_path_model.py b/src/gui/qml/models/file_path_model.py new file mode 100644 index 00000000..de571f32 --- /dev/null +++ b/src/gui/qml/models/file_path_model.py @@ -0,0 +1,31 @@ +from PySide6.QtCore import Property, QObject, QUrl, Signal, Slot + +from src._state import PATH + + +class FilePathModel(QObject): + @Property(QUrl) + def app_root(self) -> QUrl: + return QUrl.fromLocalFile(PATH.CWD) + + @Property(QUrl) + def out_directory(self) -> QUrl: + return QUrl.fromLocalFile(PATH.OUT) + + @Property(QUrl) + def templates_directory(self) -> QUrl: + return QUrl.fromLocalFile(PATH.TEMPLATES) + + @Property(QUrl) + def plugins_directory(self) -> QUrl: + return QUrl.fromLocalFile(PATH.PLUGINS) + + _preview_img_fallback_signal = Signal() + + @Property(str, notify=_preview_img_fallback_signal) + def preview_img_fallback(self) -> str: + return PATH.SRC_IMG_NOTFOUND.as_uri() + + @Slot(str, result=str) + def get_preferences_path(self, file_name: str) -> str: + return (PATH.SRC_DATA_PREFERENCES / file_name).as_uri() diff --git a/src/gui/qml/models/image_transform_model.py b/src/gui/qml/models/image_transform_model.py new file mode 100644 index 00000000..dee7dff4 --- /dev/null +++ b/src/gui/qml/models/image_transform_model.py @@ -0,0 +1,124 @@ +from asyncio import ensure_future, gather, to_thread +from concurrent.futures import ThreadPoolExecutor +from logging import getLogger +from os import process_cpu_count +from pathlib import Path + +from PySide6.QtCore import Property, QObject, Signal, Slot + +from src.gui.qml.models.file_dialog_model import FileDialogModel +from src.utils.images import IMAGE_ENCODING_TO_SUFFIX_MAPPING, save_scaled_card_image + +_logger = getLogger(__name__) + + +class ImageTransformModel(QObject): + def __init__( + self, + /, + parent: QObject | None = None, + *, + file_dialog_model: FileDialogModel, + objectName: str | None = None, + ) -> None: + super().__init__(parent, objectName=objectName) + self._file_dialog_model = file_dialog_model + + self._thread_pool_executor: ThreadPoolExecutor | None = None + self._image_file_formats: list[str] = ["PNG", "JPEG", "WebP"] + + self._downscale = True + self._downscale_width = 2176 + self._image_file_format = self._image_file_formats[1] + self._encoding_quality = 95 + + @property + def _thread_pool(self) -> ThreadPoolExecutor: + if not self._thread_pool_executor: + self._thread_pool_executor = ThreadPoolExecutor( + max_workers=(process_cpu_count() or 5) - 1 + ) + return self._thread_pool_executor + + _image_file_formats_changed = Signal() + + @Property("QVariantList", notify=_image_file_formats_changed) + def image_file_formats(self) -> list[str]: # pyright: ignore[reportRedeclaration] + return self._image_file_formats + + _downscale_changed = Signal() + + @Property(bool, notify=_downscale_changed) + def downscale(self) -> bool: # pyright: ignore[reportRedeclaration] + return self._downscale + + @downscale.setter + def downscale(self, value: bool) -> None: + if value != self._downscale: + self._downscale = value + self._downscale_changed.emit() + + _downscale_width_changed = Signal() + + @Property(int, notify=_downscale_width_changed) + def downscale_width(self) -> int: # pyright: ignore[reportRedeclaration] + return self._downscale_width + + @downscale_width.setter + def downscale_width(self, value: int) -> None: + if value != self._downscale_width: + self._downscale_width = value + self._downscale_width_changed.emit() + + _image_file_format_changed = Signal() + + @Property(str, notify=_image_file_format_changed) + def image_file_format(self) -> str: # pyright: ignore[reportRedeclaration] + return self._image_file_format + + @image_file_format.setter + def image_file_format(self, value: str) -> None: + if value != self._image_file_format: + self._image_file_format = value + self._image_file_format_changed.emit() + + _encoding_quality_changed = Signal() + + @Property(int, notify=_encoding_quality_changed) + def encoding_quality(self) -> int: # pyright: ignore[reportRedeclaration] + return self._encoding_quality + + @encoding_quality.setter + def encoding_quality(self, value: int) -> None: + if value != self._encoding_quality: + self._encoding_quality = value + self._encoding_quality_changed.emit() + + @Slot() + def transform_images(self) -> None: + async def action(): + images = await self._file_dialog_model.select_images(dialog_id="image_transform") + await gather(*[self.transform_image(image) for image in images]) + + ensure_future(action()) + + async def transform_image(self, image: Path) -> None: + try: + out_path = ( + image.parent + / "compressed" + / image.with_suffix( + IMAGE_ENCODING_TO_SUFFIX_MAPPING[self._image_file_format] + ).name + ) + await to_thread( + save_scaled_card_image, + image, + out_path, + self._image_file_format, + self._downscale_width if self._downscale else None, + self._encoding_quality, + ) + _logger.info(f"Saved transformed version of {image} to {out_path}") + except Exception: + _logger.exception(f"Failed to transform {image}") diff --git a/src/gui/qml/models/message_dialog_content_model.py b/src/gui/qml/models/message_dialog_content_model.py new file mode 100644 index 00000000..be1c1fa5 --- /dev/null +++ b/src/gui/qml/models/message_dialog_content_model.py @@ -0,0 +1,132 @@ +from asyncio import Future, get_running_loop +from collections.abc import Callable +from typing import Any + +from PySide6.QtCore import Property, QObject, Signal, Slot + + +class MessageDialogContentModel(QObject): + def __init__( + self, + /, + title: str = "", + text: str = "", + informative_text: str = "", + detailed_text: str = "", + ok_callback: Callable[[], Any] | None = None, + cancel_callback: Callable[[], Any] | None = None, + parent: QObject | None = None, + *, + objectName: str | None = None, + ) -> None: + super().__init__(parent, objectName=objectName) + self._title = title + self._text = text + self._informative_text = informative_text + self._detailed_text = detailed_text + self.ok_callback = ok_callback + self.cancel_callback = cancel_callback + + _title_changed = Signal() + + @Property(str, notify=_title_changed) + def title(self) -> str: # pyright: ignore[reportRedeclaration] + return self._title + + @title.setter + def title(self, value: str) -> None: + if value != self._title: + self._title = value + self._title_changed.emit() + + _text_changed = Signal() + + @Property(str, notify=_text_changed) + def text(self) -> str: # pyright: ignore[reportRedeclaration] + return self._text + + @text.setter + def text(self, value: str) -> None: + if value != self._text: + self._text = value + self._text_changed.emit() + + _informative_text_changed = Signal() + + @Property(str, notify=_informative_text_changed) + def informative_text(self) -> str: # pyright: ignore[reportRedeclaration] + return self._informative_text + + @informative_text.setter + def informative_text(self, value: str) -> None: + if value != self._informative_text: + self._informative_text = value + self._informative_text_changed.emit() + + _detailed_text_changed = Signal() + + @Property(str, notify=_detailed_text_changed) + def detailed_text(self) -> str: # pyright: ignore[reportRedeclaration] + return self._detailed_text + + @detailed_text.setter + def detailed_text(self, value: str) -> None: + if value != self._detailed_text: + self._detailed_text = value + self._detailed_text_changed.emit() + + @Slot() + def ok(self) -> None: + if self.ok_callback: + self.ok_callback() + + @Slot() + def cancel(self) -> None: + if self.cancel_callback: + self.cancel_callback() + + _dismissed = Signal(name="dismissed") + + def dismiss(self) -> None: + self._dismissed.emit() + + dialog_requested = Signal(name="dialogRequested") + + def open_message_dialog( + self, + title: str = "", + text: str = "", + informative_text: str = "", + detailed_text: str = "", + ok_callback: Callable[[], Any] | None = None, + cancel_callback: Callable[[], Any] | None = None, + ) -> None: + self.title = title # pyright: ignore[reportAttributeAccessIssue] + self.text = text # pyright: ignore[reportAttributeAccessIssue] + self.informative_text = informative_text # pyright: ignore[reportAttributeAccessIssue] + self.detailed_text = detailed_text # pyright: ignore[reportAttributeAccessIssue] + self.ok_callback = ok_callback + self.cancel_callback = cancel_callback + self.dialog_requested.emit() + + async def open_message_dialog_async( + self, + title: str = "", + text: str = "", + informative_text: str = "", + detailed_text: str = "", + ) -> bool: + future: Future[bool] = get_running_loop().create_future() + self.open_message_dialog( + title=title, + text=text, + informative_text=informative_text, + detailed_text=detailed_text, + ok_callback=lambda: future.get_loop().call_soon_threadsafe( + future.set_result, True + ), + cancel_callback=lambda: future.get_loop().call_soon_threadsafe( + future.set_result, False + ), + ) + return await future diff --git a/src/gui/qml/models/pydantic_q_list_model.py b/src/gui/qml/models/pydantic_q_list_model.py new file mode 100644 index 00000000..9d684507 --- /dev/null +++ b/src/gui/qml/models/pydantic_q_list_model.py @@ -0,0 +1,240 @@ +from typing import Any, override + +from pydantic import BaseModel +from PySide6.QtCore import ( + Property, + QAbstractItemModel, + QAbstractListModel, + QByteArray, + QModelIndex, + QObject, + QPersistentModelIndex, + Qt, + Signal, +) + + +class TreeItem[T: BaseModel]: + def __init__(self, data: T, parent: TreeItem[T] | None = None) -> None: + self.children: list[TreeItem[T]] = [] + self.data = data + self.parent: TreeItem[T] | None = None + if parent: + parent.append_child(self) + + def append_child(self, child: TreeItem[T]) -> None: + self.children.append(child) + child.parent = self + + def child(self, row: int) -> TreeItem[T] | None: + return self.children[row] if -1 < row < self.child_count() else None + + def child_count(self) -> int: + return len(self.children) + + def column_count(self) -> int: + return 1 + + def row(self) -> int: + return self.parent.children.index(self) if self.parent else -1 + + +class PydanticQItemModelBase[T: BaseModel]: + item_model: type[T] + _role_names: dict[int, QByteArray] + _roles_reverse: dict[str, int] + + def roleNames(self) -> dict[int, QByteArray]: + return self._role_names + + def get_role(self, property_name: str) -> int: + return self._roles_reverse[property_name] + + +class PydanticQItemModel[T: BaseModel](PydanticQItemModelBase[T], QAbstractItemModel): + """ + Item model that exposes Pydantic model's fields as roles to QML. + + Inheriting models have to set the Pydantic model's type to `item_model` field. + """ + + _root: TreeItem[T] + + def __init__(self, /, parent: QObject | None = None) -> None: + self._roles: dict[int, str] = { + Qt.ItemDataRole.UserRole + 1 + idx: field + for idx, field in enumerate(self.item_model.model_fields) + } + self._roles_reverse: dict[str, int] = { + field: idx for idx, field in self._roles.items() + } + self._role_names: dict[int, QByteArray] = { + role: QByteArray(field.encode("utf-8")) + for role, field in self._roles.items() + } + + super().__init__(parent) + + @override + def rowCount( + self, parent: QModelIndex | QPersistentModelIndex = QModelIndex() + ) -> int: + if parent.column() > 0: + return 0 + + parent_item: TreeItem[T] = ( + parent.internalPointer() if parent.isValid() else self._root + ) + + return parent_item.child_count() + + @override + def columnCount( + self, parent: QModelIndex | QPersistentModelIndex = QModelIndex() + ) -> int: + if parent.isValid(): + parent_item: TreeItem[T] = parent.internalPointer() + return parent_item.column_count() + return self._root.column_count() + + @override + def data( + self, + index: QModelIndex | QPersistentModelIndex, + role: int = Qt.ItemDataRole.DisplayRole, + ) -> Any: + if not index.isValid() or role not in self._roles: + return None + tree_item: TreeItem[T] = index.internalPointer() + return getattr(tree_item.data, self._roles[role]) + + @override + def index( + self, + row: int, + column: int, + parent: QModelIndex | QPersistentModelIndex = QModelIndex(), + ) -> QModelIndex: + if not self.hasIndex(row, column, parent=parent): + return QModelIndex() + + parent_item: TreeItem[T] = ( + parent.internalPointer() if parent.isValid() else self._root + ) + + if child_item := parent_item.child(row): + return self.createIndex(row, column, child_item) + + return QModelIndex() + + @override + def parent(self, index: QModelIndex = QModelIndex()) -> QModelIndex: # pyright: ignore[reportIncompatibleMethodOverride] + if not index.isValid(): + return QModelIndex() + + child_item: TreeItem[T] = index.internalPointer() + parent_item = child_item.parent + + if parent_item and parent_item != self._root: + return self.createIndex(parent_item.row(), 0, parent_item) + + return QModelIndex() + + def index_of_item(self, item: TreeItem[T]) -> QModelIndex: + return self.createIndex(item.row(), 0, item) + + # region Properties + + selected_model_index_changed = Signal(QModelIndex, name="selectedModelIndexChanged") + + @Property(QModelIndex, notify=selected_model_index_changed) + def selected_model_index(self) -> QModelIndex: # pyright: ignore[reportRedeclaration] + return self._selected_model_index + + def _set_selected_model_index(self, value: QModelIndex) -> None: + item: TreeItem[T] | None = value.internalPointer() + if not value.isValid() or not item: + self._selected_model_index = value + self.selected_model_index_changed.emit(value) + self.selected_title = "" # pyright: ignore[reportAttributeAccessIssue] + return None + + if value != self._selected_model_index: + self._selected_model_index = value + self.selected_model_index_changed.emit(value) + + @selected_model_index.setter + def selected_model_index(self, value: QModelIndex) -> None: + self._set_selected_model_index(value) + + # endregion Properties + + +class PydanticQListModel[T: BaseModel](PydanticQItemModelBase[T], QAbstractListModel): + """ + List model that exposes Pydantic model's fields as roles to QML. + + Inheriting models have to set the Pydantic model's type to `item_model` field. + """ + + def __init__( + self, + parent: QObject | None = None, + items: list[T] = [], + selected_index: int = -1, + ) -> None: + self._roles: dict[int, str] = { + Qt.ItemDataRole.UserRole + 1 + idx: field + for idx, field in enumerate(self.item_model.model_fields) + } + self._roles_reverse: dict[str, int] = { + field: idx for idx, field in self._roles.items() + } + self._role_names: dict[int, QByteArray] = { + role: QByteArray(field.encode("utf-8")) + for role, field in self._roles.items() + } + + self.all_items: list[T] = items + self.items: list[T] = items.copy() + self._selected_index = selected_index + + super().__init__(parent) + + # region Signals + + _selected_index_changed = Signal() + + # endregion Signals + + # region Properties + + @Property(int, notify=_selected_index_changed, final=True) + def selected_index(self) -> int: # pyright: ignore[reportRedeclaration] + return self._selected_index + + @selected_index.setter + def selected_index(self, value: int) -> None: + if value != self._selected_index: + self._selected_index = value + self._selected_index_changed.emit() + + # endregion Properties + + @override + def data( + self, + index: QModelIndex | QPersistentModelIndex, + role: int = Qt.ItemDataRole.DisplayRole, + ) -> Any: + if not index.isValid() or role not in self._roles: + return + + item = self.items[index.row()] + return getattr(item, self._roles[role]) + + @override + def rowCount( + self, parent: QModelIndex | QPersistentModelIndex = QModelIndex() + ) -> int: + return len(self.items) diff --git a/src/gui/qml/models/render_operations_model.py b/src/gui/qml/models/render_operations_model.py new file mode 100644 index 00000000..75f11b7a --- /dev/null +++ b/src/gui/qml/models/render_operations_model.py @@ -0,0 +1,205 @@ +from functools import cached_property + +from pydantic import BaseModel +from PySide6.QtCore import Property, QModelIndex, QObject, Signal, Slot + +from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel +from src.render.render_queue import RenderQueue, RenderResult +from src.render.setup import PausedEventArgs, RenderOperation + + +class RenderOperationDetails(BaseModel): + image_name: str + image_path: str + card_name: str + card_artist: str + card_set: str + card_collector_number: str + layout_name: str + class_name: str + + @cached_property + def render_operation(self) -> RenderOperation: + raise ValueError("Render operation not set") + + +class RenderOperationsModel(PydanticQListModel[RenderOperationDetails]): + item_model = RenderOperationDetails + + def __init__( + self, + render_queue: RenderQueue, + render_message_dialog_model: MessageDialogContentModel, + parent: QObject | None = None, + items: list[RenderOperationDetails] = [], + selected_index: int = -1, + ) -> None: + super().__init__(parent, items, selected_index) + + self._render_queue = render_queue + self._render_message_dialog_model = render_message_dialog_model + self._is_rendering: bool = False + self._active_operation: RenderOperationDetails | None = None + + render_queue.queued.add_listener(self._on_queued) + render_queue.dequeued.add_listener(self._on_dequeued) + render_queue.started.add_listener(self._on_started) + render_queue.finished.add_listener(self._on_finished) + + # region Queue + + def _on_queued(self, render_operations: tuple[RenderOperation, ...]) -> None: + render_opeartion_details: list[RenderOperationDetails] = [] + for render_operation in render_operations: + render_op_details = RenderOperationDetails( + image_name=render_operation.layout.art_file.name, + image_path=str(render_operation.layout.art_file), + card_name=render_operation.layout.name, + card_artist=render_operation.layout.artist, + card_set=render_operation.layout.set, + card_collector_number=render_operation.layout.collector_number_raw + or "", + layout_name=render_operation.layout.category, + class_name=render_operation.template_class.__name__, + ) + render_op_details.render_operation = render_operation + render_opeartion_details.append(render_op_details) + row_count = self.rowCount() + self.beginInsertRows( + QModelIndex(), row_count, row_count + len(render_operations) - 1 + ) + self.items.extend(render_opeartion_details) + self.endInsertRows() + + def _on_dequeued(self, data: tuple[RenderOperation, int]) -> None: + index = data[1] + self.beginRemoveRows(QModelIndex(), index, index) + self.items.pop(index) + self.endRemoveRows() + + def _on_started(self, render_operation: RenderOperation) -> None: + render_operation.paused.add_listener(self._on_render_paused) + self.beginRemoveRows(QModelIndex(), 0, 0) + self.active_operation = self.items.pop(0) + self.endRemoveRows() + self.is_rendering = True # pyright: ignore[reportAttributeAccessIssue] + + def _on_finished(self, render_result: RenderResult) -> None: + render_result["operation"].paused.remove_listener(self._on_render_paused) + self.is_rendering = False # pyright: ignore[reportAttributeAccessIssue] + self.active_operation = None + + @Slot(int) + def dequeue(self, index: int) -> None: + self._render_queue.dequeue(index) + + @Slot() + def clear(self) -> None: + self._render_queue.clear() + + # endregion Queue + + # region Properties + + _is_rendering_changed = Signal() + + @Property(bool, notify=_is_rendering_changed) + def is_rendering(self) -> bool: # pyright: ignore[reportRedeclaration] + return self._is_rendering + + @is_rendering.setter + def is_rendering(self, value: bool) -> None: + if value != self._is_rendering: + self._is_rendering = value + self._is_rendering_changed.emit() + + _active_operation_changed = Signal() + + @property + def active_operation(self) -> RenderOperationDetails | None: + return self._active_operation + + @active_operation.setter + def active_operation(self, value: RenderOperationDetails | None) -> None: + if self._active_operation != value: + self._active_operation = value + self._active_operation_changed.emit() + + @Property(str, notify=_active_operation_changed) + def rendering_image_name(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.image_name + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_image_path(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.image_path + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_card_name(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.card_name + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_card_artist(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.card_artist + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_card_set(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.card_set + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_card_collector_number(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.card_collector_number + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_layout_name(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.layout_name + return "" + + @Property(str, notify=_active_operation_changed) + def rendering_class_name(self) -> str: # pyright: ignore[reportRedeclaration] + if self._active_operation: + return self._active_operation.class_name + return "" + + # endregion Properties + + # region Pause dialog + + def _cancel_queue(self) -> None: + self._render_queue.cancel() + + @Slot() + def cancel(self) -> None: + self._render_message_dialog_model.dismiss() + self._cancel_queue() + + @Slot() + def resume(self) -> None: + if op := self._render_queue.active_operation: + op.resume() + else: + self._render_queue.execute_render() + + def _on_render_paused(self, args: PausedEventArgs) -> None: + self._render_message_dialog_model.open_message_dialog( + "Rendering paused", + text=args["message"] or "Rendering paused.", + informative_text="Press OK to resume rendering or Cancel to discard the current render.", + ok_callback=self.resume, + cancel_callback=self._cancel_queue, + ) + + # endregion Pause dialog diff --git a/src/gui/qml/models/settings_model.py b/src/gui/qml/models/settings_model.py new file mode 100644 index 00000000..2eb53529 --- /dev/null +++ b/src/gui/qml/models/settings_model.py @@ -0,0 +1,207 @@ +from functools import cached_property +from typing import Any, Literal, override + +from pydantic import BaseModel +from PySide6.QtCore import ( + Property, + QModelIndex, + QObject, + QPersistentModelIndex, + Qt, + Signal, + Slot, +) + +from src._loader import ( + BoolSetting, + ConfigHandler, + FloatSetting, + IntSetting, + NumericSetting, + SectionTitle, + StringSetting, +) +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel +from src.gui.qml.models.settings_tree_model import SettingsTreeModel + + +class HybridSettingItem(BaseModel): + type: Literal["title", "bool", "string", "numeric", "options", "int", "float"] + title: str + desc: str | None = None + value: bool | str | int | float | None = None + default_value: bool | str | int | float | None = None + options: list[str] | None = None + + @cached_property + def section(self) -> str: + return "" + + @cached_property + def key(self) -> str: + return "" + + +class SettingsModel(PydanticQListModel[HybridSettingItem]): + item_model = HybridSettingItem + + def __init__( + self, + settings_tree_model: SettingsTreeModel, + parent: QObject | None = None, + items: list[HybridSettingItem] = [], + selected_index: int = -1, + ) -> None: + super().__init__(parent, items, selected_index) + self._current_config_handler: ConfigHandler | None = None + self.settings_tree_model = settings_tree_model + settings_tree_model.selected_model_index_changed.connect( + self.on_settings_section_change + ) + + @property + def current_config_handler(self) -> ConfigHandler | None: + return self._current_config_handler + + @current_config_handler.setter + def current_config_handler(self, value: ConfigHandler | None) -> None: + if self._current_config_handler: + self._current_config_handler.config_deleted.remove_listener(self._on_clear) + self._current_config_handler.config_reset.remove_listener(self._on_reset) + + self._current_config_handler = value + if value: + if not value.has_config: + value.save(force=True) + value.config_deleted.add_listener(self._on_clear) + value.config_reset.add_listener(self._on_reset) + self._valid_changed.emit() + + @cached_property + def _value_role(self) -> int: + for role, name in self._roles.items(): + if name == "value": + return role + raise ValueError("Value role doesn't exist") + + @override + def setData( + self, + index: QModelIndex | QPersistentModelIndex, + value: Any, + /, + role: int = Qt.ItemDataRole.EditRole, + ) -> bool: + if index.isValid() or role in self._roles and self._roles[role] == "value": + item = self.items[index.row()] + if self._current_config_handler and self._current_config_handler.set_value( + item.section, item.key, value + ): + item.value = value + self.dataChanged.emit(index, index, [role]) + return True + return False + + def populate_settings(self, config_handler: ConfigHandler) -> None: + new_items: list[HybridSettingItem] = [] + schemas = ( + (config_handler.base_schema.root, config_handler.schema.root) + if config_handler.schema + else (config_handler.base_schema.root,) + ) + for schema in schemas: + for section in schema: + if isinstance(section, SectionTitle): + new_item = HybridSettingItem(type=section.type, title=section.title) + elif isinstance( + section, + ( + BoolSetting, + StringSetting, + NumericSetting, + FloatSetting, + IntSetting, + ), + ): + new_item = HybridSettingItem( + type=section.type, + title=section.title, + desc=section.desc, + value=config_handler.setting_values[section.section][ + section.key + ], + default_value=section.default, + ) + new_item.section = section.section + new_item.key = section.key + else: + new_item = HybridSettingItem( + type=section.type, + title=section.title, + desc=section.desc, + value=config_handler.setting_values[section.section][ + section.key + ], + default_value=section.default, + options=section.options, + ) + new_item.section = section.section + new_item.key = section.key + new_items.append(new_item) + self.beginResetModel() + self.items = new_items + self.current_config_handler = config_handler + self.endResetModel() + + @Slot(QModelIndex) + def on_settings_section_change(self, value: QModelIndex) -> None: + if selected := self.settings_tree_model.selected_section: + self.populate_settings(selected) + else: + self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1) + self.items = [] + self.current_config_handler = None + self.endRemoveRows() + + def _set_value(self, index: int, value: bool | str | int | float) -> None: + self.setData(self.createIndex(index, 0), value) + + @Slot(int, bool) + def bool_value_changed(self, index: int, value: bool) -> None: + self._set_value(index, value) + + @Slot(int, str) + def str_value_changed(self, index: int, value: str) -> None: + self._set_value(index, value) + + @Slot(int, int) + def int_value_changed(self, index: int, value: int) -> None: + self._set_value(index, value) + + @Slot(int, float) + def float_value_changed(self, index: int, value: float) -> None: + self._set_value(index, value) + + @Slot() + def reset(self) -> None: + if self._current_config_handler: + self._current_config_handler.reset() + + def _on_reset(self, conf_handler: ConfigHandler) -> None: + if self._current_config_handler: + self.populate_settings(self._current_config_handler) + + @Slot() + def clear(self) -> None: + if self._current_config_handler: + self._current_config_handler.delete() + + def _on_clear(self, conf_handler: ConfigHandler) -> None: + if self._current_config_handler: + self.settings_tree_model.selected_model_index = QModelIndex() # pyright: ignore[reportAttributeAccessIssue] + + _valid_changed = Signal() + + @Property(bool, notify=_valid_changed) + def valid(self) -> bool: # pyright: ignore[reportRedeclaration] + return self._current_config_handler is not None diff --git a/src/gui/qml/models/settings_tree_model.py b/src/gui/qml/models/settings_tree_model.py new file mode 100644 index 00000000..ffec4814 --- /dev/null +++ b/src/gui/qml/models/settings_tree_model.py @@ -0,0 +1,220 @@ +from collections.abc import Iterable +from logging import getLogger +from typing import override + +from pydantic import BaseModel +from PySide6.QtCore import ( + Property, + QModelIndex, + QObject, + Signal, + Slot, +) + +from src._config import AppConfig +from src._loader import AssembledTemplate, ConfigHandler, TemplateLibrary +from src.gui.qml.models.pydantic_q_list_model import PydanticQItemModel, TreeItem + +_logger = getLogger(__name__) + + +class SettingSectionItem(BaseModel): + name: str + config: ConfigHandler | None = None + has_config: bool = True + + model_config = {"arbitrary_types_allowed": True} + + +class SettingsTreeModel(PydanticQItemModel[SettingSectionItem]): + item_model = SettingSectionItem + + def __init__( + self, + /, + parent: QObject | None = None, + *, + app_config: AppConfig, + template_library: TemplateLibrary, + ) -> None: + super().__init__(parent) + + self._template_library = template_library + self._root = TreeItem(data=SettingSectionItem(name="root")) + self._selected_model_index: QModelIndex = QModelIndex() + self._selected_title: str = "" + + self._app_leaf: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem(name="Application", config=app_config.app_config), + parent=self._root, + ) + _template_defaults_leaf: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem( + name="Template defaults", config=app_config.base_config + ), + parent=self._root, + ) + + templates_branch: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem(name="Templates"), parent=self._root + ) + self._built_in_templates_branch: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem(name="Built-in"), parent=templates_branch + ) + self._construct_template_branch( + self._built_in_templates_branch, template_library.built_in_templates_by_name + ) + self._plugin_templates_branch: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem(name="Plugins"), parent=templates_branch + ) + for plugin_name, templates in template_library.plugin_templates_by_name.items(): + plugin_branch = TreeItem( + data=SettingSectionItem(name=plugin_name), + parent=self._plugin_templates_branch, + ) + self._construct_template_branch(plugin_branch, templates) + + def _construct_template_branch( + self, + root: TreeItem[SettingSectionItem], + templates: dict[str, AssembledTemplate], + ) -> None: + for name, template in templates.items(): + assembled_branch: TreeItem[SettingSectionItem] = TreeItem( + data=SettingSectionItem(name=name), parent=root + ) + duplicate_check: set[ConfigHandler] = set() + for named_template in template.templates: + for ( + template_class_name, + details, + ) in named_template.template_classes.items(): + conf = details["config"] + if conf not in duplicate_check: + tree_item = TreeItem( + data=SettingSectionItem( + name=template_class_name, + config=conf, + has_config=conf.has_config, + ), + parent=assembled_branch, + ) + conf.config_added.add_listener( + lambda _, item=tree_item: self._on_config_state_changed( + item, True + ) + ) + conf.config_deleted.add_listener( + lambda _, item=tree_item: self._on_config_state_changed( + item, False + ) + ) + duplicate_check.add(conf) + + # region Properties + + @override + def _set_selected_model_index(self, value: QModelIndex) -> None: + if not value.isValid(): + super()._set_selected_model_index(value) + return + + item: TreeItem[SettingSectionItem] | None = value.internalPointer() + + if not item or not item.data.config: + return None + + super()._set_selected_model_index(value) + + self.selected_title = item.data.name # pyright: ignore[reportAttributeAccessIssue] + + _selected_title_changed = Signal() + + @Property(str, notify=_selected_title_changed) + def selected_title(self) -> str: # pyright: ignore[reportRedeclaration] + return self._selected_title + + @selected_title.setter + def selected_title(self, value: str) -> None: + if value != self._selected_title: + self._selected_title = value + self._selected_title_changed.emit() + + @property + def selected_section(self) -> ConfigHandler | None: + idx = self._selected_model_index + if idx.isValid(): + item: TreeItem[SettingSectionItem] = idx.internalPointer() + return item.data.config + + # endregion Properties + + @Slot(str, str, str) + def select_settings_section( + self, + template_name: str | None = None, + class_name: str | None = None, + plugin: str | None = None, + ) -> None: + if not template_name: + self.selected_model_index = self.index_of_item(self._app_leaf) # pyright: ignore[reportAttributeAccessIssue] + return + + templates_root: TreeItem[SettingSectionItem] | None = None + if plugin: + for tree_item in self._plugin_templates_branch.children: + if plugin == tree_item.data.name: + templates_root = tree_item + break + if not templates_root: + _logger.error( + f"Requested plugin '{plugin}' is not present in the settings tree." + ) + return + else: + templates_root = self._built_in_templates_branch + + matched_item: TreeItem[SettingSectionItem] | None = None + for tree_item in templates_root.children: + if template_name == tree_item.data.name: + if not class_name: + matched_item = tree_item.children[0] + else: + for class_item in tree_item.children: + if class_name == class_item.data.name: + matched_item = class_item + break + break + + if not matched_item: + _logger.error( + f"Requested template name {template_name} or class name { + class_name + } is not present in the settings tree." + ) + return + + self.selected_model_index = self.index_of_item(matched_item) # pyright: ignore[reportAttributeAccessIssue] + + @Slot() + def save_configs(self) -> None: + self._save_template_configs( + self._template_library.built_in_templates_by_name.values() + ) + for ( + plugin_templates + ) in self._template_library.plugin_templates_by_name.values(): + self._save_template_configs(plugin_templates.values()) + + def _save_template_configs(self, templates: Iterable[AssembledTemplate]) -> None: + for assembled_template in templates: + for named_temaplate in assembled_template.templates: + for template_details in named_temaplate.template_classes.values(): + template_details["config"].save() + + def _on_config_state_changed( + self, tree_item: TreeItem[SettingSectionItem], has_config: bool + ) -> None: + tree_item.data.has_config = has_config + q_idx = self.index_of_item(tree_item) + self.dataChanged.emit(q_idx, q_idx, [self.get_role("has_config")]) diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py new file mode 100644 index 00000000..19d7efe5 --- /dev/null +++ b/src/gui/qml/models/template_list_model.py @@ -0,0 +1,286 @@ +from asyncio import ensure_future, gather, to_thread +from functools import cached_property +from logging import getLogger +from pathlib import Path +from urllib.request import url2pathname + +from pydantic import BaseModel +from PySide6.QtCore import ( + QObject, + QUrl, + Slot, +) + +from src._loader import ( + AssembledTemplate, + AssembledTemplateConfigChangedArgs, + AssembledTemplateInstalledArgs, + TemplateLibrary, + sort_layout_categories, +) +from src.enums.mtg import LayoutCategory +from src.gui.qml.models.file_dialog_model import FileDialogModel +from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel +from src.gui.qml.models.test_renders_model import TestRendersModel +from src.render.render_queue import RenderQueue, cancel_with_render +from src.render.setup import prepare_render_operations +from src.utils.data_structures import first +from src.utils.images import match_images_with_data_files + +_logger = getLogger(__name__) + + +class TemplateData(BaseModel): + name: str + img: str | None + card_layouts: list[LayoutCategory] + installed_template_files: list[str] + missing_template_files: list[str] + is_installed: bool + has_config: bool + plugin: str + + @cached_property + def assembled_template(self) -> AssembledTemplate: + raise ValueError("Assembled template is not set") + + @cached_property + def full_name(self) -> str: + return self.name + (f" ({self.plugin})" if self.plugin else "") + + +class TemplateListModel(PydanticQListModel[TemplateData]): + item_model = TemplateData + + def __init__( + self, + render_queue: RenderQueue, + file_dialog_model: FileDialogModel, + message_dialog_model: MessageDialogContentModel, + template_library: TemplateLibrary, + test_renders_model: TestRendersModel, + parent: QObject | None = None, + selected_index: int = 0, + ) -> None: + self._render_queue = render_queue + self._file_dialog_model = file_dialog_model + self._message_dialog_model = message_dialog_model + self._template_library = template_library + self._test_renders_model = test_renders_model + self.built_in_templates = template_library.built_in_templates_by_name + self.plugin_templates = template_library.plugin_templates_by_name + + template_datas: list[TemplateData] = [] + + for name, assembled_template in self.built_in_templates.items(): + template_data = TemplateData( + name=name, + card_layouts=( + layouts := sort_layout_categories( + assembled_template.layout_categories, LayoutCategory.Normal + ) + ), + installed_template_files=assembled_template.installed_template_files, + missing_template_files=assembled_template.missing_template_files, + is_installed=assembled_template.is_installed(), + has_config=assembled_template.has_config(), + plugin="", + img=str(path.as_uri()) + if (path := assembled_template.get_preview_image_path(first(layouts))) + else None, + ) + template_data.assembled_template = assembled_template + template_datas.append(template_data) + + for plugin_id, templates_by_name in self.plugin_templates.items(): + for name, assembled_template in templates_by_name.items(): + template_data = TemplateData( + name=name, + card_layouts=( + layouts := sort_layout_categories( + assembled_template.layout_categories, + LayoutCategory.Normal, + ) + ), + installed_template_files=assembled_template.installed_template_files, + missing_template_files=assembled_template.missing_template_files, + is_installed=assembled_template.is_installed(), + has_config=assembled_template.has_config(), + plugin=plugin_id, + img=str(path.as_uri()) + if ( + path := assembled_template.get_preview_image_path( + first(layouts) + ) + ) + else None, + ) + template_data.assembled_template = assembled_template + template_datas.append(template_data) + + for template_data in template_datas: + template_data.assembled_template.template_installed.add_listener( + self._on_template_installed + ) + template_data.assembled_template.config_state_changed.add_listener( + self._on_template_config_state_changed + ) + + super().__init__( + parent, + items=template_datas, + selected_index=selected_index, + ) + + self._sort_items() + + def render_files(self, paths: list[Path]) -> None: + if paths: + _logger.info( + f"Queueing { + len(paths) + } entries for render. Do note that the actual amount of renders might be lower if you selected JSON files or art for split cards." + ) + + selected_template_entry = self.items[self._selected_index] + if selected_template_entry.plugin: + template = self.plugin_templates[selected_template_entry.plugin][ + selected_template_entry.name + ] + else: + template = self.built_in_templates[selected_template_entry.name] + if render_operations := prepare_render_operations( + template, + match_images_with_data_files(paths), + file_dialog=self._file_dialog_model, + message_dialog=self._message_dialog_model, + ): + self._render_queue.enqueue(*render_operations) + + @Slot() + def render_selections(self) -> None: + async def action(): + # Choose images + selections = await self._file_dialog_model.select_images( + dialog_id="template_list_render", + filters=[ + FileDialogModel.IMAGES_AND_JSON_FILTER, + FileDialogModel.ALL_FILTER, + ], + ) + + self.render_files(selections) + + cancel_with_render(ensure_future(action()), self._render_queue) + + @Slot("QVariantList") + def render_targets(self, urls: list[QUrl]) -> None: + paths: list[Path] | None = None + try: + paths = [Path(url2pathname(url.path())) for url in urls] + except Exception: + _logger.exception("Failed to process drag & dropped files.") + if paths: + cancel_with_render( + ensure_future(to_thread(self.render_files, paths)), self._render_queue + ) + + @Slot(str, bool) + def test_render(self, layout: str | None = None, quick: bool = False) -> None: + async def action() -> None: + layout_category = LayoutCategory(layout) if layout else None + + selected_template_entry = self.items[self._selected_index] + if selected_template_entry.plugin: + template = self.plugin_templates[selected_template_entry.plugin][ + selected_template_entry.name + ] + else: + template = self.built_in_templates[selected_template_entry.name] + + preparation_routines = self._test_renders_model.prepare_test_renders( + (template,), layout_category, quick + ) + + _logger.info( + f"Queueing {len(preparation_routines)} template test entries for render." + ) + await gather(*preparation_routines) + + cancel_with_render(ensure_future(action()), self._render_queue) + + @Slot(result=str) + def selected_template_name(self) -> str: + if self._selected_index > -1: + return self.items[self._selected_index].full_name + return "" + + @Slot(str) + def select_template(self, name: str) -> None: + for idx, item in enumerate(self.items): + if item.full_name == name: + self.selected_index = idx # pyright: ignore[reportAttributeAccessIssue] + + @Slot(int) + def clear_settings(self, idx: int) -> None: + item = self.items[idx] + for named_template in item.assembled_template.templates: + for template_details in named_template.template_classes.values(): + template_details["config"].delete() + + def _sort_items(self) -> None: + self.beginResetModel() + self.items.sort(key=lambda item: item.name) + self.items.sort(key=lambda item: item.plugin) + self.items.sort(key=lambda item: item.is_installed, reverse=True) + self.endResetModel() + + def _on_template_installed( + self, event_args: AssembledTemplateInstalledArgs + ) -> None: + changed_template = event_args["sender"] + for idx, item in enumerate(self.items): + if item.assembled_template == changed_template: + old_value = item.is_installed + item.is_installed = changed_template.is_installed() + item.installed_template_files = ( + changed_template.installed_template_files + ) + item.missing_template_files = changed_template.missing_template_files + if old_value != item.is_installed: + # This is assumed to reset the model, so no need to emit dataChanged + self._sort_items() + else: + q_idx = self.createIndex(idx, 0) + self.dataChanged.emit( + q_idx, + q_idx, + [ + self.get_role("is_installed"), + self.get_role("installed_template_files"), + self.get_role("missing_template_files"), + ], + ) + return + + _logger.warning( + f"Installed template was not found from the template list: {changed_template.name}" + ) + + def _on_template_config_state_changed( + self, event_args: AssembledTemplateConfigChangedArgs + ) -> None: + changed_template = event_args["sender"] + for idx, item in enumerate(self.items): + if item.assembled_template == changed_template: + old_value = item.has_config + if old_value != (new_value := event_args["has_config"]): + item.has_config = new_value + q_idx = self.createIndex(idx, 0) + self.dataChanged.emit(q_idx, q_idx, [self.get_role("has_config")]) + return + + _logger.warning( + f"Template with changed config state was not found from the template list: {changed_template.name}" + ) diff --git a/src/gui/qml/models/template_updater_model.py b/src/gui/qml/models/template_updater_model.py new file mode 100644 index 00000000..9ea11697 --- /dev/null +++ b/src/gui/qml/models/template_updater_model.py @@ -0,0 +1,133 @@ +from asyncio import ensure_future, gather, to_thread +from functools import cached_property + +from pydantic import BaseModel +from PySide6.QtCore import Property, QObject, Signal, Slot + +from src._loader import AppTemplate, TemplateLibrary +from src._state import AppEnvironment +from src.enums.mtg import LayoutCategory +from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel +from src.utils.hexapi import check_api_keys + + +class DownloadableTemplateDetails(BaseModel): + file_name: str + google_drive_id: str | None + img: str + plugin: str + template_names: list[str] + template_classes: list[str] + layout_categories: list[LayoutCategory] + installed_version: str + available_version: str + download_size: int + """bytes""" + downloading: bool = False + + @cached_property + def handler(self) -> AppTemplate: + raise ValueError("Download handler is not specified") + + +class TemplateUpdaterModel(PydanticQListModel[DownloadableTemplateDetails]): + item_model = DownloadableTemplateDetails + + def __init__( + self, + app_env: AppEnvironment, + template_library: TemplateLibrary, + parent: QObject | None = None, + items: list[DownloadableTemplateDetails] = [], + selected_index: int = -1, + ) -> None: + self._app_env = app_env + self._template_library = template_library + + self._fetching_data = False + + super().__init__(parent, items, selected_index) + + _fetching_data_changed = Signal(name="fetchingDataChanged") + + @Property(bool, notify=_fetching_data_changed) + def fetching_data(self) -> bool: # pyright: ignore[reportRedeclaration] + return self._fetching_data + + @fetching_data.setter + def fetching_data(self, value: bool) -> None: + if value != self._fetching_data: + self._fetching_data = value + self._fetching_data_changed.emit() + + @Slot() + def fetch_data(self) -> None: + ensure_future(self._handle_fetch_data()) + + async def _handle_fetch_data(self) -> None: + self.fetching_data = True # pyright: ignore[reportAttributeAccessIssue] + + await check_api_keys(self._app_env) + + await gather( + *[ + to_thread(template.check_for_update) + for template in self._template_library.templates + ] + ) + + self.beginResetModel() + self.items = [ + DownloadableTemplateDetails( + file_name=template.file_name, + google_drive_id=template.google_drive_id, + img=str( + template.get_path_preview( + class_name=( + first_item := next( + iter(template.all_classes_and_layouts.items()) + ) + )[0], + class_type=first_item[1][0], + ).as_uri() + ), + plugin=template.plugin.id if template.plugin else "", + template_names=template.all_names, + template_classes=template.all_classes, + layout_categories=template.supported_layout_categories, + installed_version=template.version or "", + available_version=template.update_version or "", + download_size=template.update_size or 0, + ) + for template in self._template_library.templates + ] + for item, template in zip(self.items, self._template_library.templates): + item.handler = template + self.selected_index = 0 # pyright: ignore[reportAttributeAccessIssue] + self.endResetModel() + + self.fetching_data = False # pyright: ignore[reportAttributeAccessIssue] + + @Slot(int) + def download_template(self, index: int) -> None: + if index < 0 or index >= self.rowCount(): + return + + ensure_future(self._handle_template_download(index)) + + async def _handle_template_download(self, index: int) -> None: + item = self.items[index] + q_index = self.createIndex(index, 0) + + item.downloading = True + changed_fields: list[int] = [self.get_role("downloading")] + self.dataChanged.emit(q_index, q_index, changed_fields) + if await to_thread(item.handler.update_template): + item.installed_version = item.handler.version or "" + changed_fields.append(self.get_role("installed_version")) + item.downloading = False + self.dataChanged.emit(q_index, q_index, changed_fields) + + @Slot() + def save_versions(self) -> None: + self._template_library.save_template_versions() diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py new file mode 100644 index 00000000..73331319 --- /dev/null +++ b/src/gui/qml/models/test_renders_model.py @@ -0,0 +1,122 @@ +from asyncio import Task, create_task, ensure_future, gather, to_thread +from collections.abc import Iterable +from functools import cached_property +from logging import getLogger + +from PySide6.QtCore import Property, QObject, Signal, Slot + +from src._loader import AssembledTemplate, RenderableTemplate, TemplateLibrary +from src._state import PATH +from src.cards import parse_card_info +from src.enums.mtg import LayoutCategory, LayoutType, layout_map_category +from src.gui.qml.models.file_dialog_model import FileDialogModel +from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel +from src.render.render_queue import RenderQueue, cancel_with_render +from src.render.setup import prepare_render_operations +from src.utils.tests import get_template_render_test_cases, prepare_test_render + +_logger = getLogger(__name__) + + +class TestRendersModel(QObject): + def __init__( + self, + render_queue: RenderQueue, + template_library: TemplateLibrary, + file_dialog_model: FileDialogModel, + message_dialog_model: MessageDialogContentModel, + /, + parent: QObject | None = None, + *, + objectName: str | None = None, + ) -> None: + super().__init__(parent, objectName=objectName) + self._render_queue = render_queue + self._template_library = template_library + self._file_dialog_model = file_dialog_model + self._message_dialog_model = message_dialog_model + self._layout_categories = list(LayoutCategory) + + @cached_property + def template_render_test_cases(self) -> dict[LayoutType, dict[str, str]]: + return get_template_render_test_cases() + + _layout_categories_changed = Signal() + + @Property("QVariantList", notify=_layout_categories_changed) + def layout_categories(self) -> list[LayoutCategory]: # pyright: ignore[reportRedeclaration] + return self._layout_categories + + async def test_render( + self, template: RenderableTemplate, layout_category: LayoutCategory, quick: bool + ) -> None: + layout_types = layout_map_category[layout_category] + for layout_type in layout_types: + if test_cases := self.template_render_test_cases.get(layout_type, None): + for idx, test_case in enumerate(test_cases): + if quick and idx > 0: + break + + if render_operations := await to_thread( + prepare_render_operations, + template, + (parse_card_info(PATH.SRC_IMG_TEST, name_override=test_case),), + file_dialog=self._file_dialog_model, + message_dialog=self._message_dialog_model, + ): + for op in render_operations: + op.before_render_callback = prepare_test_render + + self._render_queue.enqueue(*render_operations) + + def prepare_test_renders( + self, + templates: Iterable[AssembledTemplate], + layout_category: LayoutCategory | None = None, + quick: bool = False, + ) -> list[Task[None]]: + preparation_routines: list[Task[None]] = [] + for template in templates: + for category in template.layout_categories: + if not template.is_installed(category) or ( + layout_category and not category == layout_category + ): + continue + + preparation_routines.append( + create_task(self.test_render(template, category, quick)) + ) + + if layout_category: + break + + return preparation_routines + + async def test_all_renders( + self, layout_category: LayoutCategory | None = None, quick: bool = False + ) -> None: + preparation_routines: list[Task[None]] = self.prepare_test_renders( + self._template_library.built_in_templates_by_name.values(), + layout_category, + quick, + ) + for ( + plugin_templates + ) in self._template_library.plugin_templates_by_name.values(): + preparation_routines += self.prepare_test_renders( + plugin_templates.values(), layout_category, quick + ) + + _logger.info( + f"Queueing all {len(preparation_routines)} test entries for render." + ) + await gather(*preparation_routines) + + @Slot(str, bool) + def test_all(self, layout: str | None = None, quick: bool = False) -> None: + cancel_with_render( + ensure_future( + self.test_all_renders(LayoutCategory(layout) if layout else None, quick) + ), + self._render_queue, + ) diff --git a/src/gui/qml/qmldir b/src/gui/qml/qmldir new file mode 100644 index 00000000..e191c862 --- /dev/null +++ b/src/gui/qml/qmldir @@ -0,0 +1,2 @@ +module qml +App App.qml \ No newline at end of file diff --git a/src/gui/qml/tools/FontViewer.qml b/src/gui/qml/tools/FontViewer.qml new file mode 100644 index 00000000..3c39bbed --- /dev/null +++ b/src/gui/qml/tools/FontViewer.qml @@ -0,0 +1,34 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + + width: 400 + height: 640 + color: palet.window + + SystemPalette { + id: palet + } + + ListView { + anchors.fill: parent + model: Qt.fontFamilies() + + delegate: Item { + id: fontDelegate + + required property string modelData + + height: 30 + width: ListView.view.width + Text { + font.family: fontDelegate.modelData + text: fontDelegate.modelData + color: palet.text + } + } + } +} diff --git a/src/gui/qml/tools/ImageTransformWindow.qml b/src/gui/qml/tools/ImageTransformWindow.qml new file mode 100644 index 00000000..c749fd5d --- /dev/null +++ b/src/gui/qml/tools/ImageTransformWindow.qml @@ -0,0 +1,155 @@ +pragma ComponentBehavior: Bound +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ApplicationWindow { + id: imageTransformWindow + + required property SystemPalette systemPalette + required property QtObject transformModel + + title: "Transform images" + width: 300 + height: 360 + leftPadding: 10 + topPadding: 10 + rightPadding: 10 + bottomPadding: 10 + visible: true + color: systemPalette.window + + ColumnLayout { + anchors.fill: parent + + spacing: 10 + + RowLayout { + Layout.fillWidth: true + + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + text: "Format" + color: imageTransformWindow.systemPalette.text + } + CustomComboBox { + Layout.alignment: Qt.AlignRight + + systemPalette: imageTransformWindow.systemPalette + model: imageTransformWindow.transformModel.image_file_formats + currentValue: imageTransformWindow.transformModel.image_file_format + + onActivated: idx => { + imageTransformWindow.transformModel.image_file_format = imageTransformWindow.transformModel.image_file_formats[currentIndex]; + } + } + } + + RowLayout { + Layout.fillWidth: true + + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + text: "Downscale" + color: imageTransformWindow.systemPalette.text + } + CustomCheckBox { + Layout.alignment: Qt.AlignRight + + systemPalette: imageTransformWindow.systemPalette + checked: imageTransformWindow.transformModel.downscale + + onClicked: { + imageTransformWindow.transformModel.downscale = !imageTransformWindow.transformModel.downscale; + } + } + } + + RowLayout { + Layout.fillWidth: true + + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + text: "Target width (px)" + color: imageTransformWindow.systemPalette.text + } + CustomSpinBox { + Layout.alignment: Qt.AlignRight + + editable: true + from: 1 + to: 100000 + value: imageTransformWindow.transformModel.downscale_width + + onValueModified: { + imageTransformWindow.transformModel.downscale_width = value; + } + } + } + + Text { + Layout.fillWidth: true + + text: `Typical widths for a proxy image, that has bleed, at different levels of DPI include: +600: 1632 px +800: 2176 px +1200: 3264 px` + color: imageTransformWindow.systemPalette.text + wrapMode: Text.WordWrap + } + + RowLayout { + Layout.fillWidth: true + + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + text: "Quality" + color: imageTransformWindow.systemPalette.text + } + CustomSpinBox { + Layout.alignment: Qt.AlignRight + + editable: true + from: 1 + to: 100 + value: imageTransformWindow.transformModel.encoding_quality + + onValueModified: { + imageTransformWindow.transformModel.encoding_quality = value; + } + } + } + + Text { + Layout.fillWidth: true + + text: "Quality doesn't affect PNG encoding." + color: imageTransformWindow.systemPalette.text + wrapMode: Text.WordWrap + } + + CustomButton { + Layout.alignment: Qt.AlignRight + + systemPalette: imageTransformWindow.systemPalette + bgColor: imageTransformWindow.systemPalette.button + text: "Transform" + onClicked: imageTransformWindow.transformModel.transform_images() + } + + Item { + Layout.fillHeight: true + } + } +} diff --git a/src/gui/qml/tools/SystemPaletteViewer.qml b/src/gui/qml/tools/SystemPaletteViewer.qml new file mode 100644 index 00000000..0f97b861 --- /dev/null +++ b/src/gui/qml/tools/SystemPaletteViewer.qml @@ -0,0 +1,197 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ApplicationWindow { + id: window + + width: 800 + height: 300 + color: palet.window + + SystemPalette { + id: palet + } + + Flow { + anchors.fill: parent + spacing: 10 + + ColumnLayout { + Text { + text: "accent" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.accent + } + } + ColumnLayout { + Text { + text: "alternateBase" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.alternateBase + } + } + ColumnLayout { + Text { + text: "base" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.base + } + } + ColumnLayout { + Text { + text: "button" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.button + } + } + ColumnLayout { + Text { + text: "buttonText" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.buttonText + } + } + ColumnLayout { + Text { + text: "dark" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.dark + } + } + ColumnLayout { + Text { + text: "highlight" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.highlight + } + } + ColumnLayout { + Text { + text: "highlightedText" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.highlightedText + } + } + ColumnLayout { + Text { + text: "light" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.light + } + } + ColumnLayout { + Text { + text: "mid" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.mid + } + } + ColumnLayout { + Text { + text: "midlight" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.midlight + } + } + ColumnLayout { + Text { + text: "placeholderText" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.placeholderText + } + } + ColumnLayout { + Text { + text: "shadow" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.shadow + } + } + ColumnLayout { + Text { + text: "text" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.text + } + } + ColumnLayout { + Text { + text: "window" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.window + } + } + ColumnLayout { + Text { + text: "windowText" + color: palet.text + } + Rectangle { + width: 50 + height: 50 + color: palet.windowText + } + } + } +} diff --git a/src/gui/qml/tools/qmldir b/src/gui/qml/tools/qmldir new file mode 100644 index 00000000..c321a1d7 --- /dev/null +++ b/src/gui/qml/tools/qmldir @@ -0,0 +1,3 @@ +module qml.tools +ImageTransformWindow ImageTransformWindow.qml +SystemPaletteViewer SystemPaletteViewer.qml \ No newline at end of file diff --git a/src/gui/qml/views/BatchRenderView.qml b/src/gui/qml/views/BatchRenderView.qml new file mode 100644 index 00000000..119a86f6 --- /dev/null +++ b/src/gui/qml/views/BatchRenderView.qml @@ -0,0 +1,89 @@ +pragma ComponentBehavior: Bound +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ListView { + id: batchRenderList + + required property SystemPalette systemPalette + required property AbstractListModel batchModel + required property QtObject pathModel + property double requiredLayoutNameWidth: 0 + + TextMetrics { + id: textMetrics + } + + Component.onCompleted: { + let requiredW = 0; + for (const name of batchModel.layout_names()) { + textMetrics.text = name; + if (textMetrics.width > requiredW) { + requiredW = textMetrics.width; + } + } + textMetrics.destroy(); + requiredLayoutNameWidth = requiredW + 10; + } + + spacing: 1 + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + reuseItems: true + clip: true + focus: true + highlightFollowsCurrentItem: false + currentIndex: -1 + model: batchModel + delegate: RowLayout { + id: batchRenderListDelegate + + width: batchRenderList.width + height: 30 + spacing: 0 + + required property int index + required property string name + required property int selected + required property list options + required property list options_installed + required property list options_preview_img_path + + Text { + Layout.alignment: Qt.AlignLeft + Layout.minimumWidth: batchRenderList.requiredLayoutNameWidth + Layout.leftMargin: 10 + + text: batchRenderListDelegate.name + color: batchRenderList.systemPalette.text + } + CustomComboBox { + id: templateComboBox + + Layout.alignment: Qt.AlignRight + Layout.fillWidth: true + + function getItemTextColor(index: int): color { + return batchRenderListDelegate.options_installed[index] ? systemPalette.text : systemPalette.placeholderText; + } + + systemPalette: batchRenderList.systemPalette + itemTextColor: getItemTextColor + implicitHeight: 30 + model: batchRenderListDelegate.options + tooltipModel: batchRenderListDelegate.options_preview_img_path.map(path => ``) + currentIndex: batchRenderListDelegate.selected + + onActivated: idx => { + batchRenderList.batchModel.select_template_for_layout(batchRenderListDelegate.index, templateComboBox.currentIndex); + } + } + } + + ScrollBar.vertical: ScrollBar {} +} diff --git a/src/gui/qml/views/Console.qml b/src/gui/qml/views/Console.qml new file mode 100644 index 00000000..d7fce4f0 --- /dev/null +++ b/src/gui/qml/views/Console.qml @@ -0,0 +1,96 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ColumnLayout { + id: root + + required property SystemPalette systemPalette + required property AbstractListModel logModel + required property QtObject pathModel + required property string emojiFontFamily + required property string monospaceFontFamily + + Settings { + id: settings + + category: "Console" + location: root.pathModel.get_preferences_path("Console.ini") + + property bool autoScroll: true + } + + RowLayout { + Layout.fillWidth: true + + Text { + Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.topMargin: 5 + Layout.bottomMargin: 5 + + text: "Log" + color: root.systemPalette.text + } + CustomButton { + Layout.alignment: Qt.AlignRight + + systemPalette: root.systemPalette + text: ` Autoscroll` + palette.buttonText: settings.autoScroll ? root.systemPalette.buttonText : root.systemPalette.placeholderText + onClicked: settings.autoScroll = !settings.autoScroll + } + CustomButton { + Layout.alignment: Qt.AlignRight + + systemPalette: root.systemPalette + text: `📋 Copy` + onClicked: root.logModel.copy_log() + } + } + ListView { + id: logList + + Layout.fillHeight: true + Layout.fillWidth: true + + leftMargin: 10 + rightMargin: 10 + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + clip: true + highlightFollowsCurrentItem: false + model: root.logModel + delegate: SelectableText { + id: logDelegate + + required property int index + required property string message + required property int severity + + width: logList.width + color: root.systemPalette.text + text: message + font.family: root.monospaceFontFamily + onLinkActivated: Qt.openUrlExternally(hoveredLink) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : undefined + } + } + + onCountChanged: { + if (settings.autoScroll) + Qt.callLater(logList.positionViewAtEnd); + } + + ScrollBar.vertical: ScrollBar {} + } +} diff --git a/src/gui/qml/views/RenderQueue.qml b/src/gui/qml/views/RenderQueue.qml new file mode 100644 index 00000000..9ed839a4 --- /dev/null +++ b/src/gui/qml/views/RenderQueue.qml @@ -0,0 +1,323 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ApplicationWindow { + id: renderQueueWindow + + required property SystemPalette systemPalette + required property string emojiFontName + required property AbstractListModel queueModel + required property QtObject pathModel + + title: "Render queue" + width: 800 + height: 640 + visible: true + color: systemPalette.window + + Settings { + id: settings + + category: "RenderQueue" + location: renderQueueWindow.pathModel.get_preferences_path("RenderQueue.ini") + + property alias windowWidth: renderQueueWindow.width + property alias windowHeight: renderQueueWindow.height + property alias windowX: renderQueueWindow.x + property alias windowY: renderQueueWindow.y + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Rectangle { + Layout.fillWidth: true + + implicitWidth: headerContent.implicitWidth + implicitHeight: headerContent.implicitHeight + color: renderQueueWindow.systemPalette.button + + RowLayout { + id: headerContent + + anchors.fill: parent + spacing: 10 + + Text { + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 10 + Layout.topMargin: 5 + Layout.bottomMargin: 5 + + text: "Currently rendering:" + font.pointSize: 12 + color: renderQueueWindow.systemPalette.text + } + Loader { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.topMargin: 5 + Layout.bottomMargin: 5 + + asynchronous: true + active: true + sourceComponent: renderQueueWindow.queueModel.is_rendering ? renderingActiveComponent : renderingInactiveComponent + + Component { + id: renderingActiveComponent + + RowLayout { + anchors.fill: parent + + spacing: 5 + + ColumnLayout { + Layout.fillWidth: true + + spacing: 2 + + SelectableText { + text: renderQueueWindow.queueModel.rendering_image_name + font.pointSize: 11 + color: renderQueueWindow.systemPalette.text + + ToolTip.delay: 300 + ToolTip.visible: renderingHoverHandler.hovered + ToolTip.text: renderQueueWindow.queueModel.rendering_image_path + + HoverHandler { + id: renderingHoverHandler + } + } + Flow { + Layout.fillWidth: true + + spacing: 2 + + SelectableText { + text: `Card: ${renderQueueWindow.queueModel.rendering_card_name} (${renderQueueWindow.queueModel.rendering_card_artist}) [${renderQueueWindow.queueModel.rendering_card_set}] {${renderQueueWindow.queueModel.rendering_card_collector_number}}` + font.pointSize: 10 + color: renderQueueWindow.systemPalette.text + } + Item { + implicitWidth: 13 + implicitHeight: 1 + } + SelectableText { + text: "Layout: " + renderQueueWindow.queueModel.rendering_layout_name + font.pointSize: 10 + color: renderQueueWindow.systemPalette.text + } + Item { + implicitWidth: 13 + implicitHeight: 1 + } + SelectableText { + text: "Class name: " + renderQueueWindow.queueModel.rendering_class_name + font.pointSize: 10 + color: renderQueueWindow.systemPalette.text + } + } + } + Item { + Layout.fillWidth: true + } + CustomButton { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: 10 + + systemPalette: renderQueueWindow.systemPalette + text: "✕" + palette.buttonText: !enabled ? renderQueueWindow.systemPalette.placeholderText : hovered || down ? "red" : renderQueueWindow.systemPalette.buttonText + enabled: renderQueueWindow.queueModel.is_rendering + + onClicked: renderQueueWindow.queueModel.cancel() + } + } + } + Component { + id: renderingInactiveComponent + + SelectableText { + text: "Nothing" + font.pointSize: 11 + color: renderQueueWindow.systemPalette.text + } + } + } + } + } + Rectangle { + Layout.fillWidth: true + + implicitWidth: subheaderContent.implicitWidth + implicitHeight: subheaderContent.implicitHeight + color: renderQueueWindow.systemPalette.base + + RowLayout { + id: subheaderContent + + anchors.fill: parent + spacing: 5 + + Text { + Layout.leftMargin: 10 + + text: "Queue" + color: renderQueueWindow.systemPalette.text + } + CustomButton { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: 10 + + systemPalette: renderQueueWindow.systemPalette + text: `🧹 Clear` + + onClicked: renderQueueWindow.queueModel.clear() + } + } + } + ListView { + id: queueList + + property int indexZeroPadding: 0 + + Connections { + target: renderQueueWindow.queueModel + + function onRowsInserted() { + const requiredPadding = renderQueueWindow.queueModel.rowCount().toString().length; + if (requiredPadding != queueList.indexZeroPadding) { + queueList.indexZeroPadding = requiredPadding; + } + } + function onRowsRemoved() { + const requiredPadding = renderQueueWindow.queueModel.rowCount().toString().length; + if (requiredPadding != queueList.indexZeroPadding) { + queueList.indexZeroPadding = requiredPadding; + } + } + } + + TextMetrics { + id: textMetrics + text: "8888." + } + + Layout.fillWidth: true + Layout.fillHeight: true + + spacing: 5 + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + clip: true + focus: true + highlightFollowsCurrentItem: false + currentIndex: -1 + model: renderQueueWindow.queueModel + delegate: RowLayout { + id: queueDelegate + + required property int index + required property string image_name + required property string image_path + required property string card_name + required property string card_artist + required property string card_set + required property string card_collector_number + required property string layout_name + required property string class_name + + width: queueList.width + spacing: 5 + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.minimumWidth: textMetrics.width + 5 + Layout.leftMargin: 10 + Layout.topMargin: 2 + + text: queueDelegate.index.toString().padStart(queueList.indexZeroPadding, "0") + "." + font.pointSize: 11 + color: renderQueueWindow.systemPalette.text + } + ColumnLayout { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.fillWidth: true + Layout.leftMargin: 10 + + spacing: 2 + + SelectableText { + Layout.alignment: Qt.AlignLeft + + text: queueDelegate.image_name + font.pointSize: 10 + color: renderQueueWindow.systemPalette.text + + ToolTip.delay: 300 + ToolTip.visible: hoverHandler.hovered + ToolTip.text: queueDelegate.image_path + + HoverHandler { + id: hoverHandler + } + } + Flow { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + + spacing: 2 + + SelectableText { + text: `Card: ${queueDelegate.card_name} (${queueDelegate.card_artist}) [${queueDelegate.card_set}] {${queueDelegate.card_collector_number}}` + font.pointSize: 9 + color: renderQueueWindow.systemPalette.text + } + Item { + implicitWidth: 13 + implicitHeight: 1 + } + SelectableText { + text: "Layout: " + queueDelegate.layout_name + font.pointSize: 9 + color: renderQueueWindow.systemPalette.text + } + Item { + implicitWidth: 13 + implicitHeight: 1 + } + SelectableText { + text: "Class name: " + queueDelegate.class_name + font.pointSize: 9 + color: renderQueueWindow.systemPalette.text + } + } + } + Item { + Layout.fillWidth: true + } + CustomButton { + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.topMargin: 2 + Layout.rightMargin: 10 + + systemPalette: renderQueueWindow.systemPalette + text: "✕" + palette.buttonText: hovered || down ? "red" : renderQueueWindow.systemPalette.buttonText + + onClicked: renderQueueWindow.queueModel.dequeue(queueDelegate.index) + } + } + + ScrollBar.vertical: ScrollBar {} + } + } +} diff --git a/src/gui/qml/views/SettingsWindow.qml b/src/gui/qml/views/SettingsWindow.qml new file mode 100644 index 00000000..bb02e27e --- /dev/null +++ b/src/gui/qml/views/SettingsWindow.qml @@ -0,0 +1,383 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ApplicationWindow { + id: settingsWindow + + required property SystemPalette systemPalette + required property QtObject pathModel + required property string emojiFontName + required property QtObject settingsTreeModel + required property QtObject settingsModel + + title: "Settings" + width: 800 + height: 640 + visible: true + color: systemPalette.window + + Settings { + id: settings + + category: "Settings" + location: settingsWindow.pathModel.get_preferences_path("Settings.ini") + + property alias windowWidth: settingsWindow.width + property alias windowHeight: settingsWindow.height + property alias windowX: settingsWindow.x + property alias windowY: settingsWindow.y + property var settingsSectionsSplitState + } + + Component.onCompleted: { + settingsSectionsSplit.restoreState(settings.settingsSectionsSplitState); + } + Component.onDestruction: { + settings.settingsSectionsSplitState = settingsSectionsSplit.saveState(); + } + + SplitView { + id: settingsSectionsSplit + + anchors.fill: parent + orientation: Qt.Horizontal + + TreeView { + id: settingsSections + + SplitView.fillHeight: true + SplitView.preferredWidth: 200 + SplitView.minimumWidth: 150 + + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + reuseItems: true + clip: true + focus: true + animate: false + alternatingRows: false + palette.base: settingsWindow.systemPalette.window + model: settingsWindow.settingsTreeModel + delegate: TreeViewDelegate { + id: settingsSectionDelegate + + required property string name + required property bool has_config + + implicitWidth: settingsSections.width + selected: settingsSections.rowAtIndex(settingsWindow.settingsTreeModel.selected_model_index) === row + onClicked: { + settingsWindow.settingsTreeModel.selected_model_index = settingsSections.index(row, 0); + } + background.implicitHeight: 0 + background.implicitWidth: 0 + indicator.implicitHeight: 20 + + // We have to override content item because otherwise it tries to get + // display role in a way that for some reason doesn't reach the Python data model? + contentItem: Label { + clip: false + text: settingsSectionDelegate.name + elide: Text.ElideRight + color: settingsSectionDelegate.has_config ? settingsSectionDelegate.highlighted ? settingsSectionDelegate.palette.highlightedText : settingsSectionDelegate.palette.buttonText : settingsWindow.systemPalette.placeholderText + visible: !settingsSectionDelegate.editing + } + } + + Connections { + id: treeViewConnections + + target: settingsWindow.settingsTreeModel + + function onSelectedModelIndexChanged(): void { + const idx = settingsWindow.settingsTreeModel.selected_model_index; + if (idx.valid) { + settingsSections.expandToIndex(idx); + settingsSections.positionViewAtIndex(idx, TreeView.AlignCenter, Qt.point(0, 0), Qt.rect(0, 0, 0, 0)); + } + } + } + + Timer { + id: positionTimer + + interval: 50 + running: false + repeat: true + onTriggered: { + // Somewhat hacky way to wait for the model to load. + // This might not guarantee the model to be ready? + if (settingsWindow.settingsTreeModel.rowCount()) { + treeViewConnections.onSelectedModelIndexChanged(); + running = false; + } + } + } + + Component.onCompleted: positionTimer.running = true + } + ColumnLayout { + SplitView.fillHeight: true + SplitView.fillWidth: true + + spacing: 0 + + Rectangle { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + Layout.minimumHeight: headerTitle.height + 10 + + color: settingsWindow.systemPalette.button + + RowLayout { + id: settingsHeader + + anchors.fill: parent + spacing: 5 + + SelectableText { + id: headerTitle + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.leftMargin: 10 + + text: settingsWindow.settingsTreeModel.selected_title + font.pointSize: 16 + color: settingsWindow.systemPalette.text + } + CustomButton { + id: resetButton + + systemPalette: settingsWindow.systemPalette + text: `🔄 Reset` + visible: settingsWindow.settingsModel.valid + + ToolTip.delay: 300 + ToolTip.visible: resetButton.hovered + ToolTip.text: "Restore default settings values" + + onClicked: { + settingsWindow.settingsModel.reset(); + } + } + CustomButton { + id: clearButton + + Layout.rightMargin: 5 + + systemPalette: settingsWindow.systemPalette + text: `🧹 Clear` + visible: settingsWindow.settingsModel.valid + + ToolTip.delay: 300 + ToolTip.visible: clearButton.hovered + ToolTip.text: "Delete the settings file" + + onClicked: { + settingsWindow.settingsModel.clear(); + } + } + } + } + ListView { + id: settingsList + + Layout.fillHeight: true + Layout.fillWidth: true + + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + reuseItems: false + clip: true + spacing: 5 + highlightFollowsCurrentItem: false + model: settingsWindow.settingsModel + delegate: ItemDelegate { + id: settingsListDelegate + + required property int index + required property string type + required property string title + required property string desc + required property var value + required property var default_value + required property var options + property bool isTitle: settingsListDelegate.type === "title" + + width: settingsList.width + highlighted: false + background: Rectangle { + color: settingsListDelegate.isTitle ? settingsWindow.systemPalette.base : "transparent" + } + + contentItem: RowLayout { + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + spacing: 10 + + ColumnLayout { + Layout.alignment: settingsListDelegate.isTitle ? Qt.AlignVCenter : Qt.AlignTop + Layout.fillWidth: true + + SelectableText { + Layout.alignment: settingsListDelegate.isTitle ? Qt.AlignVCenter : Qt.AlignTop + Layout.fillWidth: true + + text: settingsListDelegate.isTitle ? settingsListDelegate.title : `${settingsListDelegate.title}` + color: settingsWindow.systemPalette.text + font.pointSize: settingsListDelegate.isTitle ? 12 : descText.font.pointSize + } + SelectableText { + id: descText + + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: settingsListDelegate.desc + color: settingsWindow.systemPalette.text + visible: Boolean(settingsListDelegate.desc) + } + } + Loader { + id: inputLoader + + property bool isTextInput: sourceComponent === textInputComponent + + Layout.alignment: Qt.AlignRight + Layout.minimumWidth: isTextInput ? 50 : 0 + Layout.maximumWidth: isTextInput ? parent.width / 2 : inputLoader.implicitWidth + + asynchronous: true + sourceComponent: { + switch (settingsListDelegate.type) { + case "bool": + return checkboxComponent; + case "string": + return textInputComponent; + case "numeric": + if (Number.isInteger(settingsListDelegate.default_value)) { + return spinBoxIntComponent; + } + return spinBoxDouble; + case "options": + return comboBox; + case "int": + return spinBoxIntComponent; + case "float": + return spinBoxDouble; + default: + return undefined; + } + } + + Component { + id: textInputComponent + + CustomTextField { + id: settingTextInput + + text: settingsListDelegate.value + color: settingsWindow.systemPalette.text + + onEditingFinished: settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingTextInput.text) + } + } + Component { + id: checkboxComponent + + CustomCheckBox { + id: settingCheckbox + + systemPalette: settingsWindow.systemPalette + checked: settingsListDelegate.value + + onClicked: settingsWindow.settingsModel.bool_value_changed(settingsListDelegate.index, !settingsListDelegate.value) + } + } + Component { + id: spinBoxIntComponent + + CustomSpinBox { + id: settingSpinBoxInt + + editable: true + from: -2147483648 + to: 2147483647 + value: settingsListDelegate.value + + onValueModified: settingsWindow.settingsModel.int_value_changed(settingsListDelegate.index, settingSpinBoxInt.value) + } + } + Component { + id: spinBoxDouble + + CustomSpinBox { + id: settingSpinBoxDouble + + editable: true + from: -2147483648 + to: 2147483647 + value: decimalToInt(settingsListDelegate.value) + stepSize: decimalFactor + anchors.centerIn: parent + + property int decimals: 3 + property real realValue: value / decimalFactor + readonly property int decimalFactor: Math.pow(10, decimals) + + function decimalToInt(decimal: double): int { + return decimal * decimalFactor; + } + + validator: RegularExpressionValidator { + regularExpression: /[0-9]+[.]?[0-9]*/ + } + + // locale is of type Locale but throws a warning if annotated + // citing insufficient annotation of the function + textFromValue: function (value: double, locale): string { + return (value / decimalFactor).toString(); + } + + valueFromText: function (text: string, locale): int { + return Math.round(parseFloat(text) * decimalFactor); + } + + onValueModified: { + settingsWindow.settingsModel.float_value_changed(settingsListDelegate.index, settingSpinBoxDouble.realValue); + } + } + } + Component { + id: comboBox + + CustomComboBox { + id: settingComboBox + + systemPalette: settingsWindow.systemPalette + model: settingsListDelegate.options + currentValue: settingsListDelegate.value + + onActivated: idx => { + settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingComboBox.model[idx]); + } + } + } + } + } + } + + ScrollBar.vertical: ScrollBar {} + } + } + } +} diff --git a/src/gui/qml/views/TemplateDetails.qml b/src/gui/qml/views/TemplateDetails.qml new file mode 100644 index 00000000..fbb04412 --- /dev/null +++ b/src/gui/qml/views/TemplateDetails.qml @@ -0,0 +1,147 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQml +import QtQuick +import QtQuick.Controls + +import qml.components + +Rectangle { + id: templateDetails + + required property SystemPalette systemPalette + required property AbstractListModel templateListMdl + required property DelegateModel templateListDelegateMdl + required property QtObject pathModel + property var model: templateListDelegateMdl.items.count ? templateListDelegateMdl.items.get(templateListMdl.selected_index).model : undefined + + Settings { + id: settings + + category: "TemplateDetails" + location: templateDetails.pathModel.get_preferences_path("TemplateDetails.ini") + + property var detailsSplitState + } + + Component.onCompleted: { + detailsSplit.restoreState(settings.detailsSplitState); + } + Component.onDestruction: { + settings.detailsSplitState = detailsSplit.saveState(); + } + + color: systemPalette.window + + SplitView { + id: detailsSplit + + anchors.fill: parent + orientation: Qt.Vertical + + Image { + SplitView.fillWidth: true + SplitView.preferredHeight: 270 + + verticalAlignment: Image.AlignTop + asynchronous: true + source: templateDetails.model?.img ?? pathModel.preview_img_fallback + fillMode: Image.PreserveAspectFit + } + ListView { + id: detailsTextFields + + SplitView.fillWidth: true + SplitView.fillHeight: true + + spacing: 2 + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + clip: true + highlightFollowsCurrentItem: false + currentIndex: -1 + model: { + const isPlugin = Boolean(templateDetails.model?.plugin); + + return [ + { + name: "Name:", + isTitle: true, + isVisible: true + }, + { + name: templateDetails.model?.name ?? "", + isTitle: false, + isVisible: true + }, + { + name: "Plugin:", + isTitle: true, + isVisible: isPlugin + }, + { + name: templateDetails.model?.plugin ?? "", + isTitle: false, + isVisible: isPlugin + }, + { + name: "Supported layouts:", + isTitle: true, + isVisible: true + }, + { + name: templateDetails.model?.card_layouts.join(", ") ?? "", + isTitle: false, + isVisible: true + }, + { + name: "Installed templates:", + isTitle: true, + isVisible: true + }, + { + name: templateDetails.model?.installed_template_files && templateDetails.model.installed_template_files.length ? templateDetails.model.installed_template_files.join(", ") : "None", + isTitle: false, + isVisible: true + }, + { + name: "Missing templates:", + isTitle: true, + isVisible: true + }, + { + name: templateDetails.model?.missing_template_files && templateDetails.model.missing_template_files.length ? templateDetails.model.missing_template_files.join(", ") : "None", + isTitle: false, + isVisible: true + } + ]; + } + delegate: SelectableText { + id: textFieldDelegate + + required property int index + property var item: detailsTextFields.model[index] + property string name: item.name + property bool isTitle: item.isTitle + property bool isVisible: item.isVisible + + leftPadding: 5 + rightPadding: 5 + width: detailsTextFields.width + text: name + color: templateDetails.systemPalette.text + font.bold: isTitle + visible: isVisible + + Component.onCompleted: { + if (!isVisible) { + textFieldDelegate.height = -detailsTextFields.spacing; + } + } + } + + ScrollBar.vertical: ScrollBar {} + } + } +} diff --git a/src/gui/qml/views/TemplateList.qml b/src/gui/qml/views/TemplateList.qml new file mode 100644 index 00000000..c94377db --- /dev/null +++ b/src/gui/qml/views/TemplateList.qml @@ -0,0 +1,111 @@ +pragma ComponentBehavior: Bound +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ListView { + id: templateList + + required property SystemPalette systemPalette + required property AbstractListModel templateListMdl + required property DelegateModel templateListDelegateMdl + required property var openSettings + + Timer { + id: initTimer + interval: 50 + running: false + repeat: false + onTriggered: templateList.positionViewAtIndex(templateListMdl.selected_index, ListView.Center) + } + + Component.onCompleted: { + // Setting the view position in on completed doesn't work, + // so as a workaround a small delay is used. + // This might be because the delegates haven't been rendered at this point. + initTimer.start(); + } + + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + reuseItems: false + clip: true + focus: true + highlight: Rectangle { + height: templateList.currentItem?.height ?? 0 + width: templateList.currentItem?.width ?? 0 + color: templateList.systemPalette.highlight + y: templateList.currentItem?.y ?? 0 + } + highlightFollowsCurrentItem: false + currentIndex: templateListMdl.selected_index + model: templateListDelegateMdl + delegate: CustomItemDelegate { + id: templateListDelegate + + required property int index + required property string name + required property string plugin + required property bool is_installed + required property bool has_config + required property list card_layouts + + systemPalette: templateList.systemPalette + width: templateList.width + implicitHeight: 30 + highlighted: false + + onClicked: { + templateList.templateListMdl.selected_index = index; + } + + contentItem: RowLayout { + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + spacing: 0 + + Text { + Layout.fillWidth: true + Layout.rightMargin: 10 + + text: templateListDelegate.name + (templateListDelegate.plugin ? ` (${templateListDelegate.plugin})` : "") + color: templateListDelegate.is_installed ? templateList.systemPalette.text : templateList.systemPalette.placeholderText + } + Badge { + id: layoutsBadge + + property int numVisibleLayoutNames: 3 + + Layout.rightMargin: 10 + + text: templateListDelegate.card_layouts.slice(0, numVisibleLayoutNames).join(", ") + (templateListDelegate.card_layouts.length > numVisibleLayoutNames ? ` +${templateListDelegate.card_layouts.length - numVisibleLayoutNames}` : "") + textColor: templateList.systemPalette.text + bgColor: templateList.systemPalette.alternateBase + + ToolTip.delay: 300 + ToolTip.visible: layoutsBadge.hovered + ToolTip.text: templateListDelegate.card_layouts.join(", ") + } + CustomButton { + systemPalette: templateList.systemPalette + implicitWidth: 32 + text: "⚙️" + onClicked: templateList.openSettings(templateListDelegate.name, undefined, templateListDelegate.plugin) + } + CustomButton { + systemPalette: templateList.systemPalette + implicitWidth: 32 + text: templateListDelegate.has_config ? "🧹" : "" + onClicked: templateList.templateListMdl.clear_settings(templateListDelegate.index) + enabled: templateListDelegate.has_config + } + } + } + + ScrollBar.vertical: ScrollBar {} +} diff --git a/src/gui/qml/views/TemplateUpdater.qml b/src/gui/qml/views/TemplateUpdater.qml new file mode 100644 index 00000000..d14b1fc0 --- /dev/null +++ b/src/gui/qml/views/TemplateUpdater.qml @@ -0,0 +1,316 @@ +pragma ComponentBehavior: Bound +import QtCore +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import qml.components + +ApplicationWindow { + id: templateUpdaterWindow + + required property SystemPalette systemPalette + required property AbstractListModel updaterModel + required property QtObject pathModel + + DelegateModel { + id: updaterDelegateModel + model: templateUpdaterWindow.updaterModel + } + + // From https://stackoverflow.com/a/20732091 + function humanFileSize(bytes: int): string { + const i = bytes === 0 ? 0 : Math.floor(Math.log(bytes) / Math.log(1024)); + return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${["B", "kB", "MB", "GB", "TB"][i]}`; + } + + title: "Template updater" + width: 800 + height: 640 + visible: true + color: systemPalette.window + + Settings { + id: settings + + category: "TemplateUpdater" + location: templateUpdaterWindow.pathModel.get_preferences_path("TemplateUpdater.ini") + + property alias windowWidth: templateUpdaterWindow.width + property alias windowHeight: templateUpdaterWindow.height + property alias windowX: templateUpdaterWindow.x + property alias windowY: templateUpdaterWindow.y + + property var updaterSplitState + } + + Component.onCompleted: { + updaterSplit.restoreState(settings.updaterSplitState); + if (updaterDelegateModel.count < 1) + updaterModel.fetch_data(); + } + Component.onDestruction: { + settings.updaterSplitState = updaterSplit.saveState(); + } + + SplitView { + id: updaterSplit + + anchors.fill: parent + orientation: Qt.Horizontal + + Loader { + id: listLoader + + SplitView.fillHeight: true + SplitView.fillWidth: true + + asynchronous: true + sourceComponent: templateUpdaterWindow.updaterModel.fetching_data ? fetchingIndicatorComponent : availableTemplatesListComponent + + Component { + id: fetchingIndicatorComponent + + Item { + BusyIndicator { + id: indicator + + anchors.centerIn: parent + implicitWidth: 50 + implicitHeight: 50 + running: templateUpdaterWindow.updaterModel.fetching_data + + palette.dark: templateUpdaterWindow.systemPalette.text + } + } + } + Component { + id: availableTemplatesListComponent + + ListView { + id: availableTemplatesList + + orientation: ListView.Vertical + boundsBehavior: Flickable.StopAtBounds + boundsMovement: Flickable.StopAtBounds + reuseItems: true + clip: true + focus: true + highlight: Rectangle { + height: availableTemplatesList.currentItem?.height ?? 0 + width: availableTemplatesList.currentItem?.width ?? 0 + color: templateUpdaterWindow.systemPalette.highlight + y: availableTemplatesList.currentItem?.y ?? 0 + } + highlightFollowsCurrentItem: false + currentIndex: templateUpdaterWindow.updaterModel.selected_index + model: updaterDelegateModel + delegate: CustomItemDelegate { + id: availableTemplatesListDelegate + + required property int index + required property string file_name + required property string google_drive_id + required property string img + required property string plugin + required property list template_names + required property list template_classes + required property list layout_categories + required property string installed_version + required property string available_version + required property int download_size + required property bool downloading + + property bool canDownload: !installed_version && available_version + property bool hasUpdateAvailable: available_version && installed_version && (installed_version !== available_version) + + systemPalette: templateUpdaterWindow.systemPalette + width: availableTemplatesList.width + height: 30 + highlighted: false + + onClicked: { + templateUpdaterWindow.updaterModel.selected_index = index; + } + + contentItem: RowLayout { + anchors.fill: parent + anchors.leftMargin: 10 + spacing: 10 + + Text { + Layout.alignment: Qt.AlignLeft + + text: availableTemplatesListDelegate.file_name + (availableTemplatesListDelegate.plugin ? ` (${availableTemplatesListDelegate.plugin})` : "") + color: availableTemplatesListDelegate.installed_version ? (templateUpdaterWindow.updaterModel.selected_index === availableTemplatesListDelegate.index ? templateUpdaterWindow.systemPalette.highlightedText : templateUpdaterWindow.systemPalette.text) : templateUpdaterWindow.systemPalette.placeholderText + } + CustomButton { + id: downloadButton + + Layout.alignment: Qt.AlignRight + + systemPalette: templateUpdaterWindow.systemPalette + text: { + if (availableTemplatesListDelegate.downloading) { + return "Downloading"; + } + if (availableTemplatesListDelegate.hasUpdateAvailable) { + return "Update"; + } + if (availableTemplatesListDelegate.canDownload) { + return "Download"; + } + if (availableTemplatesListDelegate.installed_version) { + return "Installed"; + } + return "Unavailable"; + } + enabled: !availableTemplatesListDelegate.downloading && (availableTemplatesListDelegate.canDownload || availableTemplatesListDelegate.hasUpdateAvailable) + onClicked: { + templateUpdaterWindow.updaterModel.download_template(availableTemplatesListDelegate.index); + } + } + } + } + + ScrollBar.vertical: ScrollBar {} + } + } + } + + SplitView { + id: selectedUpdaterItemDetails + orientation: Qt.Vertical + + property var selectedItem: updaterDelegateModel.items.count ? updaterDelegateModel.items.get(templateUpdaterWindow.updaterModel.selected_index).model : undefined + + SplitView.fillHeight: true + SplitView.preferredWidth: 200 + + Image { + SplitView.fillWidth: true + SplitView.preferredHeight: 270 + + verticalAlignment: Image.AlignTop + asynchronous: true + source: selectedUpdaterItemDetails.selectedItem?.img ?? templateUpdaterWindow.pathModel.preview_img_fallback + fillMode: Image.PreserveAspectFit + } + + ColumnLayout { + SplitView.fillWidth: true + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "File name:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.file_name ?? "" + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Installed version:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.installed_version || "Not installed" + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Newest version:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.available_version ?? "Not available" + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Download size:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: templateUpdaterWindow.humanFileSize(selectedUpdaterItemDetails.selectedItem?.download_size ?? 0) + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Template names:" + wrapMode: Text.WordWrap + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.template_names.join(", ") ?? "" + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Template layouts:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.layout_categories.join(", ") ?? "" + color: templateUpdaterWindow.systemPalette.text + } + + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Template classes:" + color: templateUpdaterWindow.systemPalette.text + } + SelectableText { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: selectedUpdaterItemDetails.selectedItem?.template_classes.join(", ") ?? "" + color: templateUpdaterWindow.systemPalette.text + } + + Item { + Layout.fillHeight: true + } + } + } + } +} diff --git a/src/gui/qml/views/qmldir b/src/gui/qml/views/qmldir new file mode 100644 index 00000000..d75633cc --- /dev/null +++ b/src/gui/qml/views/qmldir @@ -0,0 +1,8 @@ +module qml.views +TemplateList TemplateList.qml +TemplateDetails TemplateDetails.qml +BatchRenderView BatchRenderView.qml +Console Console.qml +RenderQueue RenderQueue.qml +SettingsWindow SettingsWindow.qml +TemplateUpdater TemplateUpdater.qml \ No newline at end of file diff --git a/src/gui/tabs/creator.py b/src/gui/tabs/creator.py deleted file mode 100644 index d5e45ab3..00000000 --- a/src/gui/tabs/creator.py +++ /dev/null @@ -1,213 +0,0 @@ -""" -* GUI Tab: Custom Card Creator -""" -# Standard Library Imports -import os -from functools import cached_property - -# Third Party Imports -from kivy.lang import Builder -from kivy.uix.button import Button -from kivy.uix.gridlayout import GridLayout -from kivy.uix.spinner import Spinner -from kivy.uix.tabbedpanel import TabbedPanelItem, TabbedPanel - -# Local Imports -from src._state import PATH -from src._loader import TemplateCategoryMap, TemplateSelectedMap -from src.console import msg_bold -from src.gui._state import GlobalAccess - -""" -* Layout Classes -""" - - -class CreatorPanel(TabbedPanel, GlobalAccess): - """Panel tab the 'Creator' tab which renders custom cards.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "creator.kv")) - - @cached_property - def creator_tabs(self) -> list[type['CreatorLayout']]: - """Defined creator tabs.""" - return [ - CreatorNormalLayout, - CreatorPlaneswalkerLayout, - CreatorSagaLayout - ] - - def on_load(self, *args) -> None: - """Add creator tabs.""" - for tab in self.creator_tabs: - self.add_widget(CreatorTabItem(tab)) - self._tab_layout.padding = ( - '0dp', '0dp', '0dp', '0dp') - - -class CreatorTabItem(TabbedPanelItem, GlobalAccess): - """Represents a single tab in the CreatorPanels tabbed panel.""" - - def __init__(self, widget: type['CreatorLayout'], **kwargs): - super().__init__(text=widget._category, **kwargs) - self.add_widget(widget()) - - -class CreatorLayout(GridLayout, GlobalAccess): - """Represents the content of a specific 'CreatorTabItem' tab.""" - _category = '' - - def __init__(self, **kwargs): - - # Key attributes - self._templates: TemplateCategoryMap = self.main.template_map[self._category] - self._template_names: list[str] = self._templates['names'] - self._types: list[str] = list(self._templates['map'].keys()) - self._templates_selected: TemplateSelectedMap = self.main.templates_default.copy() - - # Call super - super().__init__(**kwargs) - - @cached_property - def toggle_buttons(self) -> list[Button]: - """Add render button.""" - return [self.ids.render_btn] - - """ - * Template Utils - """ - - def select_template(self, spinner: Spinner) -> None: - """Choose which template to render with. - - Args: - spinner: Spinner dropdown list element. - """ - for t in self._types: - if spinner.text in self._templates['map'][t]: - self._templates_selected[t] = self._templates['map'][t][spinner.text] - else: - # Notify the user one face type isn't supported - face = 'Front' if 'back' in t else 'Back' - self.console.update( - msg_bold(f"NOTE: Template '{spinner.text}' only supports '{face}' face cards.")) - - def render(self) -> None: - """Initiate a custom card render operation.""" - scryfall = self.format_card_data(self.get_card_data()) - template = self._templates_selected[scryfall.get('layout', 'normal')] - self.main.render_custom(template, scryfall) - - """ - * Data Utils - """ - - def get_card_data(self) -> dict: - """Extend this method to extract card data from UI form fields.""" - return {} - - def format_card_data(self, data: dict) -> dict: - """Post-process card data for layout validation or other cases. - - Args: - data: Dict containing card data, mirrors Scryfall data in the main app. - - Returns: - Formatted and validated card data dict. - """ - # Shared data - data['object'] = 'card' - data['lang'] = self.cfg.lang - - # Is this an alternate language card? - if data['lang'] != 'en': - data['printed_name'] = data['name'] - data['printed_text'] = data['oracle_text'] - data['printed_type_line'] = data['type_line'] - return data - - -class CreatorNormalLayout(CreatorLayout): - _category = 'Normal' - - def get_card_data(self) -> dict: - oracle_text = self.ids.oracle_text.text.replace("~", self.ids.name.text) - flavor_text = self.ids.flavor_text.text.replace("~", self.ids.name.text) - return { - "layout": "normal", - "set": self.ids.set.text, - "name": self.ids.name.text, - "oracle_text": oracle_text, - "flavor_text": flavor_text, - "power": self.ids.power.text, - "artist": self.ids.artist.text, - "mana_cost": self.ids.mana_cost.text, - "type_line": self.ids.type_line.text, - "toughness": self.ids.toughness.text, - "rarity": self.ids.rarity.text.lower(), - "printed_size": self.ids.card_count.text, - "keywords": self.ids.keywords.text.split(","), - "collector_number": self.ids.collector_number.text, - "color_identity": self.ids.color_identity.text.split() - } - - -class CreatorPlaneswalkerLayout(CreatorLayout): - _category = 'Planeswalker' - - def get_card_data(self) -> dict: - rules_text = "\n".join(n for n in [ - self.ids.line_1.text.replace("~", self.ids.name.text), - self.ids.line_2.text.replace("~", self.ids.name.text), - self.ids.line_3.text.replace("~", self.ids.name.text), - self.ids.line_4.text.replace("~", self.ids.name.text) - ] if n) - return { - "layout": "planeswalker", - "set": self.ids.set.text, - "name": self.ids.name.text, - "oracle_text": rules_text, - "artist": self.ids.artist.text, - "loyalty": self.ids.loyalty.text, - "mana_cost": self.ids.mana_cost.text, - "type_line": self.ids.type_line.text, - "rarity": self.ids.rarity.text.lower(), - "printed_size": self.ids.card_count.text, - "keywords": self.ids.keywords.text.split(","), - "collector_number": self.ids.collector_number.text, - "color_identity": self.ids.color_identity.text.split() - } - - -class CreatorSagaLayout(CreatorLayout): - _category = 'Saga' - - def get_card_data(self) -> dict: - text_arr, num_lines = [], 'I' - if self.ids.line_1.text != "": - text_arr.append(f"{num_lines} — " + self.ids.line_1.text.replace("~", self.ids.name.text)) - num_lines += "I" - if self.ids.line_2.text != "": - text_arr.append(f"{num_lines} — " + self.ids.line_2.text.replace("~", self.ids.name.text)) - num_lines += "I" - if self.ids.line_3.text != "": - text_arr.append(f"{num_lines} — " + self.ids.line_3.text.replace("~", self.ids.name.text)) - num_lines = "IV" if len(num_lines) == 3 else num_lines + "I" - if self.ids.line_4.text != "": - text_arr.append(f"{num_lines} — " + self.ids.line_4.text.replace("~", self.ids.name.text)) - text_arr.insert(0, f"(As this Saga enters and after your draw step, add a lore counter. " - f"Sacrifice after {num_lines}.)") - rules_text = "\n".join(text_arr) - return { - "layout": "saga", - "set": self.ids.set.text, - "oracle_text": rules_text, - "name": self.ids.name.text, - "artist": self.ids.artist.text, - "mana_cost": self.ids.mana_cost.text, - "type_line": self.ids.type_line.text, - "rarity": self.ids.rarity.text.lower(), - "printed_size": self.ids.card_count.text, - "keywords": self.ids.keywords.text.split(","), - "collector_number": self.ids.collector_number.text, - "color_identity": self.ids.color_identity.text.split() - } diff --git a/src/gui/tabs/main.py b/src/gui/tabs/main.py deleted file mode 100644 index 5cc18170..00000000 --- a/src/gui/tabs/main.py +++ /dev/null @@ -1,294 +0,0 @@ -""" -* GUI Tab: Main Rendering Tab -""" -# Standard Library Imports -import os -from pathlib import Path -from functools import cached_property - -# Kivy Imports -from kivy.lang import Builder -from kivy.uix.image import Image -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.scrollview import ScrollView -from kivy.uix.button import Button - -# Local Imports -from src._loader import TemplateDetails, TemplateCategoryMap -from src._state import PATH -from src.enums.mtg import layout_map_category -from src.gui.popup.settings import SettingsPopup -from src.gui._state import GUI, GlobalAccess -from src.gui.utils import ( - DynamicTabPanel, - DynamicTabItem, - HoverButton) - -""" -* Template Modules -""" - - -class MainPanel(BoxLayout, GlobalAccess): - """Main panel to the 'Render Cards' tab.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "main.kv")) - - @cached_property - def render_target_button(self) -> Button: - return self.ids.rend_targ_btn - - @cached_property - def render_all_button(self) -> Button: - return self.ids.rend_all_btn - - @cached_property - def app_settings_button(self) -> Button: - return self.ids.app_settings_btn - - @cached_property - def toggle_buttons(self) -> list[Button]: - """Add render and settings buttons.""" - return [ - self.render_target_button, - self.render_all_button, - self.app_settings_button, - ] - - -class TemplateModule(DynamicTabPanel, GlobalAccess): - """Module that loads template tabs.""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._tab_layout.padding = '0dp', '10dp', '0dp', '0dp' - self.tabs = [] - - # Create a scroll box with listed templates for each category - for cat, _ in layout_map_category.items(): - - # Get the template map for this category - templates: TemplateCategoryMap = self.main.template_map[cat] - - # Add a tab for this category - tab = DynamicTabItem(text=cat) - tab.content = self.get_template_container( - category=cat, - templates=templates) - self.tabs.append(tab) - - # Only add tabs when all are generated - [self.add_widget(t) for t in self.tabs] - - @staticmethod - def get_template_container( - category: str, - templates: TemplateCategoryMap - ) -> 'TemplateTabContainer': - """Return a template container for containing the list view and preview image. - - Args: - category: Template type category, e.g. Transform. - templates: Mapping of types -> name -> template details. - - Returns: - TemplateTabContainer object to add to the panel. - """ - - # Create a scroll box and container - scroll_box = TemplateView() - container = TemplateTabContainer() - - # Add template list to the scroll box - TL = TemplateList( - category=category, - templates=templates, - preview=container.ids.preview_image) - scroll_box.add_widget(TL) - GUI.template_list.setdefault(category, []).append(TL) - - # Add scroll box to the container and return it - container.ids.template_view_container.add_widget(scroll_box) - return container - - -class TemplateTabContainer(BoxLayout): - """Container that holds template list within each tab.""" - - -class TemplateView(ScrollView): - """Scrollable viewport for template list.""" - - -class TemplateList(BoxLayout, GlobalAccess): - """Builds a list of templates from a certain template type.""" - - def __init__(self, category: str, templates: type[TemplateCategoryMap], preview: Image, **kwargs): - super().__init__(**kwargs) - self.category = category - self.preview = preview - self.templates = templates - self.types = list(templates['map'].keys()) - self.add_template_rows() - - def add_template_rows(self) -> None: - """Add a row for each template in this list.""" - missing, found = [], [] - - # Create a list of buttons - for name in self.templates['names']: - - # Get details for each type - templates: dict[str, type[TemplateDetails] | None] = { - t: self.templates['map'][t].get(name) for t in self.types} - - # Is template installed? - if not all([n['object'].is_installed for _, n in templates.items() if n]): - missing.append(templates) - continue - - # Add installed template - widget = TemplateRow( - category=self.category, - template_map=templates, - preview=self.preview) - self.add_widget(widget) - found.append(widget) - - # Add the missing templates - uninstalled = [] - for m in missing: - row = TemplateRow( - category=self.category, - template_map=m, - preview=self.preview) - row.disabled = True - self.add_widget(row) - uninstalled.append(row) - - # Were no templates found at all? - if not any([found, uninstalled]): - return - - # Select the first 'found' template in this list, otherwise select the first uninstalled - btn = (found if found else uninstalled)[0].ids.toggle_button - btn.state = 'down' - btn.dispatch('on_press') - - def reload_template_rows(self) -> None: - """Remove existing rows and generate new ones using current template data.""" - self.clear_widgets() - self.add_template_rows() - - -class TemplateRow(BoxLayout, GlobalAccess): - """Row containing template selector and governing buttons.""" - - def __init__(self, category: str, template_map: dict[str, type[TemplateDetails]], preview: Image, **kwargs): - super().__init__(**kwargs) - - # Key attributes - self.image: Image = preview - self.template_map: dict[str, type[TemplateDetails]] = template_map - self.types: list[str] = list(template_map.keys()) - self.category: str = category - - # Establish the name and previews - name, config, previews = '', None, [PATH.SRC_IMG_NOTFOUND] * len(self.types) - for i, t in enumerate(self.types): - if obj := template_map.get(t, {}): - if not obj: - continue - - # Set the name if still empty - if not name: - name = obj.get('name', '') - - # Add the preview image for this type - previews[i] = obj['object'].get_path_preview( - class_name=obj['class_name'], class_type=t - ) if obj.get('object') else PATH.SRC_IMG_NOTFOUND - - # Set the chosen config object - if not config: - config = obj['config'] - config.gui_elements.append(self) - - # Set name, config, and previews - self.config = config - self.name = name or 'Unknown' - self.preview: list[Path] = previews - self.ids.toggle_button.text = self.name - self.preview_face = 0 - - # Check if config file is present - self.default_settings_button_check() - - # Add to GUI Dict - GUI.template_row.setdefault(category, {})[self.name] = self - GUI.template_btn.setdefault(category, {})[self.name] = self.ids.toggle_button - GUI.template_btn_cfg.setdefault(category, {})[self.name] = self.settings_button - - """ - * GUI Elements - """ - - @property - def settings_button(self) -> Button: - """Button: Click to load template specific settings.""" - return self.ids.settings_button - - @property - def reset_button(self) -> Button: - """Button: Click to reset template specific settings to default (global).""" - return self.ids.reset_default_button - - """ - * Utility Methods - """ - - def default_settings_button_check(self) -> None: - """Checks for a template's config file and enables/disables the reset settings button.""" - self.ids.reset_default_button.disabled = False if self.config.has_template_ini else True - - def toggle_preview_image(self) -> None: - """Toggles the preview image on multi-side templates like Transform.""" - if len(self.preview) < 2: - return - new_face = 0 if self.preview_face == 1 else 1 - self.image.source = str(self.preview[new_face]) - self.preview_face = new_face - - def set_preview_image(self) -> None: - """Sets the preview image in the parent container to the main preview face of this template.""" - self.image.manager = self - self.image.source = str(self.preview[0]) - self.preview_face = 0 - - -class TemplateSettingsButton(HoverButton, GlobalAccess): - """Opens the settings panel for a given template.""" - - async def open_settings(self) -> None: - """Opens a settings panel to customize the settings of a specific template class.""" - obj = self.parent - cfg_panel = SettingsPopup(obj.template_map[obj.types[0]]) - cfg_panel.open() - for row in obj.config.gui_elements: - row.default_settings_button_check() - - -class TemplateResetDefaultButton(HoverButton, GlobalAccess): - """Deletes the ini config file for a given template (resets its settings to default).""" - - async def reset_default(self) -> None: - """Removes the INI config file containing customized settings for a specific template class.""" - _file = self.parent.config.template_path_ini - if _file and _file.is_file(): - try: - os.remove(_file) - self.console.update(f"Reset template '{self.parent.name}' to global settings!") - except (PermissionError, OSError, FileNotFoundError): - self.console.update(f"Unable to reset settings: '{self.parent.name}'\n" - f"Likely a file permission error!") - for row in self.parent.config.gui_elements: - row.default_settings_button_check() diff --git a/src/gui/tabs/tools.py b/src/gui/tabs/tools.py deleted file mode 100644 index 11f66040..00000000 --- a/src/gui/tabs/tools.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -* GUI Tab: Tools -""" -# Standard Library Imports -import os -from functools import cached_property -from pathlib import Path -from collections.abc import Callable -from threading import Event -from concurrent.futures import ThreadPoolExecutor as Pool, as_completed - -# Third Party Imports -from photoshop.api._document import Document -from kivy.lang import Builder -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from omnitils.img import downscale_image_by_width - -# Local Imports -from src._state import PATH -from src.gui._state import GlobalAccess -from src.utils.adobe import get_photoshop_error_message -from src.helpers import import_art, reset_document, save_document_jpeg, close_document - - -class ToolsPanel(BoxLayout, GlobalAccess): - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "tools.kv")) - - class PSD: - """PSD tool paths.""" - Showcase: Path = PATH.TEMPLATES / 'tools' / 'showcase.psd' - - @cached_property - def toggle_buttons(self) -> list[Button]: - """Add tool buttons.""" - return [ - self.ids.generate_showcases_target, - self.ids.generate_showcases, - self.ids.compress_renders, - self.ids.compress_renders_target, - self.ids.compress_arts] - - @staticmethod - def process_wrapper(func) -> Callable: - """Decorator to handle state maintenance before and after an initiated render process. - - Args: - func: Function being wrapped. - - Returns: - The result of the wrapped function. - """ - def wrapper(self: 'ToolsPanel', *args): - while check := self.app.refresh_app(): - if not self.console.await_choice( - thr=Event(), - msg=get_photoshop_error_message(check), - end="Hit Continue to try again, or Cancel to end the operation.\n" - ): - # Cancel this operation - return - - # Reset - self.main.disable_buttons() - self.console.clear() - result = func(self, *args) - self.main.enable_buttons() - return result - return wrapper - - @process_wrapper - def render_showcases(self, target: bool = False) -> None: - """Render card images as showcases with rounded border. - - Args: - target: If true, select target images with Photoshop file select. - """ - - # Ensure showcase folder exists - path = PATH.OUT / 'showcase' - path.mkdir(mode=777, parents=True, exist_ok=True) - - # Targeted or all images? - images = self.main.select_art() if target else self.get_images(PATH.OUT) - - # No files provided - if not images and target: - # Cancelled file select - return - if not images: - # No files in 'out' folder - self.console.update("No card images found!") - return - - # Open the showcase tool - self.app.load(str(self.PSD.Showcase)) - docref: Document = self.app.activeDocument - - # Open each image and save with border crop - for img in images: - img_path = path / img.name - import_art( - layer=docref.activeLayer, - path=img, - docref=docref) - save_document_jpeg( - path=img_path, - docref=docref) - reset_document( - docref=docref) - close_document( - docref=docref) - - @process_wrapper - def compress_renders(self) -> None: - """Utility definition for compressing all rendered card images.""" - if not (images := self.get_images(PATH.OUT)): - self.console.update('No card images found!') - return - self.compress_images(images) - - @process_wrapper - def compress_arts(self): - """Utility definition for compressing all card arts.""" - if not (images := self.get_images(PATH.ART)): - self.console.update('No art images found!') - return - self.compress_images(images) - - @process_wrapper - def compress_target(self): - """Utility definition for compressing a target selection of images.""" - if not (images := self.main.select_art()): - return - self.compress_images(images) - - def compress_images(self, images: list[Path]) -> None: - """Compress a list of images. - - Args: - images: A list of image paths. - """ - with Pool(max_workers=(os.cpu_count() - 1) or 1) as executor: - quality = self.ids.compress_quality.text - tasks = [executor.submit( - downscale_image_by_width, img, **{ - 'max_width': 2176 if self.ids.compress_dpi.active else 3264, - 'optimize': True, - 'quality': int(quality) if quality.isnumeric() else 95, - }) for img in images] - [n.result() for n in as_completed(tasks)] - - """ - * File Utils - """ - - @staticmethod - def get_images(path: Path) -> list[Path]: - """Grab all supported image files within the "out" directory. - - Args: - path: Path to a directory containing images. - - Returns: - List of art files found in the given directory. - """ - # Folder, file list, supported extensions - all_files = os.listdir(path) - ext = (".png", ".jpg", ".jpeg") - - # Select all images in folder not prepended with ! - return [Path(path, f) for f in all_files if f.endswith(ext)] diff --git a/src/gui/test.py b/src/gui/test.py deleted file mode 100644 index 51c8c884..00000000 --- a/src/gui/test.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -* Test Mode App -""" -# Standard Library Imports -import os -import threading - -# Third Party Imports -from kivy.lang import Builder -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.popup import Popup -from kivy.uix.label import Label -from kivy.metrics import dp, sp - -# Local Imports -from src._state import PATH -from src._loader import TemplateDetails -from src.gui._state import GlobalAccess -from src.gui.utils import HoverButton - - -class TestApp(BoxLayout, GlobalAccess): - """Template Tester.""" - Builder.load_file(os.path.join(PATH.SRC_DATA_KV, "test.kv")) - - def on_load(self, *args) -> None: - """Add selector and toggle buttons.""" - self.selector = TemplateSelector( - self, size_hint=(.8, .8)) - self.main.toggle_buttons.extend([ - self.ids.test_all, - self.ids.test_target, - self.ids.test_all_deep - ]) - - def select_template(self): - """Select a target template to test.""" - self.selector.open() - - -class TemplateSelector(Popup, GlobalAccess): - """Popup selector for selecting a template to call 'Test Target' on.""" - - def __init__(self, root: TestApp, **kwargs): - self.test_app = root - super().__init__(**kwargs) - - def on_load(self, *args) -> None: - """Add template buttons to template selector.""" - for card_type, templates in { - t: temps for cat, cat_map in - self.main.template_map.items() for - t, temps in cat_map['map'].items() - }.items(): - - # Add title label - self.ids.content.add_widget( - Label( - text=card_type.replace("_", " ").title(), - size_hint=(1, None), - font_size=sp(25), - height=dp(45))) - - # Add buttons - [self.ids.content.add_widget(SelectorButton( - self.test_app, - template=template, - card_type=card_type, - text=template['name'] - )) for name, template in templates.items()] - - -class SelectorButton(HoverButton, GlobalAccess): - """Button which calls 'Test Target' on a given template.""" - - def __init__(self, root: TestApp, template: TemplateDetails, card_type: str, **kwargs): - super().__init__(**kwargs) - self.test_app = root - self.card_type = card_type - self.template = template - self.size_hint = (1, None) - self.font_size = sp(20) - self.height = dp(35) - - def on_release(self, **kwargs): - """Launch app method 'test_target' on release.""" - threading.Thread( - target=self.main.test_target, - args=(self.card_type, self.template), daemon=True - ).start() - self.test_app.selector.dismiss() diff --git a/src/gui/utils/__init__.py b/src/gui/utils/__init__.py deleted file mode 100644 index c7132550..00000000 --- a/src/gui/utils/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -* GUI Utility Modules -""" -# Local Imports -from src.gui.utils.behaviors import * -from src.gui.utils.fonts import * -from src.gui.utils.forms import * -from src.gui.utils.layouts import * diff --git a/src/gui/utils/behaviors.py b/src/gui/utils/behaviors.py deleted file mode 100644 index eff24eb8..00000000 --- a/src/gui/utils/behaviors.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -* GUI Utility Behaviors -""" -# Third Party Imports -from kivy.app import App -from kivy.core.window import Window -from kivy.properties import BooleanProperty, ObjectProperty - -""" -* Utility Classes -""" - - -class HoverBehavior: - """Utility modifier class which adds hover behavior to layout elements. - - Events: - `on_enter`: Fired when mouse enter the bbox of the widget. - `on_leave`: Fired when the mouse exit the widget - """ - hovered = BooleanProperty(False) - border_point = ObjectProperty(None) - - def __init__(self, **kwargs): - self.register_event_type('on_enter') - self.register_event_type('on_leave') - Window.bind(mouse_pos=self.on_mouse_pos) - super().__init__(**kwargs) - - def on_mouse_pos(self, *args): - if not self.get_root_window(): - return # do proceed if I'm not displayed <=> If I have no parent - pos = args[1] - # Next line to_widget allowed to compensate for relative layout - inside = self.collide_point(*self.to_widget(*pos)) - if self.hovered == inside: - # We have already done what was needed - return - self.border_point = pos - self.hovered = inside - if inside: - self.dispatch('on_enter') - else: - self.dispatch('on_leave') - - """ - BLANK METHODS - Overwritten by Extend Class, e.g. Button - """ - - def dispatch(self, action): - return - - @staticmethod - def collide_point(point: float): - if point: - return True - return False - - @staticmethod - def to_widget(point: list): - return point - - @staticmethod - def get_root_window(): - return App.root_window - - @staticmethod - def register_event_type(event: str) -> None: - return diff --git a/src/gui/utils/fonts.py b/src/gui/utils/fonts.py deleted file mode 100644 index 3144be65..00000000 --- a/src/gui/utils/fonts.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -* GUI Font Utils -""" -# Standard Library Imports -import os - -# Third Party Imports -from kivy.core.text import LabelBase - - -""" -* Utility Funcs -""" - - -def get_font(name: str, default: str = "Roboto"): - """Instantiate font if exists. Otherwise, return default font. - - Args: - name: Font to look for. - default: Font to default to if 'name' can't be found. - - Returns: - Found font or default font. - """ - basename = name[0:-4] - try: - LabelBase.register(name=basename, fn_regular=name) - return basename - except OSError: - try: - LabelBase.register(name=basename, fn_regular=f"fonts/{name}") - return basename - except OSError: - try: - LabelBase.register( - name=basename, - fn_regular=f"C:\\Users\\{os.getlogin()}\\AppData\\Local\\Microsoft\\Windows\\Fonts\\{name}" - ) - return basename - except OSError: - return default diff --git a/src/gui/utils/forms.py b/src/gui/utils/forms.py deleted file mode 100644 index 89e0467f..00000000 --- a/src/gui/utils/forms.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -* GUI Utility Form Elements -""" -from threading import Thread - -# Third Party Imports -from kivy.metrics import sp -from kivy.properties import get_color_from_hex -from kivy.uix.textinput import TextInput -from kivy.uix.spinner import Spinner -from kivy.properties import StringProperty - -# Local Imports -from src.enums.mtg import Rarity -from src.gui.utils.layouts import HoverButton, get_font - -""" -* Utility Input classes -""" - - -class InputItem(TextInput): - """Track hint text in perpetuity, add QOL key binds.""" - multiline = False - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.clicked = False - self.original = self.text - - def _on_focus(self, instance, value, *args): - """Preserve hint text.""" - if not self.clicked: - self.clicked = True - self.original = self.text - super()._on_focus(instance, value, *args) - - def keyboard_on_key_down(self, window, keycode, text, modifiers): - """Enable tab to next or previous input and F5 to reset.""" - if keycode[1] == 'tab': - # Deal with tabbing between inputs - if 'shift' in modifiers: - nxt = self.get_focus_previous() - else: - nxt = self.get_focus_next() - if nxt: - self.focus = False - nxt.focus = True - return True - if keycode[0] == 286: - # F5 to reset text to hint text - self.clicked = False - self.text = self.original - super().keyboard_on_key_down(window, keycode, text, modifiers) - - -class NoEnterInputItem(InputItem): - def keyboard_on_key_down(self, window, keycode, text, modifiers): - """Disable next line.""" - if keycode[0] == 13: # deal with cycle - return False - super().keyboard_on_key_down(window, keycode, text, modifiers) - - -class ValidatedInput(InputItem): - """Limit text input based on numeric, length, and whitelisted terms.""" - def __init__(self, **kwargs): - self._max_len = int(kwargs.pop('max_len', 0)) - self._whitelist = kwargs.pop('whitelist', []) - self._numeric = bool(kwargs.pop('numeric', False)) - self._numeric_range = kwargs.pop('numeric_range', [0, 0]) - super().__init__(**kwargs) - - def insert_text(self, substring, from_undo=False) -> None: - """ - 3 character max, numeric with a small whitelist - """ - # Character length requirement - if self._max_len != 0 and len(self.text) > (self._max_len - 1): - return - - # Numeric value or whitelisted value requirement - if self._numeric and not (substring.isnumeric() or substring in self._whitelist): - return - - # Numeric value in accepted range requirement - if self._numeric and not (self._numeric_range[1] == 0 or ( - self._numeric_range[0] <= int(self.text + str(substring)) <= self._numeric_range[1] - )): - return - - # Value is validated - return super().insert_text(substring, from_undo=from_undo) - - -class FourNumInput(ValidatedInput): - """Utility definition, 4 numeric characters with whitelisted operators.""" - multiline = False - - def __init__(self, **kwargs): - super().__init__(max_len=4, numeric=True, whitelist=["*", "X", "Y", "+", "-"], **kwargs) - - -class ThreeNumInput(ValidatedInput): - """Utility definition, 3 numeric characters with whitelisted operators.""" - multiline = False - - def __init__(self, **kwargs): - super().__init__(max_len=3, numeric=True, whitelist=["*", "X", "Y", "+", "-"], **kwargs) - - -class FourCharInput(ValidatedInput): - """Utility definition, 4 of any kind of characters.""" - multiline = False - - def __init__(self, **kwargs): - super().__init__(max_len=4, **kwargs) - - -class Range100NumInput(ValidatedInput): - """Utility definition, number between 1 and 100.""" - multiline = False - - def __init__(self, **kwargs): - super().__init__(numeric=True, numeric_range=[1, 100], **kwargs) - - -class VerticalCenteredInput(ValidatedInput): - """Utility definition for vertically centering the input field.""" - padding_x = 0, 0 - - def on_size(self, *args): - """Adjust padding to achieve vertical centering.""" - left, right = self.padding_x - self.padding = left, self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), right, 0 - super().on_size(*args) - - -class SetCodeInput(VerticalCenteredInput, FourCharInput): - """QOL definition for set code string input.""" - hint_text = StringProperty('SET') - halign = 'center' - - -class CollectorInput(VerticalCenteredInput, FourNumInput): - """QOL definition for collector number string input.""" - hint_text = StringProperty('001') - halign = 'center' - - -class PTInput(VerticalCenteredInput, ThreeNumInput): - """QOL definition for power or toughness text.""" - hint_text = StringProperty('1') - halign = 'center' - - -""" -* Spinner Classes -""" - - -class RaritySpinner(Spinner): - """Select from a list of card rarities.""" - values = [n.title() for n in Rarity] - text_autoupdate = True - - -""" -* Button Classes -""" - - -class RenderCustomButton(HoverButton): - font_name = get_font('Beleren Small Caps.ttf') - font_size = sp(16) - press_action = None - options = [ - "Do it!", - "Game on!", - "Make it!", - "Let's go!" - ] - - def __init__(self, **kwargs): - bg_color = get_color_from_hex('#376aa3') - super().__init__( - background_color=bg_color, - text='Render', - **kwargs) - - def on_press(self): - """Process action in separate thread if action is defined.""" - if self.press_action is not None: - Thread(target=self.press_action, daemon=True).start() diff --git a/src/gui/utils/layouts.py b/src/gui/utils/layouts.py deleted file mode 100644 index e9a2337c..00000000 --- a/src/gui/utils/layouts.py +++ /dev/null @@ -1,224 +0,0 @@ -""" -* GUI Utility Layouts -""" -# Standard Library Imports -import random - -# Third Party Imports -from kivy.metrics import dp, sp -from kivy.logger import Logger -from kivy.clock import Clock -from kivy.compat import string_types -from kivy.factory import Factory -from kivy.properties import ObjectProperty -from kivy.uix.behaviors import ButtonBehavior -from kivy.uix.button import Button -from kivy.uix.image import Image -from kivy.uix.tabbedpanel import ( - TabbedPanelException, - TabbedPanelContent, - TabbedPanelHeader, - TabbedPanelStrip, - TabbedPanel, - StripLayout) -from kivy.utils import get_color_from_hex - -# Local Imports -from src.gui.utils.behaviors import HoverBehavior -from src.gui.utils.fonts import get_font - -""" -* Button Layouts -""" - - -class HoverButton(Button, HoverBehavior): - """Button which adopts the hover behavior modifier.""" - options = [ - "Do it!", "Let's GO!", "Ready?", - "PROXY", "Hurry up!", "GAME ON", - "Let's DUEL", "Prox it up!", "Go for it!" - ] - hover_color = "#a4c5eb" - org_text = None - org_color = None - - def __init__(self, **kwargs): - # Set the default font - self.font_name = get_font("Beleren Small Caps.ttf") - super().__init__(**kwargs) - - def on_enter(self): - """Fired when mouse enters the hover area.""" - if not self.disabled: - self.org_color = self.background_color - if len(self.options) > 0: - self.org_text = self.text - self.text = random.choice(self.options) - self.background_color = get_color_from_hex(self.hover_color) - - def on_leave(self): - """Fired when mouse leaves the hover area.""" - if self.org_color: - self.background_color = self.org_color - if len(self.options) > 0: - self.text = self.org_text - - -""" -* Image Layouts -""" - - -class ButtonImage(ButtonBehavior, Image): - """Image which adopts the hover behavior modifier.""" - pass - - -""" -* Tabbed Panel Layouts -""" - - -class DynamicTabHeader(TabbedPanelHeader): - """Replacement for 'TabbedPanelHeader'.""" - - -class DynamicTabItem(DynamicTabHeader, TabbedPanelHeader): - """Replacement for 'Tabbed Panel Item'.""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.size_hint = (None, 1) - self.font_size = sp(16) - self.bind(texture_size=self.resize_width) - - def resize_width(self, _, texture_size): - self.width = texture_size[0] + dp(20) - - -class DynamicTabPanel(TabbedPanel): - """Replacement for 'TabbedPanel'.""" - - # Overwrite the default 'TabbedPanelHeader' - default_tab_cls = ObjectProperty(DynamicTabHeader) - - def set_def_tab(self, new_tab): - if not issubclass(new_tab.__class__, DynamicTabHeader): - raise TabbedPanelException('`default_tab_class` should be' - 'subclassed from `DynamicTabHeader`') - if self._default_tab == new_tab: - return - oltab = self._default_tab - self._default_tab = new_tab - self.remove_widget(oltab) - self._original_tab = None - self.switch_to(new_tab) - new_tab.state = 'down' - - def __init__(self, **kwargs): - # these variables need to be initialized before the kv lang is - # processed set up the base layout for the tabbed panel - self._childrens = [] - self._tab_layout = StripLayout(rows=1) - self.rows = 1 - self._tab_strip = TabbedPanelStrip( - tabbed_panel=self, - rows=1, size_hint=(None, None), - height=self.tab_height, width=self.tab_width) - - self._partial_update_scrollview = None - self.content = TabbedPanelContent() - self._current_tab = self._original_tab = self._default_tab = DynamicTabHeader() - - super(TabbedPanel, self).__init__(**kwargs) - - self.fbind('size', self._reposition_tabs) - if not self.do_default_tab: - Clock.schedule_once(self._switch_to_first_tab) - return - self._setup_default_tab() - self.switch_to(self.default_tab) - - def add_widget(self, widget, *args, **kwargs): - content = self.content - if content is None: - return - parent = widget.parent - if parent: - parent.remove_widget(widget) - if widget in (content, self._tab_layout): - super(TabbedPanel, self).add_widget(widget, *args, **kwargs) - elif isinstance(widget, DynamicTabHeader): - self_tabs = self._tab_strip - self_tabs.add_widget(widget, *args, **kwargs) - widget.group = f'__tab{self_tabs.uid!r}__' - self.on_tab_width() - else: - widget.pos_hint = {'x': 0, 'top': 1} - self._childrens.append(widget) - content.disabled = self.current_tab.disabled - content.add_widget(widget, *args, **kwargs) - - def remove_widget(self, widget, *args, **kwargs): - content = self.content - if content is None: - return - if widget in (content, self._tab_layout): - super(TabbedPanel, self).remove_widget(widget, *args, **kwargs) - elif isinstance(widget, DynamicTabHeader): - if not (self.do_default_tab and widget is self._default_tab): - self_tabs = self._tab_strip - self_tabs.width -= widget.width - self_tabs.remove_widget(widget) - if widget.state == 'down' and self.do_default_tab: - self._default_tab.on_release() - self._reposition_tabs() - else: - Logger.info('DynamicTabPanel: default tab! can\'t be removed.\n' + - 'Change `default_tab` to a different tab.') - else: - if widget in self._childrens: - self._childrens.remove(widget) - if widget in content.children: - content.remove_widget(widget, *args, **kwargs) - - def _setup_default_tab(self): - if self._default_tab in self.tab_list: - return - content = self._default_tab.content - _tabs = self._tab_strip - cls = self.default_tab_cls - - if isinstance(cls, string_types): - cls = Factory.get(cls) - - if not issubclass(cls, DynamicTabHeader): - raise TabbedPanelException('`default_tab_class` should be\ - subclassed from `DynamicTabHeader`') - - # no need to instantiate if class is DynamicTabHeader - if cls != DynamicTabHeader: - # noinspection PyCallingNonCallable - self._current_tab = self._original_tab = self._default_tab = cls() - - default_tab = self.default_tab - if self._original_tab == self.default_tab: - default_tab.text = self.default_tab_text - - default_tab.height = self.tab_height - default_tab.group = f'__tab{_tabs.uid!r}__' - default_tab.state = 'down' - default_tab.width = self.tab_width if self.tab_width else 100 - default_tab.content = content - - tl = self.tab_list - if default_tab not in tl: - _tabs.add_widget(default_tab, len(tl)) - - if default_tab.content: - self.clear_widgets() - self.add_widget(self.default_tab.content) - else: - Clock.schedule_once(self._load_default_tab_content) - self._current_tab = default_tab diff --git a/src/helpers/design.py b/src/helpers/design.py index e0fe66a3..4d12ee3f 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -2,32 +2,29 @@ * Helpers: Design """ -# Standard Library Imports from contextlib import suppress +from logging import getLogger -# Third Party Imports from photoshop.api import ( - DialogModes, ActionDescriptor, - RasterizeType, ActionReference, BlendMode, - SolidColor, + DialogModes, ElementPlacement, + RasterizeType, SaveOptions, + SolidColor, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document -# Local Imports -from src import APP, CONSOLE -from src.helpers.layers import select_layers, smart_layer, edit_smart_layer -from src.helpers.colors import rgb_black, fill_layer_primary +from src import APP +from src.helpers.colors import fill_layer_primary, rgb_black +from src.helpers.layers import edit_smart_layer, select_layers, smart_layer from src.helpers.selection import select_layer_pixels from src.utils.adobe import PS_EXCEPTIONS -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs +_logger = getLogger(__name__) """ * Filling Space @@ -102,7 +99,7 @@ def content_aware_fill_edges( docref.activeLayer = layer active_layer = layer elif not isinstance((active_layer := docref.activeLayer), ArtLayer): - print("Skipping content aware fill as active layer is not an ArtLayer.") + _logger.warning("Skipping content aware fill as active layer is not an ArtLayer.") return active_layer.rasterize(RasterizeType.EntireLayer) @@ -126,7 +123,7 @@ def content_aware_fill_edges( content_aware_fill() except PS_EXCEPTIONS: # Unable to fill due to invalid selection - CONSOLE.update("Couldn't make a valid selection!\nSkipping automated fill.") + _logger.warning("Couldn't make a valid selection. Skipping automated fill.") # Clear selection with suppress(Exception): @@ -190,15 +187,15 @@ def generative_fill_edges( generative_fill() except PS_EXCEPTIONS: # Generative fill call not responding - CONSOLE.update( - "Generative fill failed!\nFalling back to Content Aware Fill." + _logger.warning( + "Generative fill failed. Falling back to Content Aware Fill." ) active_layer.rasterize(RasterizeType.EntireLayer) content_aware_fill() close_doc = True except PS_EXCEPTIONS: # Unable to fill due to invalid selection - CONSOLE.update("Couldn't make a valid selection!\nSkipping automated fill.") + _logger.warning("Couldn't make a valid selection. Skipping automated fill.") close_doc = True # Deselect @@ -228,7 +225,9 @@ def content_aware_fill() -> None: APP.instance.sID("blendMode"), APP.instance.sID("normal"), ) - APP.instance.executeAction(APP.instance.sID("fill"), desc, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("fill"), desc, DialogModes.DisplayNoDialogs + ) def generative_fill() -> None: @@ -266,7 +265,9 @@ def generative_fill() -> None: desc1.putObject( APP.instance.sID("serviceOptionsList"), APP.instance.sID("target"), desc2 ) - APP.instance.executeAction(APP.instance.sID("syntheticFill"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("syntheticFill"), desc1, DialogModes.DisplayNoDialogs + ) def repair_edges(edge: int = 6) -> None: @@ -283,16 +284,22 @@ def repair_edges(edge: int = 6) -> None: desc632724.putEnumerated( APP.instance.sID("to"), APP.instance.sID("ordinal"), APP.instance.sID("allEnum") ) - APP.instance.executeAction(APP.instance.sID("set"), desc632724, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc632724, DialogModes.DisplayNoDialogs + ) # Contract selection contract = ActionDescriptor() contract.putUnitDouble(APP.instance.sID("by"), APP.instance.sID("pixelsUnit"), edge) contract.putBoolean(APP.instance.sID("selectionModifyEffectAtCanvasBounds"), True) - APP.instance.executeAction(APP.instance.sID("contract"), contract, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("contract"), contract, DialogModes.DisplayNoDialogs + ) # Inverse the selection - APP.instance.executeAction(APP.instance.sID("inverse"), None, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("inverse"), None, DialogModes.DisplayNoDialogs + ) # Content aware fill desc_caf = ActionDescriptor() @@ -319,7 +326,9 @@ def repair_edges(edge: int = 6) -> None: APP.instance.sID("cafOutput"), APP.instance.sID("cafOutputToNewLayer"), ) - APP.instance.executeAction(APP.instance.sID("cafWorkspace"), desc_caf, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("cafWorkspace"), desc_caf, DialogModes.DisplayNoDialogs + ) # Deselect APP.instance.activeDocument.selection.deselect() diff --git a/src/helpers/document.py b/src/helpers/document.py index 676ff652..c13a5d9d 100644 --- a/src/helpers/document.py +++ b/src/helpers/document.py @@ -1,37 +1,30 @@ """ * Helpers: Documents """ - -# Standard Library Imports +from collections.abc import Callable from pathlib import Path from typing import Any -from collections.abc import Callable -# Third Party Imports from photoshop.api import ( - DialogModes, ActionDescriptor, ActionReference, - SaveOptions, - PurgeTarget, - PNGSaveOptions, - JPEGSaveOptions, - PhotoshopSaveOptions, + DialogModes, ElementPlacement, FormatOptionsType, + JPEGSaveOptions, + PhotoshopSaveOptions, + PNGSaveOptions, + PurgeTarget, + SaveOptions, ) from photoshop.api._artlayer import ArtLayer -from photoshop.api._layerSet import LayerSet from photoshop.api._document import Document +from photoshop.api._layerSet import LayerSet -# Local Imports from src import APP from src.helpers.layers import create_new_layer from src.utils.adobe import PS_EXCEPTIONS -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Document Hierarchy """ @@ -225,7 +218,7 @@ def jump_to_history_state(position: int): ref1 = ActionReference() ref1.putOffset(APP.instance.sID("historyState"), position) desc1.putReference(APP.instance.sID("target"), ref1) - APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) def toggle_history_state(direction: str = "previous") -> None: @@ -242,7 +235,7 @@ def toggle_history_state(direction: str = "previous") -> None: APP.instance.sID(direction), ) desc1.putReference(APP.instance.sID("target"), ref1) - APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) def undo_action() -> None: @@ -265,7 +258,7 @@ def reset_document(docref: Document | None = None) -> None: d1, r1 = ActionDescriptor(), ActionReference() r1.putName(APP.instance.sID("snapshotClass"), docref.name) d1.putReference(APP.instance.sID("target"), r1) - APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs) """ @@ -354,7 +347,7 @@ def trim_transparent_pixels() -> None: desc258.putBoolean(APP.instance.sID("bottom"), True) desc258.putBoolean(APP.instance.sID("left"), True) desc258.putBoolean(APP.instance.sID("right"), True) - APP.instance.executeAction(APP.instance.sID("trim"), desc258, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("trim"), desc258, DialogModes.DisplayNoDialogs) """ @@ -371,7 +364,7 @@ def save_document_png(path: Path, docref: Document | None = None) -> None: """ docref = docref or APP.instance.activeDocument png_options = PNGSaveOptions() - png_options.compression = 3 + png_options.compression = 9 png_options.interlaced = False docref.saveAs( file_path=str(path.with_suffix(".png")), options=png_options, asCopy=True @@ -439,7 +432,7 @@ def save_document_psb(path: Path) -> None: d1.putObject(APP.instance.sID("as"), APP.instance.sID("largeDocumentFormat"), d2) d1.putPath(APP.instance.sID("in"), str(path.with_suffix(".psb"))) d1.putBoolean(APP.instance.sID("lowerCase"), True) - APP.instance.executeAction(APP.instance.sID("save"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("save"), d1, DialogModes.DisplayNoDialogs) def close_document( @@ -479,7 +472,7 @@ def rotate_document(angle: int) -> None: ) desc1.putReference(APP.instance.sID("target"), ref1) desc1.putUnitDouble(APP.instance.sID("angle"), APP.instance.sID("angleUnit"), angle) - APP.instance.executeAction(APP.instance.sID("rotateEventEnum"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("rotateEventEnum"), desc1, DialogModes.DisplayNoDialogs) def rotate_counter_clockwise() -> None: @@ -517,4 +510,4 @@ def paste_to_document(layer: ArtLayer | LayerSet | None = None): APP.instance.sID("antiAliasNone"), ) desc1.putClass(APP.instance.sID("as"), APP.instance.sID("pixel")) - APP.instance.executeAction(APP.instance.sID("paste"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("paste"), desc1, DialogModes.DisplayNoDialogs) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index 53a72ea6..19f24511 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -1,14 +1,14 @@ """ * Helpers: Layer Effects """ -# Standard Library Imports -# Third Party Imports +from _ctypes import COMError +from logging import getLogger + from photoshop.api import ActionDescriptor, ActionList, ActionReference, DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports from src import APP from src.enums.adobe import Stroke from src.helpers.colors import add_color_to_gradient, apply_color, get_color @@ -21,8 +21,7 @@ LayerEffects, ) -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs +_logger = getLogger(__name__) """ * Blending Utilities @@ -54,7 +53,7 @@ def set_fill_opacity(opacity: float, layer: ArtLayer | LayerSet | None) -> None: APP.instance.sID("fillOpacity"), APP.instance.sID("percentUnit"), opacity ) d.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), d1) - APP.instance.executeAction(APP.instance.sID("set"), d, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("set"), d, DialogModes.DisplayNoDialogs) """ @@ -88,7 +87,7 @@ def set_layer_fx_visibility( action_list.putReference(ref) desc.putList(APP.instance.sID("target"), action_list) APP.instance.executeAction( - APP.instance.sID("show" if visible else "hide"), desc, NO_DIALOG + APP.instance.sID("show" if visible else "hide"), desc, DialogModes.DisplayNoDialogs ) @@ -128,12 +127,11 @@ def clear_layer_fx(layer: ArtLayer | LayerSet | None) -> None: ) desc1600.putReference(APP.instance.sID("target"), ref126) APP.instance.executeAction( - APP.instance.sID("disableLayerStyle"), desc1600, NO_DIALOG + APP.instance.sID("disableLayerStyle"), desc1600, DialogModes.DisplayNoDialogs ) - except Exception as e: - print( - e, - f"""\nLayer "{layer.name if layer else ""}" has no effects!""", + except COMError: + _logger.exception( + f"""\nLayer "{layer.name if layer else ""}" has no effects.""", ) @@ -152,7 +150,7 @@ def rasterize_layer_fx(layer: ArtLayer) -> None: APP.instance.sID("rasterizeItem"), APP.instance.sID("layerStyle"), ) - APP.instance.executeAction(APP.instance.sID("rasterizeLayer"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("rasterizeLayer"), desc1, DialogModes.DisplayNoDialogs) def copy_layer_fx( @@ -175,7 +173,7 @@ def copy_layer_fx( APP.instance.sID("layerEffects"), ) result_desc = APP.instance.executeAction( - APP.instance.sID("get"), desc_get, NO_DIALOG + APP.instance.sID("get"), desc_get, DialogModes.DisplayNoDialogs ) # Apply layer effects to target layer @@ -188,7 +186,7 @@ def copy_layer_fx( APP.instance.sID("layerEffects"), result_desc.getObjectValue(APP.instance.sID("layerEffects")), ) - APP.instance.executeAction(APP.instance.sID("set"), desc_set, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("set"), desc_set, DialogModes.DisplayNoDialogs) """ @@ -233,7 +231,7 @@ def apply_fx(layer: ArtLayer | LayerSet, effects: list[LayerEffects]) -> None: main_action.putObject( APP.instance.sID("to"), APP.instance.sID("layerEffects"), fx_action ) - APP.instance.executeAction(APP.instance.sID("set"), main_action, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("set"), main_action, DialogModes.DisplayNoDialogs) def apply_fx_bevel(action: ActionDescriptor, fx: EffectBevel) -> None: diff --git a/src/helpers/layers.py b/src/helpers/layers.py index 67083b3a..881fb5d7 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -2,35 +2,27 @@ * Helpers: Layers and Layer Groups """ -# Standard Library Imports -from contextlib import suppress -from collections.abc import Sequence, Iterable +from collections.abc import Iterable, Sequence -# Third Party Imports from photoshop.api import ( - DialogModes, ActionDescriptor, ActionReference, BlendMode, + DialogModes, ElementPlacement, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet -# Local Imports from src import APP, ENV from src.utils.adobe import ( + PS_EXCEPTIONS, LayerContainer, LayerContainerTypes, ReferenceLayer, - PS_EXCEPTIONS, ) -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - - """ * Searching Layers """ @@ -80,11 +72,10 @@ def getLayer( # Layer couldn't be found if ENV.DEV_MODE: print(f'Layer "{name}" could not be found!') - if group and isinstance(group, LayerSet): + if isinstance(group, LayerSet): print(f"LayerSet reference used: {group.name}") elif group and isinstance(group, str): print(f"LayerSet reference used: {group}") - return def getLayerSet( @@ -131,11 +122,10 @@ def getLayerSet( # LayerSet couldn't be found if ENV.DEV_MODE: print(f'LayerSet "{name}" could not be found!') - if group and isinstance(group, LayerSet): + if isinstance(group, LayerSet): print(f"LayerSet reference used: {group.name}") elif group and isinstance(group, str): print(f"LayerSet reference used: {group}") - return def get_reference_layer( @@ -153,19 +143,22 @@ def get_reference_layer( time on bounds and dimensions handling. """ - # Select the proper group if str or None provided - if not group: - group = APP.instance.activeDocument - if isinstance(group, str): - try: - group = APP.instance.activeDocument.layerSets[group] - except PS_EXCEPTIONS: + try: + # Select the proper group if str or None provided + if not group: group = APP.instance.activeDocument + elif isinstance(group, str): + group = APP.instance.activeDocument.layerSets[group] - # Select the reference layer - with suppress(Exception): + # Select the reference layer return ReferenceLayer(parent=group.artLayers.app[name], app=APP.instance) - return None + except PS_EXCEPTIONS: + if ENV.DEV_MODE: + print(f'ReferenceLayer "{name}" could not be found!') + if isinstance(group, LayerSet): + print(f"LayerSet reference used: {group.name}") + elif group and isinstance(group, str): + print(f"LayerSet reference used: {group}") """ @@ -218,7 +211,7 @@ def merge_layers( select_layers(layers) # Merge layers and return result - APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): @@ -269,7 +262,7 @@ def group_layers( desc1.putInteger(APP.instance.sID("layerSectionStart"), 0) desc1.putInteger(APP.instance.sID("layerSectionEnd"), 1) desc1.putString(APP.instance.cID("Nm "), name) - APP.instance.executeAction(APP.instance.cID("Mk "), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.cID("Mk "), desc1, DialogModes.DisplayNoDialogs) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): @@ -301,7 +294,7 @@ def duplicate_group(group: LayerSet, name: str) -> LayerSet: desc241.putReference(APP.instance.sID("target"), ref4) desc241.putString(APP.instance.sID("name"), name) desc241.putInteger(APP.instance.sID("version"), 5) - APP.instance.executeAction(APP.instance.sID("duplicate"), desc241, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("duplicate"), desc241, DialogModes.DisplayNoDialogs) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): @@ -320,7 +313,7 @@ def merge_group(group: LayerSet | None = None) -> None: """ if group: APP.instance.activeDocument.activeLayer = group - APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs) """ @@ -340,7 +333,7 @@ def smart_layer( docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer - APP.instance.executeAction(APP.instance.sID("newPlacedLayer"), None, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("newPlacedLayer"), None, DialogModes.DisplayNoDialogs) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): @@ -364,7 +357,7 @@ def edit_smart_layer( docref = docref or APP.instance.activeDocument docref.activeLayer = layer APP.instance.executeAction( - APP.instance.sID("placedLayerEditContents"), None, NO_DIALOG + APP.instance.sID("placedLayerEditContents"), None, DialogModes.DisplayNoDialogs ) @@ -381,7 +374,7 @@ def unpack_smart_layer( docref = docref or APP.instance.activeDocument docref.activeLayer = layer APP.instance.executeAction( - APP.instance.sID("placedLayerConvertToLayers"), None, NO_DIALOG + APP.instance.sID("placedLayerConvertToLayers"), None, DialogModes.DisplayNoDialogs ) @@ -405,7 +398,7 @@ def lock_layer(layer: ArtLayer | LayerSet, protection: str = "protectAll") -> No d2.putBoolean(APP.instance.sID(protection), True) idlayerLocking = APP.instance.sID("layerLocking") d1.putObject(idlayerLocking, idlayerLocking, d2) - APP.instance.executeAction(APP.instance.sID("applyLocking"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("applyLocking"), d1, DialogModes.DisplayNoDialogs) def unlock_layer(layer: ArtLayer | LayerSet) -> None: @@ -434,7 +427,7 @@ def select_layer(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None r1.putIdentifier(APP.instance.sID("layer"), layer.id) d1.putReference(APP.instance.sID("target"), r1) d1.putBoolean(APP.instance.sID("makeVisible"), make_visible) - APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs) def select_layer_add(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None: @@ -454,7 +447,7 @@ def select_layer_add(layer: ArtLayer | LayerSet, make_visible: bool = False) -> APP.instance.sID("addToSelection"), ) desc1.putBoolean(APP.instance.sID("makeVisible"), make_visible) - APP.instance.executeAction(APP.instance.sID("select"), desc1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: @@ -484,13 +477,13 @@ def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: d1.putReference(idTarget, r1) d1.putEnumerated(idSelMod, idSelModType, idAddToSel) d1.putBoolean(APP.instance.sID("makeVisible"), False) - APP.instance.executeAction(idSelect, d1, NO_DIALOG) + APP.instance.executeAction(idSelect, d1, DialogModes.DisplayNoDialogs) # Select each additional layer for lay in layers[1:]: r1.putIdentifier(idLayer, lay.id) d1.putReference(idTarget, r1) - APP.instance.executeAction(idSelect, d1, NO_DIALOG) + APP.instance.executeAction(idSelect, d1, DialogModes.DisplayNoDialogs) def select_no_layers() -> None: @@ -502,4 +495,4 @@ def select_no_layers() -> None: APP.instance.sID("targetEnum"), ) d1.putReference(APP.instance.sID("target"), r1) - APP.instance.executeAction(APP.instance.sID("selectNoLayers"), d1, NO_DIALOG) + APP.instance.executeAction(APP.instance.sID("selectNoLayers"), d1, DialogModes.DisplayNoDialogs) diff --git a/src/helpers/text.py b/src/helpers/text.py index b59aadb5..ae1b95fe 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -2,16 +2,15 @@ * Helpers: Text Items """ -# Standard Library Imports -from typing import Literal, overload from collections.abc import Sequence +from logging import getLogger +from typing import Literal, overload -# Third Party Imports from photoshop.api import ( - DialogModes, ActionDescriptor, - ActionReference, ActionList, + ActionReference, + DialogModes, LayerKind, ) from photoshop.api._artlayer import ArtLayer @@ -19,7 +18,6 @@ from photoshop.api._layerSet import LayerSet from photoshop.api.text_item import TextItem -# Local Imports from src import APP from src.helpers.bounds import ( get_layer_height, @@ -30,8 +28,7 @@ from src.helpers.document import pixels_to_points from src.utils.adobe import PS_EXCEPTIONS -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs +_logger = getLogger(__name__) """ * Text Utils @@ -70,7 +67,9 @@ def apply_text_key(text_layer: ArtLayer, text_key: ActionDescriptor) -> None: ref.putIdentifier(APP.instance.sID("layer"), text_layer.id) action.putReference(APP.instance.sID("target"), ref) action.putObject(APP.instance.sID("to"), APP.instance.sID("textLayer"), text_key) - APP.instance.executeAction(APP.instance.sID("set"), action, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), action, DialogModes.DisplayNoDialogs + ) def get_line_count(layer: ArtLayer, docref: Document | None = None) -> int: @@ -110,10 +109,7 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: # Check if our target text exists if find not in current_text: - print( - f"Text replacement couldn't find the text '{find}' " - f"in layer with name '{layer.name}'!" - ) + _logger.warning(f"Couldn't find the text '{find}' in layer: '{layer.name}'") return # Reusable ID's @@ -144,10 +140,7 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: # Skip applying changes if no replacement could be made if not replaced: - print( - f"Text replacement couldn't find the text '{find}' " - f"in layer with name '{layer.name}'!" - ) + _logger.warning(f"Couldn't replace the text '{find}' in layer: {layer.name}") return # Apply changes @@ -188,8 +181,8 @@ def replace_text_legacy( APP.instance.sID("targetEnum"), ) desc31.putReference(APP.instance.sID("target"), ref3) - desc32.putString(APP.instance.sID("find"), f"""{find}""") - desc32.putString(APP.instance.sID("replace"), f"""{replace}""") + desc32.putString(APP.instance.sID("find"), find) + desc32.putString(APP.instance.sID("replace"), replace) desc32.putBoolean( APP.instance.sID( "checkAll" @@ -204,7 +197,7 @@ def replace_text_legacy( desc32.putBoolean(APP.instance.sID("ignoreAccents"), True) desc31.putObject(APP.instance.sID("using"), idFindReplace, desc32) try: - APP.instance.executeAction(idFindReplace, desc31, NO_DIALOG) + APP.instance.executeAction(idFindReplace, desc31, DialogModes.DisplayNoDialogs) except PS_EXCEPTIONS: replace_text_legacy( find=find, replace=replace, layer=layer, targeted_replace=False @@ -446,7 +439,9 @@ def set_space_after(space: int | float) -> None: APP.instance.sID("spaceAfter"), APP.instance.sID("pointsUnit"), space ) desc1.putObject(APP.instance.sID("to"), APP.instance.sID("paragraphStyle"), deesc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def set_text_leading(layer: ArtLayer, leading: float | int) -> None: @@ -466,7 +461,9 @@ def set_text_leading(layer: ArtLayer, leading: float | int) -> None: APP.instance.sID("leading"), APP.instance.sID("pointsUnit"), leading ) desc1.putObject(APP.instance.sID("to"), APP.instance.sID("textStyle"), desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def set_text_size(layer: ArtLayer, size: float | int) -> None: @@ -485,7 +482,9 @@ def set_text_size(layer: ArtLayer, size: float | int) -> None: desc1.putReference(APP.instance.sID("target"), ref1) desc2.putUnitDouble(APP.instance.sID("size"), APP.instance.sID("pointsUnit"), size) desc1.putObject(APP.instance.sID("to"), textStyle, desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def set_text_size_and_leading( @@ -509,7 +508,9 @@ def set_text_size_and_leading( desc2.putUnitDouble(APP.instance.sID("size"), ptUnit, size) desc2.putUnitDouble(APP.instance.sID("leading"), ptUnit, leading) desc1.putObject(APP.instance.sID("to"), textStyle, desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def set_composer(layer: ArtLayer, every: bool = False) -> None: @@ -529,7 +530,9 @@ def set_composer(layer: ArtLayer, every: bool = False) -> None: desc1.putReference(APP.instance.sID("target"), ref1) desc2.putBoolean(APP.instance.sID("textEveryLineComposer"), every) desc1.putObject(APP.instance.sID("to"), paraStyle, desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def set_composer_single_line(layer: ArtLayer) -> None: diff --git a/src/layouts.py b/src/layouts.py index 4302c86b..86395420 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1,37 +1,35 @@ """ * Card Layout Data """ + import re -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from datetime import date from functools import cached_property -from os import path as osp +from itertools import batched +from logging import getLogger from pathlib import Path from pprint import pprint from re import Match -from typing import Any, NotRequired, TypedDict, Union +from typing import Any, NotRequired, TypedDict from omnitils.strings import get_line, get_lines, normalize_str, strip_lines -from src import CFG, CON, CONSOLE, ENV, PATH -from src._state import HexproofSet -from src.cards import ( - CardDetails, - FrameDetails, - get_card_data, - parse_card_info, - process_card_data, -) -from src.console import msg_error, msg_success, msg_warn +from src import CFG, CON +from src._config import AppConfig +from src._state import PATH, HexproofSet +from src.cards import CardDetails, FrameDetails, get_card_data, process_card_data from src.enums.layers import LAYERS from src.enums.mtg import ( CardTextPatterns, CardTypes, CardTypesSuper, + LayoutCategory, LayoutScryfall, LayoutType, Rarity, TransformIcons, + layout_map_types, planeswalkers_tall, ) from src.enums.settings import CollectorMode, WatermarkMode @@ -52,6 +50,8 @@ get_cards_oracle, ) +_logger = getLogger(__name__) + class PowerToughness(TypedDict): power: str @@ -63,31 +63,39 @@ class ProtoDetails(TypedDict): mana_cost: str pt: str + class PlaneswalkerAbility(TypedDict): text: str icon: str cost: str + class SagaLine(TypedDict): text: str icons: list[str] + class ClassLine(TypedDict): cost: str | None level_translation: str level: str text: str + """ * Layout Processing """ -def assign_layout(filename: Path) -> "str | CardLayout": +def assign_layout( + card: CardDetails, + scryfall_override: ScryfallCard | None = None, +) -> NormalLayout | None: """Assign layout object to a card. Args: - filename (Path): Path to the art file, filename supports optional tags. + filename: Path to the art file, filename supports optional tags. + name_override: Card name to use instead of the filename without suffix Filename Tags: | Tag | Description | @@ -98,42 +106,43 @@ def assign_layout(filename: Path) -> "str | CardLayout": | `$creator` | Must be at the end of filename, indicates the creator. | Returns: - str | CardLayout: Layout object for this card. + Layout object for this card or None if no layout can be assigned. """ - # Get basic card information - card = parse_card_info(filename) - name_failed = osp.basename(str(card.get('file', 'None'))) + name_failed = card["file"].name # Get scryfall data for the card - scryfall = get_card_data(card, cfg=CFG, logger=CONSOLE) + scryfall = scryfall_override or get_card_data(card, cfg=CFG) if not scryfall: - return msg_error(name_failed, reason="Scryfall search failed") - - if CFG.manually_edit_card_data: + _logger.error(f"Scryfall search failed for {name_failed}") + return + + if not scryfall_override and CFG.manually_edit_card_data: try: scryfall = manually_modify_model(scryfall, CFG.manual_text_editor) - except Exception as e: - CONSOLE.log_exception(e) - return msg_error(name_failed, reason="Manual card data modification failed") + except Exception: + _logger.exception( + f"Manual card data modification failed for {name_failed}" + ) + return scryfall = process_card_data(scryfall, card) # Instantiate layout object if scryfall.layout in layout_map: try: - layout = layout_map[scryfall.layout](scryfall, card) - except Exception as e: + layout = layout_map[scryfall.layout](scryfall, card, CFG) + except Exception: # Couldn't instantiate layout object - CONSOLE.log_exception(e) - return msg_error(name_failed, reason="Layout generation failed") - if not ENV.TEST_MODE: - CONSOLE.update(f"{msg_success('FOUND:')} {str(layout)}") + _logger.error(f"Layout generation failed for {name_failed}") + return return layout # Couldn't find an appropriate layout - return msg_error(name_failed, reason="Layout incompatible") + _logger.error(f"Couldn't find an appropriate layout for {name_failed}") -def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]) -> list["str | CardLayout"]: +def join_dual_card_layouts[T: NormalLayout | None]( + layouts: Iterable[T], +) -> list[NormalLayout | T]: """Join any layout objects that are dual sides of the same card, i.e. Split cards. Args: @@ -142,20 +151,18 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]) -> list["str Returns: List of layouts, with split layouts joined. """ - # Check if we have any split cards - normal: list[str | CardLayout] = [ - n for n in layouts - if isinstance(n, str) - or not isinstance(n, SplitLayout)] - split: list[SplitLayout] = [ - n for n in layouts - if isinstance(n, SplitLayout)] + normal: list[NormalLayout | T] = [ + n for n in layouts if not isinstance(n, SplitLayout) + ] + split: list[SplitLayout] = [n for n in layouts if isinstance(n, SplitLayout)] + + # Return early if no split layouts are present if not split: return normal # Join any identical split cards - skip: list[SplitLayout] = [] - add: list[SplitLayout] = [] + skip: list[NormalLayout] = [] + add: list[NormalLayout] = [] for card in split: if card in skip: continue @@ -164,12 +171,14 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]) -> list["str continue if str(c) == str(card): # Order them according to name position - card.art_files = [*card.art_files, *c.art_files] if ( - normalize_str(card.name[0]) == normalize_str(card.file['name']) - ) else [*c.art_files, *card.art_files] + card.art_files = ( + [*card.art_files, *c.art_files] + if (normalize_str(card.name[0]) == normalize_str(card.file["name"])) + else [*c.art_files, *card.art_files] + ) skip.extend([card, c]) add.append(card) - return [*normal, *add] + return normal + add """ @@ -179,34 +188,42 @@ def join_dual_card_layouts(layouts: list[Union[str, 'CardLayout']]) -> list["str class NormalLayout: """Defines unified properties for all cards and serves as the layout for any M15 style typical card.""" + + def __init__(self, scryfall: ScryfallCard, file: CardDetails, config: AppConfig): + # Establish core properties + self._file = file + self._scryfall = scryfall + self.config = config + + def __str__(self): + """String representation of the card layout object.""" + return ( + f"{self.name}" + f"{f' [{self.set}]' if self.set else ''}" + f"{f' {{{self.collector_number_raw}}}' if self.collector_number else ''}" + ) + + def _get_card_name(self, data: ScryfallCard | ScryfallCardFace) -> str: + return ( + data.printed_name if self.is_alt_lang and data.printed_name else data.name + ) + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Normal - # Static properties + @cached_property + def category(self) -> LayoutCategory: + return layout_map_types[self.type] + @cached_property def is_transform(self) -> bool: return False - + @cached_property def is_mdfc(self) -> bool: return False - def __init__(self, scryfall: ScryfallCard, file: CardDetails): - - # Establish core properties - self._file = file - self._scryfall = scryfall - - def __str__(self): - """String representation of the card layout object.""" - return (f"{self.name}" - f"{f' [{self.set}]' if self.set else ''}" - f"{f' {{{self.collector_number_raw}}}' if self.collector_number else ''}") - - def _get_card_name(self, data: ScryfallCard | ScryfallCardFace) -> str: - return data.printed_name if self.is_alt_lang and data.printed_name else data.name - """ * Core Data """ @@ -224,12 +241,12 @@ def scryfall(self) -> ScryfallCard: @cached_property def template_file(self) -> Path: """Template PSD file path, replaced before render process.""" - return PATH.TEMPLATES / 'normal.psd' + return PATH.TEMPLATES / "normal.psd" @cached_property def art_file(self) -> Path: """Path: Art image file path.""" - return Path(self.file['file']) + return Path(self.file["file"]) @cached_property def scryfall_scan(self) -> str: @@ -279,14 +296,13 @@ def card(self) -> ScryfallCard | ScryfallCardFace: break if not matching_face: - face_listing = '\n'.join(f" - {face.name}" for face in faces) - CONSOLE.update( - msg_warn( - f"""None of the card faces -{face_listing} -matches the input file's name '{self.input_name}'. -Defaulting to first face.""" - ) + face_listing = "\n".join(f" - {face.name}" for face in faces) + _logger.warning( + f"None of the card faces
{ + face_listing + }
matches the input file's name { + self.input_name + }.
Defaulting to first face." ) return faces[0] @@ -351,7 +367,7 @@ def display_name(self) -> str: @cached_property def input_name(self) -> str: """Card name, version provided in art file name.""" - return self.file['name'] + return self.file["name"] @cached_property def mana_cost(self) -> str: @@ -361,7 +377,11 @@ def mana_cost(self) -> str: @cached_property def oracle_text(self) -> str: """Card rules text, supports alternate language source.""" - return self.card.printed_text if self.is_alt_lang and self.card.printed_text else self.oracle_text_raw + return ( + self.card.printed_text + if self.is_alt_lang and self.card.printed_text + else self.oracle_text_raw + ) @cached_property def oracle_text_raw(self) -> str: @@ -376,7 +396,7 @@ def flavor_text(self) -> str: @cached_property def rules_text(self) -> str: """Utility definition comprised of rules and flavor text as available.""" - return (self.oracle_text or '') + (self.flavor_text or '') + return (self.oracle_text or "") + (self.flavor_text or "") @cached_property def power(self) -> str: @@ -395,7 +415,11 @@ def toughness(self) -> str: @cached_property def type_line(self) -> str: """Card type line, supports alternate language source.""" - return self.card.printed_type_line if self.is_alt_lang and self.card.printed_type_line else self.type_line_raw + return ( + self.card.printed_type_line + if self.is_alt_lang and self.card.printed_type_line + else self.type_line_raw + ) @cached_property def type_line_raw(self) -> str: @@ -405,7 +429,7 @@ def type_line_raw(self) -> str: @cached_property def types_raw(self) -> list[str]: """List of types extracted from the raw typeline.""" - return self.type_line_raw.replace(' —', '').split(' ') + return self.type_line_raw.replace(" —", "").split(" ") @cached_property def types(self) -> list[str]: @@ -421,9 +445,9 @@ def supertypes(self) -> list[str]: def subtypes(self) -> list[str]: """Subtypes represented, e.g. Elf, Human, Goblin, etc.""" return [ - n for n in self.types_raw - if n not in self.supertypes - and n not in self.types + n + for n in self.types_raw + if n not in self.supertypes and n not in self.types ] """ @@ -449,8 +473,8 @@ def color_indicator(self) -> str: @cached_property def symbol_code(self) -> str: """Code used to match a symbol to this card's set. Provided by hexproof.io.""" - if CFG.symbol_force_default: - return CFG.symbol_default.upper() + if self.config.symbol_force_default: + return self.config.symbol_default.upper() if self.set_data: return self.set_data.code_symbol.upper() return "DEFAULT" @@ -463,9 +487,11 @@ def lang(self) -> str: @cached_property def rarity(self) -> str: """Card rarity, interprets 'special' rarities based on card data.""" - return self.rarity_raw if self.rarity_raw in [ - Rarity.C, Rarity.U, Rarity.R, Rarity.M, Rarity.T - ] else get_special_rarity(self.rarity_raw, self.scryfall) + return ( + self.rarity_raw + if self.rarity_raw in [Rarity.C, Rarity.U, Rarity.R, Rarity.M, Rarity.T] + else get_special_rarity(self.rarity_raw, self.scryfall) + ) @cached_property def rarity_raw(self) -> str: @@ -480,17 +506,17 @@ def rarity_letter(self) -> str: @cached_property def artist(self) -> str: """Card artist name, prioritizes user provided artist name. Controls for duplicate last names.""" - if self.file.get('artist'): - return self.file['artist'] + if self.file.get("artist"): + return self.file["artist"] artist = self.card.artist count: set[str] = set() # Check for duplicate last names - if isinstance(artist, str) and '&' in artist: - for w in artist.split(' '): + if isinstance(artist, str) and "&" in artist: + for w in artist.split(" "): count.add(w) - return ' '.join(count) + return " ".join(count) return artist or "Unknown" @@ -498,7 +524,9 @@ def artist(self) -> str: def collector_number(self) -> int: """int: Card number assigned within release set. Non-digit characters are ignored, falls back to 0.""" if self.collector_number_raw: - return int(''.join(char for char in self.collector_number_raw if char.isdigit())) + return int( + "".join(char for char in self.collector_number_raw if char.isdigit()) + ) return 0 @cached_property @@ -511,7 +539,10 @@ def card_count(self) -> int | None: """int | None: Number of cards within the card's release set. Only required in 'Normal' Collector Mode.""" # Skip if collector mode doesn't require it or if collector number is bad - if CFG.collector_mode != CollectorMode.Normal or not self.collector_number_raw: + if ( + self.config.collector_mode != CollectorMode.Normal + or not self.collector_number_raw + ): return # Prefer printed count, fallback to card count, skip if count isn't found @@ -529,12 +560,12 @@ def collector_data(self) -> str: return f"{str(self.collector_number).zfill(3)}/{str(self.card_count).zfill(3)} {self.rarity_letter}" if self.collector_number_raw: return f"{self.rarity_letter} {str(self.collector_number).zfill(4)}" - return '' + return "" @cached_property def creator(self) -> str: """str: Optional creator string provided by user in art file name.""" - return self.file.get('creator', '') + return self.file.get("creator", "") """ * Symbols @@ -543,32 +574,45 @@ def creator(self) -> str: @cached_property def symbol_svg(self) -> Path | None: """SVG path definition for card's expansion symbol.""" - if CFG.symbol_force_rarity: - path = (PATH.SRC_IMG_SYMBOLS / 'set' / self.set / CFG.symbol_force_rarity).with_suffix('.svg') + if self.config.symbol_force_rarity: + path = ( + PATH.SRC_IMG_SYMBOLS + / "set" + / self.set + / self.config.symbol_force_rarity + ).with_suffix(".svg") if path.is_file(): return path # If code is default, perform replacement and check if we have a local asset first - if self.symbol_code == 'DEFAULT': - if not CFG.symbol_force_default: - path = (PATH.SRC_IMG_SYMBOLS / 'set' / self.set / self.rarity_letter).with_suffix('.svg') + if self.symbol_code == "DEFAULT": + if not self.config.symbol_force_default: + path = ( + PATH.SRC_IMG_SYMBOLS / "set" / self.set / self.rarity_letter + ).with_suffix(".svg") if path.is_file(): return path - self.symbol_code = CFG.symbol_default.upper() + self.symbol_code = self.config.symbol_default.upper() # Does SVG exist? - path = (PATH.SRC_IMG_SYMBOLS / 'set' / self.symbol_code / self.rarity_letter).with_suffix('.svg') + path = ( + PATH.SRC_IMG_SYMBOLS / "set" / self.symbol_code / self.rarity_letter + ).with_suffix(".svg") if path.is_file(): return path # Revert to mythic for special rarities if self.rarity not in [Rarity.C, Rarity.U, Rarity.R, Rarity.M]: - path = (PATH.SRC_IMG_SYMBOLS / 'set' / self.symbol_code / 'M').with_suffix('.svg') + path = (PATH.SRC_IMG_SYMBOLS / "set" / self.symbol_code / "M").with_suffix( + ".svg" + ) if path.is_file(): return path # Revert to default symbol or None - path = (PATH.SRC_IMG_SYMBOLS / 'set' / 'DEFAULT' / self.rarity_letter).with_suffix('.svg') + path = ( + PATH.SRC_IMG_SYMBOLS / "set" / "DEFAULT" / self.rarity_letter + ).with_suffix(".svg") if path.is_file(): return path return @@ -578,7 +622,7 @@ def watermark(self) -> str | None: """Name of the card's watermark file that is actually used, if provided.""" if not self.watermark_svg: return - if self.watermark_svg.stem.upper() == 'WM': + if self.watermark_svg.stem.upper() == "WM": return self.watermark_svg.parent.stem.lower() return self.watermark_svg.stem.lower() @@ -590,6 +634,7 @@ def watermark_raw(self) -> str | None: @cached_property def watermark_svg(self) -> Path | None: """Path to the watermark SVG file, if provided.""" + def _find_watermark_svg(wm: str) -> Path | None: """Try to find a watermark SVG asset, allowing for special cases and set code fallbacks. @@ -608,26 +653,27 @@ def _find_watermark_svg(wm: str) -> Path | None: wm = wm.lower() # Special case watermarks - if self.first_print and wm in ['set', 'symbol']: + if self.first_print and wm in ["set", "symbol"]: return get_watermark_svg_from_set( - self.first_print.set if wm == 'set' else self.set) + self.first_print.set if wm == "set" else self.set + ) # Look for normal watermark return get_watermark_svg(wm) # WatermarkMode: Disabled or Forced - if CFG.watermark_mode == WatermarkMode.Disabled: + if self.config.watermark_mode == WatermarkMode.Disabled: return - elif CFG.watermark_mode == WatermarkMode.Forced: - return _find_watermark_svg(CFG.watermark_default) + elif self.config.watermark_mode == WatermarkMode.Forced: + return _find_watermark_svg(self.config.watermark_default) # WatermarkMode: Automatic path = _find_watermark_svg(self.watermark_raw) if self.watermark_raw else None - if path or CFG.watermark_mode == WatermarkMode.Automatic: + if path or self.config.watermark_mode == WatermarkMode.Automatic: return path # WatermarkMode: Fallback - return _find_watermark_svg(CFG.watermark_default) + return _find_watermark_svg(self.config.watermark_default) @cached_property def watermark_basic(self) -> Path | None: @@ -637,14 +683,15 @@ def watermark_basic(self) -> Path | None: # Map pinlines to basic land type _map = { - 'W': 'plains', - 'U': 'island', - 'B': 'swamp', - 'R': 'mountain', - 'G': 'forest', - 'Land': 'wastes'} + "W": "plains", + "U": "island", + "B": "swamp", + "R": "mountain", + "G": "forest", + "Land": "wastes", + } if basic_type := _map.get(self.pinlines): - return (PATH.SRC_IMG_SYMBOLS / 'watermark' / basic_type).with_suffix('.svg') + return (PATH.SRC_IMG_SYMBOLS / "watermark" / basic_type).with_suffix(".svg") return """ @@ -659,44 +706,44 @@ def is_creature(self) -> bool: @cached_property def is_land(self) -> bool: """True if card is a Land.""" - return 'Land' in self.type_line_raw + return "Land" in self.type_line_raw @cached_property def is_basic_land(self) -> bool: """True if card is a Basic Land.""" - return self.type_line_raw.startswith('Basic') + return self.type_line_raw.startswith("Basic") @cached_property def is_legendary(self) -> bool: """True if card is Legendary.""" - return 'Legendary' in self.type_line_raw + return "Legendary" in self.type_line_raw @cached_property def is_colorless(self) -> bool: """True if card is colorless or devoid.""" - return self.frame['is_colorless'] + return self.frame["is_colorless"] @cached_property def is_hybrid(self) -> bool: """True if card is a hybrid frame.""" - return self.frame['is_hybrid'] + return self.frame["is_hybrid"] @cached_property def is_artifact(self) -> bool: """True if card is an Artifact.""" - return 'Artifact' in self.type_line_raw + return "Artifact" in self.type_line_raw @cached_property def is_vehicle(self) -> bool: """True if card is a Vehicle.""" - return 'Vehicle' in self.type_line_raw + return "Vehicle" in self.type_line_raw @cached_property def is_promo(self) -> bool: """True if card is a promotional print.""" if self.scryfall.promo: return True - if self.set_type == 'promo': + if self.set_type == "promo": return True if self.promo_types: return True @@ -710,7 +757,7 @@ def is_front(self) -> bool: @cached_property def is_alt_lang(self) -> bool: """True if language selected isn't English.""" - return CFG.use_printed_texts or bool(self.lang != 'EN') + return self.config.use_printed_texts or bool(self.lang != "EN") """ * Cosmetic Bool @@ -719,18 +766,18 @@ def is_alt_lang(self) -> bool: @cached_property def is_token(self) -> bool: """bool: True if card is a Token or Emblem.""" - return bool('Token' in self.type_line_raw or self.is_emblem) + return bool("Token" in self.type_line_raw or self.is_emblem) @cached_property def is_emblem(self) -> bool: """bool: True on card is an Emblem.""" - return bool('Emblem' in self.type_line_raw) + return bool("Emblem" in self.type_line_raw) @cached_property def is_nyx(self) -> bool: """True if card has Nyx enchantment background texture.""" # Check for 'Enchantment Creature' - return bool(self.is_creature and 'Enchantment' in self.type_line_raw) + return bool(self.is_creature and "Enchantment" in self.type_line_raw) @cached_property def is_companion(self) -> bool: @@ -745,7 +792,7 @@ def is_miracle(self) -> bool: @cached_property def is_snow(self) -> bool: """True if card is a 'Snow' card.""" - return bool('Snow' in self.type_line_raw) + return bool("Snow" in self.type_line_raw) """ * Frame Details @@ -759,22 +806,22 @@ def frame(self) -> FrameDetails: @cached_property def twins(self) -> str: """Identity of the name and title boxes.""" - return self.frame['twins'] + return self.frame["twins"] @cached_property def pinlines(self) -> str: """Identity of the pinlines.""" - return self.frame['pinlines'] + return self.frame["pinlines"] @cached_property def background(self) -> str: """Identity of the background.""" - return self.frame['background'] + return self.frame["background"] @cached_property def identity(self) -> str: """Frame appropriate color identity of the card.""" - return self.frame['identity'] + return self.frame["identity"] """ * Opposing Face Properties @@ -787,7 +834,7 @@ def transform_icon(self) -> str: if effect in TransformIcons: return effect # Cover special Havengul Lab case - if self.scryfall.oracle_id == 'e71ac446-02a4-4468-8d29-f28b21617665': + if self.scryfall.oracle_id == "e71ac446-02a4-4468-8d29-f28b21617665": return TransformIcons.UPSIDEDOWN # All other cards use the triangle (originally: `convertdfc`) return TransformIcons.CONVERT @@ -807,7 +854,7 @@ def other_face_frame(self) -> FrameDetails | dict[str, Any]: @cached_property def other_face_twins(self) -> str: """Name and title box identity of opposing face.""" - return self.other_face_frame.get('twins', '') + return self.other_face_frame.get("twins", "") @cached_property def other_face_mana_cost(self) -> str: @@ -861,29 +908,30 @@ def other_face_toughness(self) -> str: @cached_property def other_face_left(self) -> str: """Abridged type of the opposing side to display on bottom MDFC bar.""" - return self.other_face_type_line_raw.split(' ')[-1] if self.other_face else '' + return self.other_face_type_line_raw.split(" ")[-1] if self.other_face else "" @cached_property def other_face_right(self) -> str: """Mana cost or mana ability of opposing side, depending on land or nonland.""" if not self.other_face: - return '' + return "" # Other face is not a land - if 'Land' not in self.other_face_type_line_raw: + if "Land" not in self.other_face_type_line_raw: return self.other_face_mana_cost # Other face is a land, find the mana tap ability - for line in self.other_face_oracle_text.split('\n'): - if line.startswith('{T}'): + for line in self.other_face_oracle_text.split("\n"): + if line.startswith("{T}"): return f"{line.split('.')[0]}." return self.other_face_oracle_text class MutateLayout(NormalLayout): """Mutate card layout introduced in Ikoria: Lair of Behemoths.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Mutate """ @@ -893,7 +941,11 @@ def card_class(self) -> str: @cached_property def oracle_text_unprocessed(self) -> str: """str: Unaltered text to split between oracle and mutate.""" - return self.card.printed_text if self.is_alt_lang and self.card.printed_text else self.oracle_text_raw + return ( + self.card.printed_text + if self.is_alt_lang and self.card.printed_text + else self.oracle_text_raw + ) @cached_property def oracle_text(self) -> str: @@ -912,8 +964,9 @@ def mutate_text(self) -> str: class PrototypeLayout(NormalLayout): """Prototype card layout, introduced in The Brothers' War.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Prototype """ @@ -922,7 +975,7 @@ def card_class(self) -> str: @cached_property def oracle_text(self) -> str: - return self.proto_details['oracle_text'] + return self.proto_details["oracle_text"] """ * Prototype Properties @@ -931,23 +984,25 @@ def oracle_text(self) -> str: @cached_property def proto_details(self) -> ProtoDetails: """Returns dictionary containing prototype data and separated oracle text.""" - proto_text, rules_text = self.card.oracle_text.split("\n", 1) if self.card.oracle_text else ("", "") + proto_text, rules_text = ( + self.card.oracle_text.split("\n", 1) if self.card.oracle_text else ("", "") + ) match = CardTextPatterns.PROTOTYPE.match(proto_text) return { - 'oracle_text': rules_text, - 'mana_cost': match[1] if match else "", - 'pt': match[2] if match else "" + "oracle_text": rules_text, + "mana_cost": match[1] if match else "", + "pt": match[2] if match else "", } @cached_property def proto_mana_cost(self) -> str: """Mana cost of card when case with Prototype Ability.""" - return self.proto_details['mana_cost'] + return self.proto_details["mana_cost"] @cached_property def proto_pt(self) -> str: """Power/Toughness of card when cast with Prototype ability.""" - return self.proto_details['pt'] + return self.proto_details["pt"] """ * Prototype Colors @@ -956,14 +1011,16 @@ def proto_pt(self) -> str: @cached_property def proto_color(self) -> str: """Color to use for colored prototype frame elements.""" - return self.color_identity[0] if len(self.color_identity) > 0 else LAYERS.ARTIFACT + return ( + self.color_identity[0] if len(self.color_identity) > 0 else LAYERS.ARTIFACT + ) class PlaneswalkerLayout(NormalLayout): """Planeswalker card layout introduced in Lorwyn block.""" @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Planeswalker """ @@ -974,7 +1031,9 @@ def card_class(self) -> str: def oracle_text(self) -> str: """Fix Scryfall's minus character.""" if self.is_alt_lang: - return (self.card.printed_text or self.card.oracle_text or "").replace("\u2212", "-") + return (self.card.printed_text or self.card.oracle_text or "").replace( + "\u2212", "-" + ) return self.oracle_text_raw @cached_property @@ -1008,20 +1067,18 @@ def pw_abilities(self) -> list[PlaneswalkerAbility]: # Process alternate language lines if needed if self.is_alt_lang and self.card.printed_text: - # Separate alternate language lines - alt_lines = self.oracle_text.split('\n') + alt_lines = self.oracle_text.split("\n") new_lines: list[str] = [] for line in lines: - # Ensure number of breaks matches alternate text - breaks = line.count('\n') + 1 + breaks = line.count("\n") + 1 if not alt_lines or not (len(alt_lines) >= breaks): new_lines = lines break # Slice and add lines - new_lines.append('\n'.join(alt_lines[:breaks])) + new_lines.append("\n".join(alt_lines[:breaks])) alt_lines = alt_lines[breaks:] # Replace with alternate language lines @@ -1031,17 +1088,21 @@ def pw_abilities(self) -> list[PlaneswalkerAbility]: abilities: list[PlaneswalkerAbility] = [] for i, line in enumerate(lines): index = en_lines[i].find(": ") - abilities.append({ - # Activated ability - 'text': line[index + 1:].lstrip(), - 'icon': en_lines[i][0], - 'cost': en_lines[i][0:index] - } if 5 > index > 0 else { - # Static ability - 'text': line, - 'icon': "", - 'cost': "" - }) + abilities.append( + { + # Activated ability + "text": line[index + 1 :].lstrip(), + "icon": en_lines[i][0], + "cost": en_lines[i][0:index], + } + if 5 > index > 0 + else { + # Static ability + "text": line, + "icon": "", + "cost": "", + } + ) return abilities @@ -1054,9 +1115,13 @@ def is_transform(self) -> bool: return True @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: """Card class separated by card face.""" - return LayoutType.PlaneswalkerTransformFront if self.is_front else LayoutType.PlaneswalkerTransformBack + return ( + LayoutType.PlaneswalkerTransformFront + if self.is_front + else LayoutType.PlaneswalkerTransformBack + ) class PlaneswalkerMDFCLayout(PlaneswalkerLayout): @@ -1068,9 +1133,13 @@ def is_mdfc(self) -> bool: return True @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: """Card class separated by card face.""" - return LayoutType.PlaneswalkerMDFCFront if self.is_front else LayoutType.PlaneswalkerMDFCBack + return ( + LayoutType.PlaneswalkerMDFCFront + if self.is_front + else LayoutType.PlaneswalkerMDFCBack + ) class TransformLayout(NormalLayout): @@ -1082,7 +1151,7 @@ def is_transform(self) -> bool: return True @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: """Card class separated by card face, also supports special Ixalan lands.""" return LayoutType.TransformFront if self.is_front else LayoutType.TransformBack @@ -1096,7 +1165,7 @@ def is_mdfc(self) -> bool: return True @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: """Card class separated by card face.""" return LayoutType.MDFCFront if self.is_front else LayoutType.MDFCBack @@ -1109,15 +1178,16 @@ def oracle_text(self) -> str: """On MDFC alternate language data contains text from both sides, separate them.""" if self.is_alt_lang and self.card.printed_text: return get_lines( - self.card.printed_text, - self.oracle_text_raw.count('\n') + 1) + self.card.printed_text, self.oracle_text_raw.count("\n") + 1 + ) return self.oracle_text_raw class AdventureLayout(NormalLayout): """Adventure card layout, introduced in Throne of Eldraine.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Adventure """ @@ -1129,7 +1199,9 @@ def adventure(self) -> ScryfallCardFace: """Card object for adventure side.""" if self.scryfall.card_faces: return self.scryfall.card_faces[1] - raise ValueError(f"Scryfall data doesn't have a card face for Adventure side: {pprint(self.scryfall)}") + raise ValueError( + f"Scryfall data doesn't have a card face for Adventure side: {pprint(self.scryfall)}" + ) """ * Adventure Text @@ -1171,7 +1243,9 @@ def flavor_text_adventure(self) -> str: @cached_property def color_identity_adventure(self) -> list[str]: """Colors present in the adventure side mana cost.""" - return [n for n in get_ordered_colors(get_mana_cost_colors(self.mana_adventure))] + return [ + n for n in get_ordered_colors(get_mana_cost_colors(self.mana_adventure)) + ] @cached_property def adventure_colors(self) -> str: @@ -1187,8 +1261,9 @@ def adventure_colors(self) -> str: class LevelerLayout(NormalLayout): """Leveler card layout, introduced in Rise of the Eldrazi.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Leveler """ @@ -1203,43 +1278,44 @@ def leveler_match(self) -> Match[str] | None: @cached_property def level_up_text(self) -> str: """Main text that defines the 'Level up' cost.""" - return self.leveler_match[1] if self.leveler_match else '' + return self.leveler_match[1] if self.leveler_match else "" @cached_property def middle_level(self) -> str: """Level number(s) requirement of middle stage, e.g. 1-2.""" - return self.leveler_match[2] if self.leveler_match else '' + return self.leveler_match[2] if self.leveler_match else "" @cached_property def middle_power_toughness(self) -> str: """Creature Power/Toughness applied at middle stage.""" - return self.leveler_match[3] if self.leveler_match else '' + return self.leveler_match[3] if self.leveler_match else "" @cached_property def middle_text(self) -> str: """Rules text applied at middle stage.""" - return self.leveler_match[4] if self.leveler_match else '' + return self.leveler_match[4] if self.leveler_match else "" @cached_property def bottom_level(self) -> str: """Level number(s) requirement of bottom stage, e.g. 5+.""" - return self.leveler_match[5] if self.leveler_match else '' + return self.leveler_match[5] if self.leveler_match else "" @cached_property def bottom_power_toughness(self) -> str: """Creature Power/Toughness applied at bottom stage.""" - return self.leveler_match[6] if self.leveler_match else '' + return self.leveler_match[6] if self.leveler_match else "" @cached_property def bottom_text(self) -> str: """Rules text applied at bottom stage.""" - return self.leveler_match[7] if self.leveler_match else '' + return self.leveler_match[7] if self.leveler_match else "" class SagaLayout(NormalLayout): """Saga card layout, introduced in Dominaria.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Saga _chapter_regex = re.compile(r"^[ ,IVXLCDM]+ — ") @@ -1261,13 +1337,11 @@ def is_transform(self) -> bool: def saga_text(self) -> str: """Text comprised of Saga ability lines.""" return "\n".join( - - text - for text in self.oracle_text.splitlines() - if self._chapter_regex.match(text) - + text + for text in self.oracle_text.splitlines() + if self._chapter_regex.match(text) ) - + @cached_property def ability_text(self) -> str: """ @@ -1287,7 +1361,7 @@ def ability_text(self) -> str: @cached_property def saga_description(self) -> str: """Description at the top of the Saga card""" - if CFG.remove_reminder: + if self.config.remove_reminder: return "" line = get_line(self.oracle_text, 0) return "" if self._chapter_regex.match(line) else line @@ -1300,27 +1374,24 @@ def saga_lines(self) -> list[SagaLine]: # Full ability line if "—" in line: icons, text = line.split("—", 1) - abilities.append({ - "text": text.strip(), - "icons": icons.strip().split(", ") - }) + abilities.append( + {"text": text.strip(), "icons": icons.strip().split(", ")} + ) continue # Static text line, "Greatest Show in the Multiverse" if i == 0: - abilities.append({ - 'text': line, - 'icons': [] - }) + abilities.append({"text": line, "icons": []}) continue # Part of a previous ability line - abilities[-1]['text'] += f"\n{line}" + abilities[-1]["text"] += f"\n{line}" return abilities class ClassLayout(NormalLayout): """Class card layout, introduced in Adventures in the Forgotten Realms.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Class """ @@ -1332,46 +1403,53 @@ def class_text(self) -> str: """Text comprised of class ability lines.""" return ( self.oracle_text - if CFG.remove_reminder + if self.config.remove_reminder else strip_lines(self.oracle_text, 1) ) @cached_property def class_description(self) -> str: """Description at the top of the Class card.""" - return "" if CFG.remove_reminder else get_line(self.oracle_text, 0) + return "" if self.config.remove_reminder else get_line(self.oracle_text, 0) @cached_property def class_lines(self) -> list[ClassLine]: """Unpack class text into list of dictionaries containing ability levels, cost, and text.""" # Initial class ability - initial, *lines = self.class_text.split('\n') + initial, *lines = self.class_text.split("\n") # Non-English Class cards have extra //Level_X// after first line of each level - lines = [line for line in lines if not CardTextPatterns.CLASS_NON_ENGLISH.match(line)] - abilities: list[ClassLine] = [{'text': initial, 'cost': None, 'level': "1", "level_translation": ""}] + lines = [ + line for line in lines if not CardTextPatterns.CLASS_NON_ENGLISH.match(line) + ] + abilities: list[ClassLine] = [ + {"text": initial, "cost": None, "level": "1", "level_translation": ""} + ] # Add level-up abilities - for line in ["\n".join(lines[i:i + 2]) for i in range(0, len(lines), 2)]: + for line in ["\n".join(lines[i : i + 2]) for i in range(0, len(lines), 2)]: # Try to match this line to a class ability details = CardTextPatterns.CLASS.match(line) if details and len(details.groups()) >= 3: - abilities.append({ - 'cost': details[1], - 'level_translation': details[2], - 'level': details[3], - 'text': details[4] - }) + abilities.append( + { + "cost": details[1], + "level_translation": details[2], + "level": details[3], + "text": details[4], + } + ) continue # Otherwise add line to the previous ability - abilities[-1]['text'] += f'\n{line}' + abilities[-1]["text"] += f"\n{line}" return abilities - + class CaseLayout(NormalLayout): """Case card layout, introduced in Murders at Karlov Manor.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Case @cached_property @@ -1382,8 +1460,9 @@ def case_lines(self) -> list[str]: class BattleLayout(TransformLayout): """Battle card layout, introduced in March of the Machine.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Battle """ @@ -1398,15 +1477,17 @@ def defense(self) -> str: class PlanarLayout(NormalLayout): """Planar card layout, introduced in Planechase block.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Planar class SplitLayout(NormalLayout): """Split card layout, introduced in Invasion.""" + @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Split # Static properties @@ -1447,9 +1528,11 @@ def power(self) -> str: return "" def __str__(self): - return (f"{self.display_name}" - f"{f' [{self.set}]' if self.set else ''}" - f"{f' {{{self.collector_number}}}' if self.collector_number else ''}") + return ( + f"{self.display_name}" + f"{f' [{self.set}]' if self.set else ''}" + f"{f' {{{self.collector_number}}}' if self.collector_number else ''}" + ) """ * Core Data @@ -1462,7 +1545,7 @@ def art_file(self) -> Path: @cached_property def art_files(self) -> list[Path]: """list[Path]: Two image files, second is appended during render process.""" - return [Path(self.file['file'])] + return [Path(self.file["file"])] @cached_property def display_name(self) -> str: @@ -1499,7 +1582,7 @@ def color_indicator(self) -> str: @cached_property def watermark(self) -> str | None: return self.watermarks[0] - + @cached_property def watermarks(self) -> list[str | None]: """Name of the card's watermark file that is actually used, if provided.""" @@ -1507,7 +1590,7 @@ def watermarks(self) -> list[str | None]: for wm in self.watermark_svgs: if not wm: watermarks.append(None) - elif wm.stem.upper() == 'WM': + elif wm.stem.upper() == "WM": watermarks.append(wm.parent.stem.lower()) else: watermarks.append(wm.stem.lower()) @@ -1516,12 +1599,12 @@ def watermarks(self) -> list[str | None]: @cached_property def watermark_raw(self) -> str | None: return self.watermarks_raw[0] - + @cached_property def watermarks_raw(self) -> list[str | None]: """Name of the card's watermark from raw Scryfall data, if provided.""" return [c.watermark or "" for c in self.cards] - + @cached_property def watermark_svg(self) -> Path | None: return self.watermark_svgs[0] @@ -1529,6 +1612,7 @@ def watermark_svg(self) -> Path | None: @cached_property def watermark_svgs(self) -> list[Path | None]: """Path to the watermark SVG file, if provided.""" + def _find_watermark_svg(wm: str) -> Path | None: """Try to find a watermark SVG asset, allowing for special cases and set code fallbacks. @@ -1547,9 +1631,12 @@ def _find_watermark_svg(wm: str) -> Path | None: wm = wm.lower() # Special case watermarks - if wm in ['set', 'symbol']: + if wm in ["set", "symbol"]: return get_watermark_svg_from_set( - self.first_print.set if wm == 'set' and self.first_print else self.set) + self.first_print.set + if wm == "set" and self.first_print + else self.set + ) # Look for normal watermark return get_watermark_svg(wm) @@ -1557,26 +1644,22 @@ def _find_watermark_svg(wm: str) -> Path | None: # Find a watermark SVG for each face watermarks: list[Path | None] = [] for w in self.watermarks_raw: - if CFG.watermark_mode == WatermarkMode.Disabled: + if self.config.watermark_mode == WatermarkMode.Disabled: # Disabled Mode watermarks.append(None) continue - elif CFG.watermark_mode == WatermarkMode.Forced: + elif self.config.watermark_mode == WatermarkMode.Forced: # Forced Mode - watermarks.append( - _find_watermark_svg( - CFG.watermark_default)) + watermarks.append(_find_watermark_svg(self.config.watermark_default)) continue else: # Automatic Mode path = _find_watermark_svg(w) if w else None - if path or CFG.watermark_mode == WatermarkMode.Automatic: + if path or self.config.watermark_mode == WatermarkMode.Automatic: watermarks.append(path) continue # Fallback Mode - watermarks.append( - _find_watermark_svg( - CFG.watermark_default)) + watermarks.append(_find_watermark_svg(self.config.watermark_default)) return watermarks """ @@ -1596,7 +1679,7 @@ def names(self) -> list[str]: def name_raw(self) -> str: """Sanitized card name to use for rendered image file.""" return f"{self.name[0]} _ {self.name[1]}" - + @cached_property def type_line(self) -> str: return self.type_lines[0] @@ -1607,7 +1690,7 @@ def type_lines(self) -> list[str]: if self.is_alt_lang: return [c.printed_type_line or c.type_line or "" for c in self.cards] return [c.type_line or "" for c in self.cards] - + @cached_property def mana_cost(self) -> str: return self.mana_costs[0] @@ -1616,7 +1699,7 @@ def mana_cost(self) -> str: def mana_costs(self) -> list[str]: """Both side mana costs.""" return [c.mana_cost or "" for c in self.cards] - + @cached_property def oracle_text(self) -> str: return self.oracle_texts[0] @@ -1627,19 +1710,20 @@ def oracle_texts(self) -> list[str]: text: list[str] = [] for t in [ c.printed_text or c.oracle_text or "" - if self.is_alt_lang else c.oracle_text or "" + if self.is_alt_lang + else c.oracle_text or "" for c in self.cards ]: - if 'Fuse' in self.keywords: + if "Fuse" in self.keywords: # Remove Fuse if present in oracle text data - t = ''.join(t.split('\n')[:-1]) + t = "".join(t.split("\n")[:-1]) elif self.shared_reminder: # Remove shared reminder if present - t = t[0:-len(self.shared_reminder)].rstrip() + t = t[0 : -len(self.shared_reminder)].rstrip() text.append(t) return text - + @cached_property def flavor_text(self) -> str: return self.flavor_texts[0] @@ -1648,13 +1732,14 @@ def flavor_text(self) -> str: def flavor_texts(self) -> list[str]: """Both sides flavor text.""" return [c.flavor_text or "" for c in self.cards] - + @cached_property def shared_reminder(self) -> str: prev_match: str = "" for oracle_text in [ c.printed_text or c.oracle_text or "" - if self.is_alt_lang else c.oracle_text or "" + if self.is_alt_lang + else c.oracle_text or "" for c in self.cards ]: match = CardTextPatterns.TEXT_REMINDER_ENDING.match(oracle_text) @@ -1671,25 +1756,25 @@ def shared_reminder(self) -> str: @cached_property def is_hybrid(self) -> bool: return self.hybrid_checks[0] - + @cached_property def hybrid_checks(self) -> list[bool]: """Both sides hybrid check.""" - return [f['is_hybrid'] for f in self.frames] + return [f["is_hybrid"] for f in self.frames] @cached_property def is_colorless(self) -> bool: return self.colorless_checks[0] - + @cached_property def colorless_checks(self) -> list[bool]: """Both sides colorless check.""" - return [f['is_colorless'] for f in self.frames] + return [f["is_colorless"] for f in self.frames] """ * Frame Details """ - + @cached_property def frames(self) -> list[FrameDetails]: """Both sides frame data.""" @@ -1698,38 +1783,38 @@ def frames(self) -> list[FrameDetails]: @cached_property def pinlines(self) -> str: return get_ordered_colors(self.color_identity) - + @cached_property def pinline_identities(self) -> list[str]: """Both sides pinlines identity.""" - return [f['pinlines'] for f in self.frames] + return [f["pinlines"] for f in self.frames] @cached_property def twins(self) -> str: return self.pinlines - + @cached_property def twins_identities(self) -> list[str]: """Both sides twins identity.""" - return [f['twins'] for f in self.frames] + return [f["twins"] for f in self.frames] @cached_property def background(self) -> str: return self.pinlines - + @cached_property def backgrounds(self) -> list[str]: """Both sides background identity.""" - return [f['background'] for f in self.frames] + return [f["background"] for f in self.frames] @cached_property def identity(self) -> str: return self.pinlines - + @cached_property def identities(self) -> list[str]: """Both sides frame accurate color identity.""" - return [f['identity'] for f in self.frames] + return [f["identity"] for f in self.frames] class TokenLayout(NormalLayout): @@ -1748,10 +1833,10 @@ def display_name(self) -> str: def collector_data(self) -> str: """str: Formatted collector info line, rarity letter is always T, e.g. 050/230 T.""" if self.card_count: - return f'{str(self.collector_number).zfill(3)}/{str(self.card_count).zfill(3)} T' + return f"{str(self.collector_number).zfill(3)}/{str(self.card_count).zfill(3)} T" if self.collector_number_raw: - return f'T {str(self.collector_number).zfill(4)}' - return '' + return f"T {str(self.collector_number).zfill(4)}" + return "" @cached_property def set(self) -> str: @@ -1765,7 +1850,10 @@ def card_count(self) -> int | None: """Optional[int]: Use token count for token cards.""" # Skip if collector mode doesn't require it or if collector number is bad - if CFG.collector_mode != CollectorMode.Normal or not self.collector_number_raw: + if ( + self.config.collector_mode != CollectorMode.Normal + or not self.collector_number_raw + ): return if self.set_data: @@ -1781,9 +1869,16 @@ class StationDetails(TypedDict): class StationLayout(NormalLayout): _pt_pattern = re.compile(r"([0-9]+)/([0-9]+)") + _station_level_pattern = re.compile(r"\n([0-9]+\+) \| ") + + @cached_property + def _station_levels_start_idx(self) -> int: + if match := self._station_level_pattern.search(self.oracle_text_unprocessed): + return match.start() + return -1 @cached_property - def card_class(self) -> str: + def type(self) -> LayoutType: return LayoutType.Station @cached_property @@ -1798,32 +1893,33 @@ def oracle_text_unprocessed(self) -> str: @cached_property def oracle_text(self) -> str: """Oracle text with station levels stripped.""" - stations_start = self.oracle_text_unprocessed.index("\nSTATION ") - return self.oracle_text_unprocessed[0:stations_start] + if self._station_levels_start_idx > -1: + return self.oracle_text_unprocessed[0 : self._station_levels_start_idx] + return self.oracle_text_unprocessed @cached_property def stations(self) -> list[StationDetails]: - stations_start = self.oracle_text_unprocessed.index("\nSTATION ") - station_splits = self.oracle_text_unprocessed[stations_start:].split("STATION ") out: list[StationDetails] = [] - for split in station_splits: - if stripped := split.strip(): - lines = stripped.split("\n") - - details: StationDetails = {"requirement": lines.pop(0), "ability": ""} - for line in lines: - if match := self._pt_pattern.match(line): - details["pt"] = { - "power": match[1], - "toughness": match[2] + if self._station_levels_start_idx > -1: + station_splits = self._station_level_pattern.split( + self.oracle_text_unprocessed[self._station_levels_start_idx :] + ) + if not station_splits[0]: + station_splits.pop(0) + for requirement, ability in batched(station_splits, n=2): + if isinstance(requirement, str) and isinstance(ability, str): + out.append( + { + "requirement": requirement.strip(), + "ability": ability.strip(), } - else: - details["ability"] += line + "\n" + ) - details["ability"] = details["ability"].strip() + if self.power and self.toughness: + last = out[-1] + last["pt"] = {"power": self.power, "toughness": self.toughness} - out.append(details) return out @@ -1833,30 +1929,27 @@ def stations(self) -> list[StationDetails]: """All card layout classes.""" CardLayout = ( - NormalLayout| - TransformLayout| - ModalDoubleFacedLayout| - AdventureLayout| - LevelerLayout| - SagaLayout| - MutateLayout| - PrototypeLayout| - ClassLayout| - SplitLayout| - PlanarLayout| - TokenLayout + NormalLayout + | TransformLayout + | ModalDoubleFacedLayout + | AdventureLayout + | LevelerLayout + | SagaLayout + | MutateLayout + | PrototypeLayout + | ClassLayout + | SplitLayout + | PlanarLayout + | TokenLayout ) """Planeswalker card layout classes.""" PlaneswalkerLayouts = ( - PlaneswalkerLayout| - PlaneswalkerTransformLayout| - PlaneswalkerMDFCLayout + PlaneswalkerLayout | PlaneswalkerTransformLayout | PlaneswalkerMDFCLayout ) """Maps Scryfall layout names to their respective layout class.""" layout_map: dict[str, type[CardLayout]] = { - # Definitions supported by Scryfall natively LayoutScryfall.Normal: NormalLayout, LayoutScryfall.Split: SplitLayout, @@ -1874,13 +1967,11 @@ def stations(self) -> list[StationDetails]: LayoutScryfall.Planar: PlanarLayout, LayoutScryfall.Token: TokenLayout, LayoutScryfall.Emblem: TokenLayout, - # Definitions added to Scryfall data in postprocessing LayoutScryfall.Planeswalker: PlaneswalkerLayout, LayoutScryfall.PlaneswalkerMDFC: PlaneswalkerMDFCLayout, LayoutScryfall.PlaneswalkerTransform: PlaneswalkerTransformLayout, LayoutScryfall.Station: StationLayout, - # TODO: Supported by Scryfall, not implemented LayoutScryfall.Flip: TransformLayout, LayoutScryfall.Scheme: NormalLayout, @@ -1890,5 +1981,4 @@ def stations(self) -> list[StationDetails]: LayoutScryfall.Host: NormalLayout, LayoutScryfall.ArtSeries: TokenLayout, LayoutScryfall.ReversibleCard: TransformLayout, - } diff --git a/src/render/__init__.py b/src/render/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/render/render_queue.py b/src/render/render_queue.py new file mode 100644 index 00000000..a2d95fcb --- /dev/null +++ b/src/render/render_queue.py @@ -0,0 +1,112 @@ +from asyncio import EventLoop, Future, Task, run +from logging import getLogger +from threading import Lock, Thread +from typing import TypedDict + +from src.render.setup import RenderOperation +from src.utils.event import SubscribableEvent + +_logger = getLogger(__name__) + + +class RenderResult(TypedDict): + operation: RenderOperation + result: bool + + +class RenderQueue: + """Queue for render operations. Runs the render loop in a separate thread.""" + + _render_lock = Lock() + + def __init__(self) -> None: + self._queue: list[RenderOperation] = [] + self._queue_lock = Lock() + + self._active_operation: RenderOperation | None = None + self._cancel: bool = False + + self.queued: SubscribableEvent[tuple[RenderOperation, ...]] = ( + SubscribableEvent() + ) + self.dequeued: SubscribableEvent[tuple[RenderOperation, int]] = ( + SubscribableEvent() + ) + self.started: SubscribableEvent[RenderOperation] = SubscribableEvent() + self.finished: SubscribableEvent[RenderResult] = SubscribableEvent() + self.cancelled: SubscribableEvent[None] = SubscribableEvent() + + @property + def active_operation(self) -> RenderOperation | None: + return self._active_operation + + def enqueue(self, *operations: RenderOperation) -> None: + with self._queue_lock: + self._queue.extend(operations) + self.queued.trigger(operations) + self.execute_render() + + def dequeue(self, index: int) -> RenderOperation | None: + try: + with self._queue_lock: + self.dequeued.trigger((self._queue.pop(index), index)) + except IndexError: + return + + def clear(self) -> None: + with self._queue_lock: + for idx in reversed(range(len(self._queue))): + self.dequeued.trigger((self._queue.pop(), idx)) + + def cancel(self) -> None: + if self._active_operation: + self._cancel = True + self._active_operation.cancel() + self.cancelled.trigger(None) + + def execute_render(self) -> None: + if self._render_lock.locked(): + return + + async def loop_render(): + locked = self._render_lock.acquire(blocking=False) + if locked: + try: + try: + while not self._cancel and (next_render := self._queue.pop(0)): + self._active_operation = next_render + self.started.trigger(next_render) + result = False + try: + result = await next_render.render() + finally: + self.finished.trigger( + {"operation": next_render, "result": result} + ) + except IndexError: + return + finally: + self._active_operation = None + self._cancel = False + finally: + self._render_lock.release() + + def target(): + # Using asyncio.run without a factory starts another Qt loop, since + # Qt sets the global loop policy to it's own implementation. + # Starting another Qt loop fails, because the loop also tries to + # exec another Qt application. As a workaround, we have to specifically + # create an instance of the default non-Qt event loop. + run(loop_render(), loop_factory=EventLoop) + + Thread(target=target).start() + + +def cancel_with_render[T]( + task: Future[T] | Task[T], queue: RenderQueue, msg: str | None = None +) -> None: + def on_cancel(_: None = None) -> None: + task.cancel() + + queue.cancelled.listen_once(on_cancel) + task.add_done_callback(lambda _: queue.cancelled.remove_listener(on_cancel)) diff --git a/src/render/setup.py b/src/render/setup.py new file mode 100644 index 00000000..997a68ec --- /dev/null +++ b/src/render/setup.py @@ -0,0 +1,264 @@ +from _ctypes import COMError +from asyncio import CancelledError, Future, Task, create_task, get_running_loop +from collections.abc import Callable, Iterable, Mapping +from logging import getLogger +from time import perf_counter +from typing import TYPE_CHECKING, TypedDict + +from src import CON, ENV +from src._config import AppConfig +from src._loader import ConfigHandler, RenderableTemplate, get_template_class +from src.cards import CardDetails +from src.enums.mtg import LayoutCategory +from src.layouts import NormalLayout, assign_layout, join_dual_card_layouts +from src.templates._core import BaseTemplate +from src.utils.asynchronic import async_to_sync +from src.utils.event import SubscribableEvent +from src.utils.scryfall import ScryfallCard + +if TYPE_CHECKING: + from src.gui.qml.models.file_dialog_model import FileDialogModel + from src.gui.qml.models.message_dialog_content_model import ( + MessageDialogContentModel, + ) + +_logger = getLogger(__name__) + + +class PausedEventArgs(TypedDict): + message: str | None + + +class RenderOperation: + def __init__( + self, + template_class: type[BaseTemplate], + layout: NormalLayout, + config: ConfigHandler, + file_dialog: FileDialogModel, + message_dialog: MessageDialogContentModel, + ) -> None: + self._file_dialog = file_dialog + self._message_dialog = message_dialog + + self.template_class = template_class + self.layout = layout + self.config = config + self.template_instance: BaseTemplate | None = None + self.before_render_callback: ( + Callable[[RenderOperation, AppConfig], None] | None + ) = None + self.do_not_pause: bool = False + self.render_task: Task[bool] | None = None + self._waiting: Future[bool] | None = None + self._paused: SubscribableEvent[PausedEventArgs] = SubscribableEvent() + + @property + def paused(self): + return self._paused + + async def render(self) -> bool: + card_display_name = f"{self.layout.display_name} ({self.layout.artist}) [{self.layout.set}] {{{self.layout.collector_number_raw}}} |{self.layout.category.value}| \\{self.template_class.__name__}/" + _logger.info(f"Starting rendering of {card_display_name}") + + try: + self.layout.config = self.layout.config.copy(self.config) + CON.reload() + + self.template_instance = self.template_class(self.layout) + self.template_instance.render_operation = self + self.template_instance.file_dialog = self._file_dialog + self.template_instance.message_dialog = self._message_dialog + + if self.before_render_callback: + self.before_render_callback(self, self.layout.config) + + start_time = perf_counter() + + self.render_task = create_task(self.template_instance.execute()) + try: + try: + result = await self.render_task + except CancelledError: + result = False + + _logger.info( + f"{'Finished' if result else 'Cancelled'} rendering { + card_display_name + }
Rendering took { + round(perf_counter() - start_time, 1) + } seconds" + ) + return result + finally: + try: + self.template_instance.docref.close() + except COMError as exc: + _logger.debug( + f"Couldn't close the document for { + card_display_name + }. It might have already been closed.", + exc_info=exc, + ) + except Exception: + _logger.exception(f"Failed rendering {card_display_name}") + return False + + def cancel(self) -> None: + if self.template_instance: + self.template_instance.cancel() + if (task := self.render_task) and not task.done(): + task.get_loop().call_soon_threadsafe(task.cancel) + if (waiting := self._waiting) and not waiting.done(): + waiting.get_loop().call_soon_threadsafe(waiting.set_result, False) + + async def pause( + self, message: str | None = None, show_photoshop: bool = True + ) -> bool: + if self.do_not_pause: + return True + + if waiting := self._waiting: + return await waiting + + self._waiting = get_running_loop().create_future() + if show_photoshop and self.template_instance: + self.template_instance.app.bringToFront() + self._paused.trigger({"message": message}) + + return await self._waiting + + def pause_sync( + self, message: str | None = None, show_photoshop: bool = True + ) -> bool: + return async_to_sync(self.pause(message, show_photoshop)) + + def resume(self) -> None: + if (waiting := self._waiting) and not waiting.done(): + waiting.get_loop().call_soon_threadsafe(waiting.set_result, True) + + +def prepare_render_operations( + template_choices: RenderableTemplate | Mapping[LayoutCategory, RenderableTemplate], + assets: Iterable[CardDetails | tuple[CardDetails, ScryfallCard]], + file_dialog: FileDialogModel, + message_dialog: MessageDialogContentModel, +) -> list[RenderOperation]: + if not template_choices: + _logger.warning( + "No template choices were provided, so no cards can be rendered. Please select some installed templates in the Batch mode in order to render cards with that feature." + ) + return [] + + layouts: list[NormalLayout] = [] + layout_template_class_mapping: dict[ + NormalLayout, tuple[str, type[BaseTemplate], RenderableTemplate] + ] = {} + + for asset in assets: + if isinstance(asset, tuple): + card, scryfall_override = asset + else: + card = asset + scryfall_override = None + + if not ( + layout := assign_layout( + card, + scryfall_override=scryfall_override, + ) + ): + # assign_layout reports its own errors + continue + + if isinstance(template_choices, Mapping): + if not (template_to_use := template_choices.get(layout.category, None)): + _logger.error( + f"Skipping image { + card['file'].name + } because no template was specified for layout { + layout.category + }." + ) + continue + else: + template_to_use = template_choices + + if not ( + class_name_and_file := template_to_use.get_class_name_and_file_for_layout( + layout.type + ) + ): + _logger.error( + f"Failed to find a template class name from template { + template_to_use.name + } for layout {layout.type}." + ) + continue + + class_name, file_path = class_name_and_file + + if not file_path.is_file(): + _logger.error( + f"The PSD file {file_path} for template { + template_to_use.name + } is not available. This might be becuase you haven't downloaded it using the Template updater." + ) + continue + + # Store PSD path to layout + layout.template_file = file_path + + try: + template_class = get_template_class( + class_name, template_to_use.plugin, ENV.FORCE_RELOAD + ) + except Exception: + _logger.exception( + f"Failed to load class {class_name} for template {template_to_use.name}" + ) + continue + + if not issubclass(template_class, BaseTemplate): + _logger.error( + f"Class {class_name} of template { + template_to_use.name + } is not a subclass of BaseTemplate." + ) + continue + + layouts.append(layout) + layout_template_class_mapping[layout] = ( + class_name, + template_class, + template_to_use, + ) + + layouts = join_dual_card_layouts(layouts) + + render_operations: list[RenderOperation] = [] + + for layout in layouts: + class_name, template_class, template_to_use = layout_template_class_mapping[ + layout + ] + + if not (conf := template_to_use.get_config(class_name)): + _logger.error( + f"Failed to get config for class {class_name} of template { + template_to_use.name + }." + ) + continue + + render_operations.append( + RenderOperation( + template_class, + layout, + conf, + file_dialog=file_dialog, + message_dialog=message_dialog, + ) + ) + + return render_operations diff --git a/src/schema/adobe.py b/src/schema/adobe.py index d3197a0c..e70f0508 100644 --- a/src/schema/adobe.py +++ b/src/schema/adobe.py @@ -2,14 +2,10 @@ * Schema: Photoshop """ -# Standard Library Imports from typing import Literal -# Third Party Imports -from omnitils.schema import ArbitrarySchema from pydantic import BaseModel -# Local Imports from src.schema.colors import ColorObject, GradientColor """ @@ -66,7 +62,7 @@ class LayerDimensions(BaseModel): GradientMethod = Literal["perceptual", "linear", "classic", "smooth", "stripes"] -class EffectBevel(ArbitrarySchema): +class EffectBevel(BaseModel): """Layer Effect: Bevel""" highlight_color: ColorObject = (255, 255, 255) @@ -81,14 +77,14 @@ class EffectBevel(ArbitrarySchema): softness: float | int = 14 -class EffectColorOverlay(ArbitrarySchema): +class EffectColorOverlay(BaseModel): """Layer Effect: Color Overlay""" color: ColorObject = (0, 0, 0) opacity: float | int = 100 -class EffectDropShadow(ArbitrarySchema): +class EffectDropShadow(BaseModel): """Layer Effect: Drop Shadow""" color: ColorObject = (0, 0, 0) @@ -100,7 +96,7 @@ class EffectDropShadow(ArbitrarySchema): noise: float | int = 0 -class EffectGradientOverlay(ArbitrarySchema): +class EffectGradientOverlay(BaseModel): """Layer Effect: Drop Shadow""" colors: list[GradientColor] = [] @@ -113,7 +109,7 @@ class EffectGradientOverlay(ArbitrarySchema): method: GradientMethod = "classic" -class EffectStroke(ArbitrarySchema): +class EffectStroke(BaseModel): """Layer Effect: Stroke""" color: ColorObject = (0, 0, 0) diff --git a/src/schema/colors.py b/src/schema/colors.py index f4d898ad..5e9c89cc 100644 --- a/src/schema/colors.py +++ b/src/schema/colors.py @@ -2,19 +2,14 @@ * Schema: Colors and Gradients """ -# Standard Library Imports from collections.abc import Sequence from functools import cache -from typing import Any, TypeGuard, TypedDict +from typing import Annotated, Any, TypedDict, TypeGuard -# Third Party Imports -from omnitils.schema import ArbitrarySchema -from photoshop.api import SolidColor, CMYKColor, RGBColor, LabColor, HSBColor +from photoshop.api import CMYKColor, HSBColor, LabColor, RGBColor, SolidColor from pydantic import BaseModel, GetCoreSchemaHandler, GetJsonSchemaHandler from pydantic.json_schema import JsonSchemaValue from pydantic_core import core_schema -from typing import Annotated - """ * General Color Objects @@ -97,7 +92,7 @@ def __get_pydantic_json_schema__( ) -class GradientColor(ArbitrarySchema): +class GradientColor(BaseModel): """Defines a color within a gradient.""" color: ColorObject = (0, 0, 0) diff --git a/src/startup.py b/src/startup.py new file mode 100644 index 00000000..906c12fa --- /dev/null +++ b/src/startup.py @@ -0,0 +1,129 @@ +from logging import getLogger +from threading import Thread + +from packaging.version import InvalidVersion, parse +from pydantic import ValidationError +from requests import RequestException + +from src import CON, ENV +from src._state import PATH +from src.utils.adobe import PhotoshopHandler +from src.utils.fonts import check_app_fonts +from src.utils.github import get_github_releases +from src.utils.hexapi import get_api_key, update_hexproof_cache +from src.utils.threading import ThreadInitializedInstance + +_logger = getLogger(__name__) + + +def photoshop_checks(app: PhotoshopHandler) -> None: + # Check Photoshop connection + result = app.refresh_app() + if isinstance(result, OSError): + # Photoshop test failed + _logger.exception( + "Photoshop connection failed. Can't test fonts without Photoshop.", + exc_info=result, + ) + return + + # Photoshop test passed + _logger.info("Connected to Photoshop.") + + # Check for missing or outdated fonts + missing, outdated = check_app_fonts(app, [PATH.FONTS]) + + # Font test passed + if not missing and not outdated: + _logger.info("All essential fonts are installed.") + return + + # Missing fonts + if missing: + _logger.warning( + f"The following fonts aren't installed:
{ + '
'.join( + [details['name'] for details in missing.values() if details['name']] + ) + }" + ) + if outdated: + _logger.warning( + f"The following fonts are outdated and have to be reinstalled:
{ + '
'.join( + [ + details['name'] + for details in outdated.values() + if details['name'] + ] + ) + }" + ) + + +def check_app_version() -> None: + """Check if app is the latest version. + + Returns: + Return True if up to date, otherwise False. + """ + if ENV.APP_UPDATES_REPO: + try: + releases = get_github_releases(ENV.APP_UPDATES_REPO, per_page=1) + if len(releases) > 0: + latest = releases[0].tag_name + try: + update_available = parse(ENV.VERSION.lstrip("v")) < parse( + latest.lstrip("v") + ) + except InvalidVersion: + update_available = ENV.VERSION < latest + if update_available: + _logger.info( + f'A newer version of Proxyshop is available: { + latest + }. Download' + ) + except RequestException, ValidationError: + _logger.exception("Failed to check app version.") + + +def update_set_data() -> None: + # Update set data if needed + updated, error = update_hexproof_cache() + if updated: + CON.reload() + _logger.info("Hexproof API data update was applied.") + if error: + _logger.error(f"Failed to update Hexproof API data: {error}") + + +def check_api_keys() -> None: + # Check if API keys are valid + if not ENV.API_GOOGLE: + ENV.API_GOOGLE = get_api_key("proxyshop.google.drive") + if not ENV.API_AMAZON: + ENV.API_AMAZON = get_api_key("proxyshop.amazon.s3") + keys = { + "Google Drive": ENV.API_GOOGLE, + "Amazon S3": ENV.API_AMAZON, + } + if keys_missing := [k for k, v in keys.items() if not v]: + _logger.warning(f"Failed to retrieve API keys for: {', '.join(keys_missing)}") + else: + _logger.info(f"Retrieved keys for: {', '.join(keys)}") + + +def run_startup_checks( + photoshop_initializer: ThreadInitializedInstance[PhotoshopHandler], +) -> None: + photoshop_initializer.initialize() + if photoshop_initializer.ready: + photoshop_checks(photoshop_initializer.instance) + else: + photoshop_initializer.add_listener(photoshop_checks) + + for check in (check_app_version, update_set_data, check_api_keys): + Thread(target=check).start() diff --git a/src/templates/_core.py b/src/templates/_core.py index a5b6e422..5ccccdea 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -2,19 +2,16 @@ * CORE PROXYSHOP TEMPLATES """ -# Standard Library Imports -import os.path as osp from collections.abc import Callable, Sequence from contextlib import suppress from functools import cached_property +from logging import getLogger from pathlib import Path from threading import Event -from typing import Any, Unpack +from traceback import format_stack +from typing import TYPE_CHECKING, Any, Unpack from omnitils.files import get_unique_filename - -# Third Party Imports -from pathvalidate import sanitize_filename from photoshop.api import BlendMode, ElementPlacement, SaveOptions, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document @@ -23,11 +20,9 @@ from PIL import Image import src.helpers as psd - -# Local Imports -from src import APP, CFG, CON, CONSOLE, ENV, PATH -from src.cards import strip_reminder_text -from src.console import TerminalConsole, msg_error, msg_warn +from src import APP, CON +from src._state import PATH +from src.cards import sanitize_card_filename, strip_reminder_text from src.enums.layers import LAYERS from src.enums.mtg import CardTextPatterns, MagicIcons from src.enums.settings import ( @@ -38,7 +33,6 @@ WatermarkMode, ) from src.frame_logic import is_multicolor_string -from src.gui.console import GUIConsole from src.helpers.adjustments import CreateColorLayerKwargs from src.helpers.effects import LayerEffects from src.helpers.position import DimensionNames @@ -69,6 +63,15 @@ from src.utils.scryfall import get_card_scan from src.utils.windows import WindowState +if TYPE_CHECKING: + from src.gui.qml.models.file_dialog_model import FileDialogModel + from src.gui.qml.models.message_dialog_content_model import ( + MessageDialogContentModel, + ) + from src.render.setup import RenderOperation + +_logger = getLogger(__name__) + """ * Template Classes """ @@ -88,51 +91,9 @@ class BaseTemplate: def __init__(self, layout: NormalLayout): # Setup manual properties self.layout = layout + self.config = layout.config self._text: list[FormattedTextLayer] = [] - """ - * Template Class Routing - """ - - def __new__(cls, layout: NormalLayout) -> "BaseTemplate": - """Governs which class is called to instantiate a new object.""" - return cls.get_template_route(layout) - - @staticmethod - def redirect_template( - template_class: type["BaseTemplate"], - template_file: str | Path, - layout: NormalLayout, - ) -> "BaseTemplate": - """Reroutes template initialization to another template class and PSD file. - - Args: - template_class: Template class to reroute to. - template_file: Filename of the PSD to load with this template class. - layout: The card layout object. - - Returns: - Initialized template class object. - """ - if isinstance(template_file, Path): - layout.template_file = template_file - else: - layout.template_file = layout.template_file.with_name(template_file) - return template_class(layout) - - @classmethod - def get_template_route(cls, layout: NormalLayout) -> "BaseTemplate": - """Overwrite this method to reroute a template class to another class under a set of - conditions. See the 'IxalanTemplate' class for an example. - - Args: - layout: The card layout object. - - Returns: - Initialized template class object. - """ - return super().__new__(cls) - """ * Enabled Method Lists """ @@ -203,16 +164,23 @@ def hook_large_mana(self) -> None: * App Properties """ + @cached_property + def render_operation(self) -> RenderOperation: + raise ValueError("Render operation not assigned") + + @cached_property + def file_dialog(self) -> FileDialogModel | None: + return None + + @cached_property + def message_dialog(self) -> MessageDialogContentModel | None: + return None + @cached_property def event(self) -> Event: """Event: Threading Event used to signal thread cancellation.""" return Event() - @cached_property - def console(self) -> GUIConsole | TerminalConsole: - """type[CONSOLE]: Console output object used to communicate with the user.""" - return CONSOLE - @property def app(self) -> PhotoshopHandler: """PhotoshopHandler: Photoshop Application object used to communicate with Photoshop.""" @@ -220,8 +188,8 @@ def app(self) -> PhotoshopHandler: @cached_property def docref(self) -> Document: - """Optional[Document]: This template's document open in Photoshop.""" - if doc := psd.get_document(osp.basename(self.layout.template_file)): + """This template's document open in Photoshop.""" + if doc := psd.get_document(self.layout.template_file.name): return doc raise ValueError("Failed to get document reference for the template.") @@ -265,26 +233,22 @@ def RGB_WHITE(self) -> SolidColor: @cached_property def save_mode(self) -> Callable[[Path, Document | None], None]: """Callable: Function called to save the rendered image.""" - if CFG.output_file_type == OutputFileType.PNG: + if self.config.output_file_type == OutputFileType.PNG: return psd.save_document_png - if CFG.output_file_type == OutputFileType.PSD: + if self.config.output_file_type == OutputFileType.PSD: return psd.save_document_psd return psd.save_document_jpeg @cached_property def output_directory(self) -> Path: - """Path: Directory where the rendered image will be saved to.""" - if ENV.TEST_MODE: - path = PATH.OUT / "test_mode_renders" / self.__class__.__name__ - path.mkdir(mode=777, parents=True, exist_ok=True) - return path + """Directory where the rendered image will be saved to.""" return PATH.OUT @cached_property def output_file_name(self) -> Path: - """Path: The formatted filename for the rendered image.""" + """The formatted path for the rendered image.""" name, tag_map = ( - CFG.output_file_name, + self.config.output_file_name, { "#name": self.layout.name_raw, "#artist": self.layout.artist, @@ -313,12 +277,13 @@ def output_file_name(self) -> Path: for tag, value in tag_map.items(): if value: name = name.replace(tag, value) - path = Path(self.output_directory, sanitize_filename(name)).with_suffix( - f".{CFG.output_file_type}" + + path = Path(self.output_directory, sanitize_card_filename(name)).with_suffix( + f".{self.config.output_file_type}" ) # Are we overwriting duplicate names? - if not CFG.overwrite_duplicate: + if not self.config.overwrite_duplicate: path = get_unique_filename(path) return path @@ -479,9 +444,12 @@ def is_content_aware_enabled(self) -> bool: @cached_property def is_collector_promo(self) -> bool: """bool: Governs whether to use promo star in collector info.""" - if CFG.collector_promo == CollectorPromo.Always: + if self.config.collector_promo == CollectorPromo.Always: return True - if self.layout.is_promo and CFG.collector_promo == CollectorPromo.Automatic: + if ( + self.layout.is_promo + and self.config.collector_promo == CollectorPromo.Automatic + ): return True return False @@ -694,7 +662,7 @@ def transform_icon_layer(self) -> ArtLayer | None: def art_reference(self) -> ReferenceLayer | None: """ReferenceLayer: Reference frame used to scale and position the art layer.""" # Check if art is vertically oriented, or forced vertical, and for valid vertical frame - if self.is_art_vertical or (self.is_fullart and CFG.vertical_fullart): + if self.is_art_vertical or (self.is_fullart and self.config.vertical_fullart): if layer := psd.get_reference_layer(self.art_frame_vertical): return layer # Check for normal art frame @@ -750,14 +718,14 @@ def process_layout_data(self) -> None: """Performs any required pre-processing on the provided layout data.""" # Strip flavor text, string or list - if CFG.remove_flavor: + if self.config.remove_flavor: if isinstance(self.layout, SplitLayout): self.layout.flavor_texts = ["" for _ in self.layout.flavor_texts] else: self.layout.flavor_text = "" # Strip reminder text, string or list - if CFG.remove_reminder: + if self.config.remove_reminder: if isinstance(self.layout, SplitLayout): self.layout.oracle_texts = [ strip_reminder_text(txt) for txt in self.layout.oracle_texts @@ -799,10 +767,6 @@ def load_artwork( if not art_layer: raise ValueError("Failed to get art layer.") - # Check for full-art test image - if ENV.TEST_MODE and self.is_fullart: - art_file = PATH.SRC_IMG / "test-fa.jpg" - # Import art file if self.art_action: # Use action pipeline @@ -825,23 +789,24 @@ def load_artwork( # Perform content aware fill if needed if self.is_content_aware_enabled: # Perform a generative fill - if CFG.generative_fill: + if self.config.generative_fill: if _doc_generated := psd.generative_fill_edges( layer=art_layer, - feather=CFG.feathered_fill, - close_doc=bool(not CFG.select_variation), + feather=self.config.feathered_fill, + close_doc=bool(not self.config.select_variation), docref=self.docref, ): # Document reference was returned, await user intervention - self.console.await_choice( - self.event, - msg="Select a Generative Fill variation, then click Continue ...", + self.render_operation.pause_sync( + "Select a Generative Fill variation, then click Continue ...", ) _doc_generated.close(SaveOptions.SaveChanges) return # Perform a content aware fill - psd.content_aware_fill_edges(layer=art_layer, feather=CFG.feathered_fill) + psd.content_aware_fill_edges( + layer=art_layer, feather=self.config.feathered_fill + ) def paste_scryfall_scan( self, rotate: bool = False, visible: bool = True @@ -899,11 +864,11 @@ def collector_info(self) -> None: # Which collector info mode? if ( - CFG.collector_mode in [CollectorMode.Default, CollectorMode.Modern] + self.config.collector_mode in [CollectorMode.Default, CollectorMode.Modern] and self.layout.collector_data ): return self.collector_info_authentic() - elif CFG.collector_mode == CollectorMode.ArtistOnly: + elif self.config.collector_mode == CollectorMode.ArtistOnly: return self.collector_info_artist_only() return self.collector_info_basic() @@ -1013,10 +978,10 @@ def load_expansion_symbol(self) -> None: """Imports and positions the expansion symbol SVG image.""" # Check for expansion symbol disabled - if not CFG.symbol_enabled or not self.expansion_reference: + if not self.config.symbol_enabled or not self.expansion_reference: return if not self.layout.symbol_svg: - return self.log("Expansion symbol disabled, SVG file not found.") + return _logger.error("Expansion symbol disabled. SVG file not found.") # Try to import the expansion symbol try: @@ -1039,8 +1004,8 @@ def load_expansion_symbol(self) -> None: svg.name = "Expansion Symbol" self.expansion_symbol_layer = svg - except Exception as e: - return self.log("Expansion symbol disabled due to an error.", e) + except Exception: + _logger.exception("Expansion symbol disabled due to an error") """ * Watermark @@ -1120,7 +1085,7 @@ def create_watermark(self) -> None: ) # Apply opacity, blending, and effects - wm.opacity = wm_details.get("opacity", CFG.watermark_opacity) + wm.opacity = wm_details.get("opacity", self.config.watermark_opacity) wm.blendMode = self.watermark_blend_mode psd.apply_fx(wm, self.watermark_fx) @@ -1198,8 +1163,8 @@ def add_basic_watermark_snow_effects(self, wm: ArtLayer): @cached_property def border_color(self) -> str: """Use 'black' unless an alternate color and a valid border group is provided.""" - if CFG.border_color != BorderColor.Black and self.border_group: - return CFG.border_color + if self.config.border_color != BorderColor.Black and self.border_group: + return self.config.border_color return "black" @try_photoshop @@ -1247,10 +1212,8 @@ def check_photoshop(self) -> None: return # Connection with Photoshop couldn't be established, try again? - if not self.console.await_choice( - self.event, - get_photoshop_error_message(check), - end="Hit Continue to try again, or Cancel to end the operation.\n\n", + if not self.render_operation.pause_sync( + f"{get_photoshop_error_message(check)}\nPress OK to try again, or Cancel to end the render.", ): # Cancel the operation raise OSError(check) @@ -1263,29 +1226,16 @@ def reset(self) -> None: psd.reset_document(self.docref) except PS_EXCEPTIONS: pass - self.console.end_await() """ * Tasks and Logging """ - def log(self, text: str, e: Exception | None = None) -> None: - """Writes a message to console if test mode isn't enabled, logs an exception if provided. - - Args: - text: Message to write to console. - e: Exception to log if provided. - """ - if e: - self.console.log_exception(e) - if not ENV.TEST_MODE: - self.console.update(text) - def run_tasks( self, funcs: list[Callable[[], Any]], message: str, - warning: bool = False, + ignore_exception: bool = False, ) -> bool: """Run a list of functions, checking for thread cancellation and exceptions on each. @@ -1295,7 +1245,7 @@ def run_tasks( warning: Warn the user if True, otherwise raise error. Returns: - True if tasks completed, False if exception occurs or thread is cancelled. + True if tasks completed, False if exception occurred or thread was cancelled. """ # Execute each function @@ -1306,44 +1256,17 @@ def run_tasks( try: # Run the task func() - except Exception as e: - # Raise error or warning - if not warning: - self.raise_error(message=message, error=e) + except Exception: + _logger.exception(self.format_exception_message(message)) + if not ignore_exception: return False - self.raise_warning(message=message, error=e) # Once again, check if thread was cancelled if self.event.is_set(): return False return True - def raise_error(self, message: str, error: Exception | None = None) -> None: - """Raise an error on the console display. - - Args: - message: Message to be displayed - error: Exception object - """ - self.console.log_error( - thr=self.event, - card=self.layout.name, - template=str(self.layout.template_file), - msg=f"{msg_error(message)}\nCheck [b]/logs/error.txt[/b] for details.", - e=error, - ) - self.reset() - - def raise_warning(self, message: str, error: Exception | None = None) -> None: - """Raise a warning on the console display. - - Args: - message: Message to be displayed. - error: Exception object. - """ - if error: - self.console.log_exception(error) - message += "\nCheck [b]/logs/error.txt[/b] for details." - self.console.update(msg_warn(message), exception=error) + def format_exception_message(self, message: str) -> str: + return f"An exception occurred while rendering {self.layout.display_name}: {message}" """ * Layer Generator Utilities @@ -1541,7 +1464,9 @@ def generate_layer( # Failed to match a recognized color notation if group: - self.log(f"Couldn't generate frame element: '{group.name}'") + _logger.warning( + f"Couldn't generate layer: {group.name}\n{''.join(format_stack())}" + ) """ * Extendable Methods @@ -1572,7 +1497,10 @@ def rules_text_and_pt_layers(self) -> None: * Execution Sequence """ - def execute(self) -> bool: + def cancel(self) -> None: + self.event.set() + + async def execute(self) -> bool: """Perform actions to render the card using this template. Notes: @@ -1586,7 +1514,7 @@ def execute(self) -> bool: ): return False - if CFG.minimize_photoshop: + if self.config.minimize_photoshop: APP.instance.set_window_state(WindowState.MINIMIZE) # Pre-process layout data @@ -1609,29 +1537,29 @@ def execute(self) -> bool: return False # Load in Scryfall scan and frame it - if CFG.import_scryfall_scan: + if self.config.import_scryfall_scan: self.run_tasks( funcs=[self.paste_scryfall_scan], message="Couldn't import Scryfall scan, continuing without it!", - warning=True, + ignore_exception=True, ) # Add expansion symbol self.run_tasks( funcs=[self.load_expansion_symbol], message="Unable to generate expansion symbol!", - warning=True, + ignore_exception=True, ) # Add watermark - if CFG.enable_basic_watermark and self.is_basic_land: + if self.config.enable_basic_watermark and self.is_basic_land: # Basic land watermark if not self.run_tasks( funcs=[self.create_basic_watermark], message="Unable to generate basic land watermark!", ): return False - elif CFG.watermark_mode is not WatermarkMode.Disabled: + elif self.config.watermark_mode is not WatermarkMode.Disabled: # Normal watermark if not self.run_tasks( funcs=[self.create_watermark], message="Unable to generate watermark!" @@ -1663,8 +1591,8 @@ def execute(self) -> bool: return False # Manual edit step? - if CFG.exit_early and not ENV.TEST_MODE: - self.console.await_choice(self.event) + if self.config.exit_early: + await self.render_operation.pause("Rendering paused for manual editing.") # Save the document if not self.run_tasks( @@ -1680,11 +1608,7 @@ def execute(self) -> bool: ): return False - # Reset document, return success - if not ENV.TEST_MODE: - self.console.update( - f"[b]{self.output_file_name.stem}[/b] rendered successfully!" - ) + # Reset document self.reset() return True diff --git a/src/templates/_cosmetic.py b/src/templates/_cosmetic.py index 9cc32dc5..5c02c7ed 100644 --- a/src/templates/_cosmetic.py +++ b/src/templates/_cosmetic.py @@ -2,17 +2,14 @@ * Cosmetic Template Class Modifiers """ -# Standard Library Imports -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd +from src.enums.layers import LAYERS from src.helpers.layers import select_layer from src.templates._core import BaseTemplate from src.templates._vector import VectorTemplate @@ -373,7 +370,7 @@ def prompt_nickname_text(self) -> None: _textItem = self.text_layer_name.textItem _textItem.contents = "ENTER NICKNAME" select_layer(self.text_layer_name) - self.console.await_choice( - self.event, msg="Enter nickname text, then hit continue ..." + self.render_operation.pause_sync( + "Enter nickname text." ) self.layout.nickname = _textItem.contents diff --git a/src/templates/mutate.py b/src/templates/mutate.py index 0d754811..a95e1c20 100644 --- a/src/templates/mutate.py +++ b/src/templates/mutate.py @@ -1,21 +1,17 @@ """ * Mutate Templates """ -# Standard Library -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer -# Local Imports -from src import CFG +import src.helpers as psd +import src.text_layers as text_classes from src.cards import strip_reminder_text from src.enums.layers import LAYERS -import src.helpers as psd from src.layouts import MutateLayout from src.templates._core import NormalTemplate -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer """ @@ -65,7 +61,7 @@ def mutate_reference(self) -> ReferenceLayer | None: def process_layout_data(self) -> None: """Remove reminder text for mutate text if required.""" - if CFG.remove_reminder and isinstance(self.layout, MutateLayout): + if self.config.remove_reminder and isinstance(self.layout, MutateLayout): self.layout.mutate_text = strip_reminder_text( self.layout.mutate_text) super().process_layout_data() diff --git a/src/templates/normal.py b/src/templates/normal.py index 0402ba4a..336afb9d 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1,53 +1,48 @@ """ * Normal Templates """ - -# Standard Library Imports -from functools import cached_property from collections.abc import Callable, Iterable, Sequence +from functools import cached_property -# Third Party Imports from photoshop.api import AnchorPosition, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src import CFG, CON -from src.schema.colors import GradientConfig -from src.helpers.layers import get_reference_layer -from src.schema.adobe import EffectColorOverlay, EffectBevel -from src.templates import NicknameMod -from src.utils.adobe import ReferenceLayer -from src.enums.mtg import MagicIcons +import src.helpers as psd +from src import CON from src.enums.layers import LAYERS +from src.enums.mtg import MagicIcons from src.enums.settings import ( BorderlessColorMode, BorderlessTextbox, - ModernClassicCrown, CollectorMode, + ModernClassicCrown, ) from src.frame_logic import is_multicolor_string -from src.helpers import get_line_count, LayerEffects -import src.helpers as psd -from src.schema.colors import ColorObject, pinlines_color_map +from src.helpers import LayerEffects, get_line_count +from src.helpers.layers import get_reference_layer +from src.schema.adobe import EffectBevel, EffectColorOverlay +from src.schema.colors import ColorObject, GradientConfig, pinlines_color_map +from src.templates import NicknameMod from src.templates._core import NormalTemplate from src.templates._cosmetic import ( + CompanionMod, ExtendedMod, FullartMod, NyxMod, VectorBorderlessMod, - CompanionMod, ) from src.templates._vector import MaskAction, VectorTemplate -from src.templates.transform import VectorTransformMod from src.templates.mdfc import VectorMDFCMod +from src.templates.transform import VectorTransformMod from src.text_layers import ( - TextField, - ScaledTextField, - FormattedTextField, FormattedTextArea, + FormattedTextField, + ScaledTextField, ScaledWidthTextField, + TextField, ) +from src.utils.adobe import ReferenceLayer """ * Extendable Templates @@ -118,7 +113,7 @@ def rules_text_and_pt_layers(self) -> None: self.text_layer_rules.textItem.color = psd.rgb_white() # Make the divider white - if self.layout.flavor_text and self.layout.oracle_text and CFG.flavor_divider: + if self.layout.flavor_text and self.layout.oracle_text and self.config.flavor_divider: psd.enable_layer_fx(self.divider_layer) @@ -215,7 +210,7 @@ def template_suffix(self) -> str: @cached_property def is_dark_mode(self) -> bool: """Governs whether Dark Mode is enabled.""" - return bool(CFG.get_bool_setting("FRAME", "Dark.Mode", default=False)) + return bool(self.config.get_bool_setting("FRAME", "Dark.Mode", default=False)) """ * Methods @@ -242,7 +237,7 @@ def enable_frame_layers(self) -> None: if ( self.layout.flavor_text and self.layout.oracle_text - and CFG.flavor_divider + and self.config.flavor_divider ): psd.enable_layer_fx(self.divider_layer) @@ -265,7 +260,7 @@ def is_land(self) -> bool: def twins(self) -> str: """str: Pull twins color from settings. Silver or Bronze.""" return str( - CFG.get_setting( + self.config.get_setting( section="FRAME", key="Accent", default="Silver", is_bool=False ) ) @@ -391,17 +386,17 @@ def template_suffix(self) -> str: @cached_property def is_promo_star(self) -> bool: """bool: Whether to enable the Promo Star overlay.""" - return CFG.get_bool_setting(section="FRAME", key="Promo.Star", default=False) + return self.config.get_bool_setting(section="FRAME", key="Promo.Star", default=False) @cached_property def is_extended(self) -> bool: """bool: Whether to render using Extended Art framing.""" - return CFG.get_bool_setting(section="FRAME", key="Extended.Art", default=False) + return self.config.get_bool_setting(section="FRAME", key="Extended.Art", default=False) @cached_property def is_align_collector_left(self) -> bool: """CollectorAlign: Which collector alignment to use.""" - return CFG.get_bool_setting( + return self.config.get_bool_setting( section="TEXT", key="Collector.Align.Left", default=False ) @@ -507,11 +502,11 @@ def collector_info(self) -> None: # Which collector info mode? if ( - CFG.collector_mode in [CollectorMode.Default, CollectorMode.Modern] + self.config.collector_mode in [CollectorMode.Default, CollectorMode.Modern] and self.layout.collector_data ): self.collector_info_authentic() - elif CFG.collector_mode == CollectorMode.ArtistOnly: + elif self.config.collector_mode == CollectorMode.ArtistOnly: self.collector_info_artist_only() else: self.collector_info_basic() @@ -796,12 +791,12 @@ def twins_group(self) -> LayerSet | None: @cached_property def color_limit(self) -> int: """Supports 2 and 3 color limit setting.""" - return int(CFG.get_setting("FRAME", "Max.Colors", "3")) + 1 + return int(self.config.get_setting("FRAME", "Max.Colors", "3")) + 1 @cached_property def gold_pt(self) -> bool: """Returns True if PT for multicolored cards should be gold.""" - return CFG.get_bool_setting("FRAME", "Gold.PT", False) + return self.config.get_bool_setting("FRAME", "Gold.PT", False) """ * Bool @@ -1379,7 +1374,7 @@ def size(self) -> str: # Get the user's preferred setting size = str( - CFG.get_option( + self.config.get_option( section="FRAME", key="Textbox.Size", enum_class=BorderlessTextbox, @@ -1419,27 +1414,27 @@ def size(self) -> str: @cached_property def color_limit(self) -> int: # Built in setting, dual and triple color split - return int(CFG.get_setting(section="COLORS", key="Max.Colors", default="2")) + 1 + return int(self.config.get_setting(section="COLORS", key="Max.Colors", default="2")) + 1 @cached_property def drop_shadow_enabled(self) -> bool: """Returns True if Drop Shadow text setting is enabled.""" return bool( - CFG.get_bool_setting(section="TEXT", key="Drop.Shadow", default=True) + self.config.get_bool_setting(section="TEXT", key="Drop.Shadow", default=True) ) @cached_property def crown_texture_enabled(self) -> bool: """Returns True if Legendary crown clipping texture should be enabled.""" return bool( - CFG.get_bool_setting(section="FRAME", key="Crown.Texture", default=True) + self.config.get_bool_setting(section="FRAME", key="Crown.Texture", default=True) ) @cached_property def multicolor_textbox(self) -> bool: """Returns True if Textbox for multicolored cards should use blended colors.""" return bool( - CFG.get_bool_setting( + self.config.get_bool_setting( section="COLORS", key="Multicolor.Textbox", default=True ) ) @@ -1448,7 +1443,7 @@ def multicolor_textbox(self) -> bool: def multicolor_pinlines(self) -> bool: """Returns True if Pinlines and Crown for multicolored cards should use blended colors.""" return bool( - CFG.get_bool_setting( + self.config.get_bool_setting( section="COLORS", key="Multicolor.Pinlines", default=True ) ) @@ -1457,7 +1452,7 @@ def multicolor_pinlines(self) -> bool: def multicolor_twins(self) -> bool: """Returns True if Twins for multicolored cards should use blended colors.""" return bool( - CFG.get_bool_setting( + self.config.get_bool_setting( section="COLORS", key="Multicolor.Twins", default=False ) ) @@ -1466,21 +1461,21 @@ def multicolor_twins(self) -> bool: def multicolor_pt(self) -> bool: """Returns True if PT Box for multicolored cards should use the last color.""" return bool( - CFG.get_bool_setting(section="COLORS", key="Multicolor.PT", default=False) + self.config.get_bool_setting(section="COLORS", key="Multicolor.PT", default=False) ) @cached_property def hybrid_colored(self) -> bool: """Returns True if Twins and PT should be colored on Hybrid cards.""" return bool( - CFG.get_bool_setting(section="COLORS", key="Hybrid.Colored", default=True) + self.config.get_bool_setting(section="COLORS", key="Hybrid.Colored", default=True) ) @cached_property def front_face_colors(self) -> bool: """Returns True if lighter color map should be used on front face DFC cards.""" return bool( - CFG.get_bool_setting( + self.config.get_bool_setting( section="COLORS", key="Front.Face.Colors", default=True ) ) @@ -1489,13 +1484,13 @@ def front_face_colors(self) -> bool: def land_colorshift(self) -> bool: """Returns True if Land cards should use the darker brown color.""" return bool( - CFG.get_bool_setting(section="COLORS", key="Land.Colorshift", default=False) + self.config.get_bool_setting(section="COLORS", key="Land.Colorshift", default=False) ) @cached_property def artifact_color_mode(self) -> str: """Setting determining what elements to color for colored artifacts..""" - return CFG.get_option( + return self.config.get_option( section="COLORS", key="Artifact.Color.Mode", enum_class=BorderlessColorMode ) @@ -1533,7 +1528,7 @@ def art_frame(self) -> str: @cached_property def is_basic_land(self) -> bool: """Disable basic land watermark if Textless is enabled.""" - if bool(CFG.get_bool_setting(section="FRAME", key="Textless", default=False)): + if bool(self.config.get_bool_setting(section="FRAME", key="Textless", default=False)): return False return super().is_basic_land @@ -1544,19 +1539,19 @@ def is_textless(self) -> bool: [self.layout.oracle_text, self.layout.flavor_text, self.is_basic_land] ): return True - return CFG.get_bool_setting(section="FRAME", key="Textless", default=False) + return self.config.get_bool_setting(section="FRAME", key="Textless", default=False) @cached_property def is_nickname(self) -> bool: """Return True if this a nickname render.""" if self.layout.nickname: return True - return CFG.get_bool_setting(section="TEXT", key="Nickname", default=False) + return self.config.get_bool_setting(section="TEXT", key="Nickname", default=False) @cached_property def is_colored_nickname(self) -> bool: """Return True if nickname plate should be colored.""" - return CFG.get_bool_setting(section="COLORS", key="Nickname", default=False) + return self.config.get_bool_setting(section="COLORS", key="Nickname", default=False) @cached_property def is_multicolor(self) -> bool: @@ -1586,7 +1581,7 @@ def is_centered(self) -> bool: @cached_property def is_pt_enabled(self) -> bool: """Return True if a separate Power/Toughness text layer is used for this render.""" - return self.is_creature and (not self.is_textless or not CFG.symbol_enabled) + return self.is_creature and (not self.is_textless or not self.config.symbol_enabled) @cached_property def is_drop_shadow(self) -> bool: @@ -1784,7 +1779,7 @@ def pinlines_colors( @cached_property def pt_group(self) -> LayerSet | None: """PT Box group, alternative Textless option used when Expansion Symbol is disabled.""" - if self.is_textless and CFG.symbol_enabled: + if self.is_textless and self.config.symbol_enabled: return if self.is_textless and self.is_pt_enabled: return psd.getLayerSet(f"{LAYERS.PT_BOX} {LAYERS.TEXTLESS}") @@ -2250,7 +2245,7 @@ def textbox_positioning(self) -> None: self.text_layer_type.translate(0, delta) # Shift expansion symbol - if CFG.symbol_enabled and self.expansion_symbol_layer: + if self.config.symbol_enabled and self.expansion_symbol_layer: self.expansion_symbol_layer.translate(0, delta) # Shift indicator @@ -2402,7 +2397,7 @@ def is_content_aware_enabled(self) -> bool: @cached_property def crown_mode(self) -> str: """Whether to use pinlines when generating the Legendary Crown.""" - return CFG.get_option("FRAME", "Crown.Mode", ModernClassicCrown) + return self.config.get_option("FRAME", "Crown.Mode", ModernClassicCrown) """ * References diff --git a/src/templates/planar.py b/src/templates/planar.py index e31ad000..900571aa 100644 --- a/src/templates/planar.py +++ b/src/templates/planar.py @@ -1,20 +1,15 @@ """ * PLANAR TEMPLATES """ - -# Standard Library Imports from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer -# Local Imports -from src import CFG -from src.templates._core import StarterTemplate +import src.helpers as psd import src.text_layers as text_classes from src.enums.layers import LAYERS from src.layouts import NormalLayout -import src.helpers as psd +from src.templates._core import StarterTemplate """ * Template Classes @@ -29,8 +24,8 @@ class PlanarTemplate(StarterTemplate): """ def __init__(self, layout: NormalLayout): - CFG.exit_early = True super().__init__(layout) + self.config.exit_early = True @cached_property def text_layer_static_ability(self) -> ArtLayer | None: diff --git a/src/templates/planeswalker.py b/src/templates/planeswalker.py index c65b565f..b205294b 100644 --- a/src/templates/planeswalker.py +++ b/src/templates/planeswalker.py @@ -2,27 +2,27 @@ * PLANESWALKER TEMPLATES """ -# Standard Library Imports -from functools import cached_property from collections.abc import Callable, Sequence +from functools import cached_property +from logging import getLogger -# Third Party Imports -from photoshop.api import ElementPlacement, ColorBlendMode +from photoshop.api import ColorBlendMode, ElementPlacement from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd +import src.text_layers as text_classes +from src.enums.layers import LAYERS from src.helpers import scale_text_layers_to_height from src.layouts import NormalLayout, PlaneswalkerAbility, PlaneswalkerLayout from src.templates._core import StarterTemplate from src.templates._cosmetic import BorderlessMod, FullartMod from src.templates.mdfc import MDFCMod from src.templates.transform import TransformMod -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer +_logger = getLogger(__name__) + """ * Template Classes """ @@ -310,7 +310,13 @@ def pw_layer_positioning(self) -> None: for i, ref_layer in enumerate(self.ability_layers): # Break if we encounter a length mismatch if len(self.icons) < (i + 1) or len(self.colons) < (i + 1): - self.raise_warning("Encountered bizarre Planeswalker data!") + _logger.warning( + f"Planeswalker ability, icon and colon layers don't match. There's { + len(self.ability_layers) + } ability layers, {len(self.icons)} icon layers and { + len(self.colons) + } colon layers." + ) break # Skip if this is a static ability if (icon := self.icons[i]) and (colon := self.colons[i]): diff --git a/src/templates/prototype.py b/src/templates/prototype.py index 0bff1755..b7d83ecf 100644 --- a/src/templates/prototype.py +++ b/src/templates/prototype.py @@ -1,22 +1,17 @@ """ * Templates: Prototype """ - -# Standard Library -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src import CFG -from src.enums.layers import LAYERS import src.helpers as psd +import src.text_layers as text_classes +from src.enums.layers import LAYERS from src.layouts import PrototypeLayout from src.templates._core import NormalTemplate -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer """ @@ -183,7 +178,7 @@ def text_layers_prototype(self): ) # Remove reminder text if necessary - if CFG.remove_reminder and self.text_layer_proto: + if self.config.remove_reminder and self.text_layer_proto: self.text_layer_proto.textItem.size = ( psd.get_text_scale_factor(self.text_layer_proto) * 9 ) diff --git a/src/templates/split.py b/src/templates/split.py index c609db94..d936680e 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -3,9 +3,10 @@ """ from _ctypes import COMError +from collections.abc import Callable, Sequence from functools import cached_property +from logging import getLogger from pathlib import Path -from collections.abc import Callable, Sequence from omnitils.strings import normalize_str from photoshop.api import BlendMode, ElementPlacement @@ -13,19 +14,20 @@ from photoshop.api._layerSet import LayerSet import src.helpers as psd -from src import CFG, CON +from src import CON from src.enums.layers import LAYERS from src.helpers import LayerEffects -from src.schema.colors import GradientConfig from src.helpers.effects import copy_layer_fx from src.helpers.layers import get_reference_layer from src.layouts import SplitLayout from src.schema.adobe import EffectColorOverlay, EffectGradientOverlay -from src.schema.colors import ColorObject, GradientColor +from src.schema.colors import ColorObject, GradientColor, GradientConfig from src.templates import BaseTemplate from src.text_layers import FormattedTextArea, FormattedTextField, ScaledTextField from src.utils.adobe import ReferenceLayer +_logger = getLogger(__name__) + class SplitMod(BaseTemplate): """ @@ -64,7 +66,7 @@ def pinline_gradient_locations(self) -> list[dict[int, list[int | float]]]: @cached_property def color_limit(self) -> int: """One more than the max number of colors this card can split by.""" - return CFG.get_int_setting(section="COLORS", key="Max.Colors", default=2) + 1 + return self.config.get_int_setting(section="COLORS", key="Max.Colors", default=2) + 1 # endregion Settings @@ -370,11 +372,10 @@ def load_artworks( # Second art not provided if len(art_files) == 1: # Manually select a second art - self.console.update("Please select the second split art!") + _logger.info("Please select the second split art!") file: list[Path] = self.app.openDialog() if not file: - self.console.update("No art selected, cancelling render.") - self.console.cancel_thread(thr=self.event) + _logger.info("No art selected, cancelling render.") return # Place new art in the correct order @@ -480,7 +481,7 @@ def create_watermark(self) -> None: ) # Apply opacity, blending, and effects - wm.opacity = wm_details.get("opacity", CFG.watermark_opacity) + wm.opacity = wm_details.get("opacity", self.config.watermark_opacity) wm.blendMode = BlendMode.ColorBurn psd.apply_fx(wm, self.watermark_fxs[i]) else: diff --git a/src/templates/token.py b/src/templates/token.py index ff675244..dc4c0b7d 100644 --- a/src/templates/token.py +++ b/src/templates/token.py @@ -3,22 +3,18 @@ * Treated as 'Normal' templates, separated for better organization. """ -# Standard Library Imports -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports -from omnitils.strings import is_multiline from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports +import src.helpers as psd +import src.text_layers as text_classes from src import CON from src.enums.layers import LAYERS -import src.helpers as psd from src.templates._core import StarterTemplate from src.templates._cosmetic import FullartMod -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer """ @@ -81,7 +77,9 @@ def textbox_group(self) -> LayerSet | None: if all([self.layout.oracle_text, self.layout.flavor_text]): # Both rules and flavor text group = LAYERS.FULL - if any(is_multiline([self.layout.oracle_text, self.layout.flavor_text])): + if any( + "\n" in text for text in (self.layout.oracle_text, self.layout.flavor_text) + ): # Multi-line text group = LAYERS.FULL if len(self.layout.oracle_text) > 50: diff --git a/src/templates/transform.py b/src/templates/transform.py index 8055b4bf..20640214 100644 --- a/src/templates/transform.py +++ b/src/templates/transform.py @@ -2,20 +2,16 @@ * Templates: Transform / Ixalan """ -# Standard Library Imports -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports +import src.helpers as psd from src.enums.layers import LAYERS from src.enums.mtg import TransformIcons -import src.helpers as psd from src.helpers.position import DimensionNames -from src.layouts import NormalLayout from src.templates._core import BaseTemplate, NormalTemplate from src.templates._vector import VectorTemplate from src.text_layers import TextField @@ -252,30 +248,4 @@ def pinlines_layer(self) -> ArtLayer | None: class IxalanTemplate(IxalanMod, NormalTemplate): """Template for the back face lands for transforming cards from Ixalan block.""" - - @classmethod - def get_template_route(cls, layout: NormalLayout) -> BaseTemplate: - """Reroute for multicolor cards, front cards, and non-land and/or creature cards. - - Args: - layout: The card layout object. - - Returns: - Initialized template class object. - """ - if any( - [ - len(layout.identity) > 1, - not layout.is_land, - layout.is_front, - layout.is_creature, - ] - ): - # Redirect to regular Transform template - return cls.redirect_template( - template_class=TransformTemplate, - template_file="tf-front.psd" if layout.is_front else "tf-back.psd", - layout=layout, - ) - # Route normally - return super().get_template_route(layout) + pass diff --git a/src/text_layers.py b/src/text_layers.py index bfcb0b1b..af701042 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -2,58 +2,57 @@ * Text Layer Classes """ -# Standard Library Imports +from _ctypes import COMError from contextlib import suppress from functools import cached_property +from logging import getLogger from typing import NotRequired, TypedDict, Unpack -# Third Party Imports from photoshop.api import ( ActionDescriptor, - ActionReference, ActionList, + ActionReference, DialogModes, - LayerKind, - SolidColor, - Language, Justification, + Language, + LayerKind, RasterizeType, + SolidColor, ) -from photoshop.api._document import Document from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection from photoshop.api.text_item import TextItem -# Local Imports -from src import APP, CFG, CON, CONSOLE +from src import APP, CFG, CON from src.cards import ( - generate_italics, - locate_symbols, - locate_italics, CardItalicString, CardSymbolString, + generate_italics, + locate_italics, + locate_symbols, ) from src.enums.mtg import CardFonts from src.helpers import select_layer -from src.helpers.bounds import get_layer_dimensions, LayerDimensions, get_layer_width -from src.helpers.colors import apply_color -from src.helpers.position import position_between_layers, clear_reference_vertical +from src.helpers.bounds import LayerDimensions, get_layer_dimensions, get_layer_width +from src.helpers.colors import apply_color, rgb_black +from src.helpers.position import clear_reference_vertical, position_between_layers from src.helpers.selection import select_layer_bounds from src.helpers.text import ( get_text_scale_factor, remove_trailing_text, - scale_text_to_width_textbox, - scale_text_to_width, - scale_text_to_height, scale_text_left_overlap, scale_text_right_overlap, + scale_text_to_height, + scale_text_to_width, + scale_text_to_width_textbox, ) from src.schema.colors import ColorObject from src.utils.adobe import ReferenceLayer +from src.utils.lazy import LazyLoader -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs +_logger = getLogger(__name__) """ * Text Layer Classes @@ -151,11 +150,24 @@ def kw_symbol_map(self) -> dict[str, tuple[str, list[ColorObject]]]: """Symbol map to use for formatting mana symbols.""" if not (symbol_map := self.kwargs.get("symbol_map")): symbol_map = {} + + def get_default_color() -> SolidColor: + # For some reason fetching color from a seemingly valid TextItem can fail. + # The error goes away if the color is changed via Photoshop's GUI. + try: + return self.TI.color + except COMError: + return rgb_black() + # Use text layer's color for all None colors + default_color = LazyLoader(get_default_color) for key, value in CON.symbol_map.items(): symbol_map[key] = ( value[0], - [item if item is not None else self.TI.color for item in value[1]], + [ + item if item is not None else default_color() + for item in value[1] + ], ) return symbol_map @@ -361,7 +373,7 @@ def text_details(self) -> TextDetails: # Locate symbols and update the rules string rules, symbols = locate_symbols( - text=self.contents, symbol_map=self.kw_symbol_map, logger=CONSOLE + text=self.contents, symbol_map=self.kw_symbol_map, logger=_logger ) # Create the new input string @@ -376,7 +388,7 @@ def text_details(self) -> TextDetails: st=input_str, italics_strings=italic_text, symbol_map=self.kw_symbol_map, - logger=CONSOLE, + logger=_logger, ) # Return text details @@ -728,7 +740,9 @@ def format_text(self): ) main_target.putReference(APP.instance.sID("target"), main_ref) main_target.putObject(idTo, textLayer, main_desc) - APP.instance.executeAction(APP.instance.sID("set"), main_target, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), main_target, DialogModes.DisplayNoDialogs + ) def execute(self): super().execute() diff --git a/src/utils/adobe.py b/src/utils/adobe.py index 706e872d..fc1ff572 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -1,15 +1,14 @@ """ * Utils: Adobe Photoshop """ -# Standard Library -from _ctypes import COMError, ArgumentError + +from _ctypes import ArgumentError, COMError +from collections.abc import Callable from contextlib import suppress from ctypes import c_uint32 from functools import cache, cached_property -from typing import ParamSpec, TypeVar, Any, TypedDict -from collections.abc import Callable +from typing import TypedDict -# Third Party from packaging.version import parse from photoshop.api import ( ActionDescriptor, @@ -19,7 +18,8 @@ ElementPlacement, PhotoshopPythonAPIError, TypeUnits, - Units) + Units, +) from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop from photoshop.api._document import Document @@ -27,15 +27,16 @@ from photoshop.api._layerSet import LayerSet from win32api import FormatMessage -# Local Imports from src._state import AppEnvironment -from src.utils.windows import WindowState, get_window_handle_by_process_file_path_suffix, set_window_state +from src.utils.windows import ( + WindowState, + get_window_handle_by_process_file_path_suffix, + set_window_state, +) """ * Types & Definitions """ -T = TypeVar("T") -P = ParamSpec("P") # Common Layer Objects LayerContainer = LayerSet, Document @@ -144,9 +145,6 @@ def is_error_dialog_enabled(self) -> bool: class PhotoshopHandler(ApplicationHandler): """Wrapper for a single global Photoshop Application object equipped with soft loading, caching mechanisms, environment settings, and more.""" - DIMS_1200 = (3264, 4440) - DIMS_800 = (2176, 2960) - DIMS_600 = (1632, 2220) _instance = None _window_handle: int | None = None @@ -158,7 +156,7 @@ def window_handle(self) -> int | None: ) return self._window_handle - def __new__(cls, env: Any | None = None) -> 'PhotoshopHandler': + def __new__(cls, env: AppEnvironment | None = None) -> PhotoshopHandler: """Always return the same Photoshop Application instance on successive calls. Args: @@ -390,7 +388,7 @@ class ReferenceLayer(ArtLayer): """A static ArtLayer whose properties such as width or height are not going to change. Most often used as a reference to position or size other layers.""" - def __init__(self, parent: Any = None, app: PhotoshopHandler | None = None): + def __init__(self, parent: Photoshop | None = None, app: PhotoshopHandler | None = None): self._global_app = app if app else PhotoshopHandler() super().__init__(parent=parent) @@ -404,7 +402,7 @@ def duplicate( insertionLocation: ElementPlacement | None = None ) -> ArtLayer: """Duplicates the layer and returns it as a `ReferenceLayer` object.""" - return ReferenceLayer(self.app.duplicate(relativeObject, insertionLocation)) + return ReferenceLayer(super().duplicate(relativeObject, insertionLocation)) """ * Cached Conversions @@ -429,7 +427,7 @@ def sID(self, index: str) -> int: @cached_property def id(self) -> int: """int: This layer's ID (cached).""" - return self.app.id + return super().id @cached_property def action_getter(self) -> ActionDescriptor: @@ -449,7 +447,7 @@ def action_getter(self) -> ActionDescriptor: @cached_property def bounds(self) -> LayerBounds: """LayerBounds: Bounds of the layer (left, top, right, bottom).""" - return self.app.bounds + return super().bounds @cached_property def bounds_no_effects(self) -> LayerBounds: @@ -526,7 +524,7 @@ def get_dimensions_from_bounds(bounds: tuple[float,float,float,float]) -> LayerD """ -def try_photoshop(func: Callable[P, T]) -> Callable[P, T | None]: +def try_photoshop[**P,T](func: Callable[P, T]) -> Callable[P, T | None]: """Decorator to handle trying to run a Photoshop action but allowing exceptions to fail silently. Args: diff --git a/src/utils/asynchronic.py b/src/utils/asynchronic.py new file mode 100644 index 00000000..9b972653 --- /dev/null +++ b/src/utils/asynchronic.py @@ -0,0 +1,22 @@ +from asyncio import EventLoop, get_running_loop, run +from collections.abc import Coroutine +from concurrent.futures import ThreadPoolExecutor +from typing import Any + +_thread_pool = ThreadPoolExecutor(max_workers=4) + + +def async_to_sync[T](awaitable: Coroutine[Any, Any, T]) -> T: + return _thread_pool.submit(run, awaitable, loop_factory=EventLoop).result() + + +async def run_in_thread[T](coro: Coroutine[Any, Any, T]) -> None: + def target(): + # Using asyncio.run without a factory starts another Qt loop, since + # Qt sets the global loop policy to it's own implementation. + # Starting another Qt loop fails, because the loop also tries to + # exec another Qt application. As a workaround, we have to specifically + # create an instance of the default non-Qt event loop. + run(coro, loop_factory=EventLoop) + + get_running_loop().run_in_executor(_thread_pool, target) diff --git a/src/utils/build.py b/src/utils/build.py index cbe9a289..192a4231 100644 --- a/src/utils/build.py +++ b/src/utils/build.py @@ -1,27 +1,21 @@ """ * Utils: Building Releases and Docs """ -# Standard Library -import os + import ast +import os import zipfile from contextlib import suppress from glob import glob from pathlib import Path -from typing import TypedDict, NotRequired -from shutil import ( - copy2, - copytree, - rmtree, - move -) - -# Third party imports +from shutil import copy2, copytree, move, rmtree +from subprocess import run +from typing import NotRequired, TypedDict + import PyInstaller.__main__ -from omnitils.files import get_project_version, load_data_file, dump_data_file +from omnitils.files import dump_data_file, get_project_version, load_data_file -# Local Imports -from src import PATH +from src._state import PATH # Directory definitions SRC: Path = PATH.CWD @@ -174,9 +168,9 @@ def clear_build_files(clear_dist: bool = True) -> None: clear_dist: Remove previous dist directory if True, otherwise skip. """ # Run pyclean on main directory and venv - os.system("pyclean -v .") - if os.path.exists(os.path.join(SRC, '.venv')): - os.system("pyclean -v .venv") + run(("pyclean", "-v", "."), check=True) + if (SRC / '.venv').is_dir(): + run(("pyclean", "-v", ".venv")) # Remove build directory with suppress(Exception): diff --git a/src/utils/data_structures.py b/src/utils/data_structures.py new file mode 100644 index 00000000..a37d18e2 --- /dev/null +++ b/src/utils/data_structures.py @@ -0,0 +1,42 @@ +import tomllib +from collections.abc import Callable, Iterable +from pathlib import Path + +import yaml +from pydantic import BaseModel + + +def first[T](iterable: Iterable[T]) -> T: + return next(iter(iterable)) + + +def find_index[T](iterable: Iterable[T], condition: Callable[[T], bool]) -> int: + for idx, item in enumerate(iterable): + if condition(item): + return idx + return -1 + + +def parse_model[T: BaseModel](path: Path, model: type[T]) -> T: + if path.suffix == ".json": + return model.model_validate_json(path.read_bytes()) + else: + if path.suffix == ".toml": + with open(path, "rb") as f: + data = tomllib.load(f) + return model.model_validate(data) + if path.suffix in (".yaml", ".yml"): + with open(path, "rb") as f: + data = yaml.safe_load(f) + return model.model_validate(data) + raise NotImplementedError( + f"{ + model + } can be parsed only from .json, .toml, .yaml and .yml files. Got file: {path}" + ) + + +def dump_model(path: Path, model: BaseModel) -> None: + with open(path, "w", encoding="utf-8") as f: + if path.suffix in (".yaml", ".yml"): + yaml.dump(model.model_dump(), stream=f) diff --git a/src/utils/event.py b/src/utils/event.py index 7fee35f3..bba61ecd 100644 --- a/src/utils/event.py +++ b/src/utils/event.py @@ -1,10 +1,7 @@ from collections.abc import Callable -from typing import Generic, TypeVar -T = TypeVar("T") - -class SubscriptableEvent(Generic[T]): +class SubscribableEvent[T]: def __init__(self) -> None: self._listeners: set[Callable[[T], None]] = set() @@ -28,7 +25,16 @@ def remove_listener(self, listener: Callable[[T], None]) -> bool: except KeyError: return False - def trigger(self, value: T) -> None: + def trigger(self, value: T) -> bool: + """Send the event to listeners. + + Returns: + True if listeners were present, otherwise False""" + if len(self._listeners) == 0: + return False + # Copy the set in order to allow listeners to remove themselves during iteration. for listener in self._listeners.copy(): listener(value) + + return True diff --git a/src/utils/fonts.py b/src/utils/fonts.py index b7881a90..ff98b14c 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -1,24 +1,25 @@ """ * Font Utils """ -# Standard Library Imports + import ctypes import os -from contextlib import suppress -from ctypes import wintypes import os.path as osp import re +from contextlib import suppress +from ctypes import wintypes +from logging import getLogger from typing import TypedDict -# Third Party Imports -from photoshop.api._document import Document -from photoshop.api.enumerations import LayerKind -from photoshop.api._layerSet import LayerSet from fontTools.ttLib import TTFont, TTLibError from packaging.version import parse +from photoshop.api._document import Document +from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import LayerKind + +from src.utils.adobe import PS_EXCEPTIONS, PhotoshopHandler -# Local Imports -from src.utils.adobe import PhotoshopHandler, PS_EXCEPTIONS +_logger = getLogger(__name__) # Precompile font version pattern REG_FONT_VER: re.Pattern[str] = re.compile(r"\b(\d+\.\d+)\b") @@ -54,14 +55,14 @@ def register_font(ps_app: PhotoshopHandler, font_path: str) -> bool: # Font Resource added successfully try: # Notify all programs - print(f"{osp.basename(font_path)} added to font cache!") + _logger.debug(f"Font '{osp.basename(font_path)}' added to font cache.") hwnd_broadcast = wintypes.HWND(-1) ctypes.windll.user32.SendMessageW( hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) ) ps_app.refreshFonts() - except Exception as e: - print(e) + except Exception: + _logger.exception(f"Couldn't register font: {font_path}") return True return False @@ -81,14 +82,14 @@ def unregister_font(ps_app: PhotoshopHandler, font_path: str) -> bool: # Font Resource removed successfully try: # Notify all programs - print(f"{osp.basename(font_path)} removed from font cache!") + _logger.debug(f"Font {osp.basename(font_path)} removed from font cache!") hwnd_broadcast = wintypes.HWND(-1) ctypes.windll.user32.SendMessageW( hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) ) ps_app.refreshFonts() - except Exception as e: - print(e) + except Exception: + _logger.exception(f"Couldn't unregister font: {font_path}") return True return False @@ -151,9 +152,8 @@ def get_document_fonts( 'name': ps_fonts.get(font, None), 'count': 1 } - except PS_EXCEPTIONS: - # Font property couldn't be accessed - print(f"Font unreadable for layer: {layer.name}") + except PS_EXCEPTIONS as exc: + _logger.warning(f"Couldn't read font from layer: {layer.name}", exc_info=exc) # Make additional calls for nested groups for group in container.layerSets: @@ -177,12 +177,18 @@ def get_font_details(path: str) -> tuple[str, FontDetails] | None: """ with suppress(*PS_EXCEPTIONS, TTLibError): with TTFont(path) as font: - font_name = font['name'].getName(4, 3, 1, 1033).toUnicode() - font_postscript = font['name'].getDebugName(6) - version_match = REG_FONT_VER.search(font['name'].getDebugName(5)) - font_version = version_match.group(1).lstrip('0') if version_match else None - return font_postscript, {'name': font_name, 'version': font_version} - return + if ( + (name_record := font["name"].getName(4, 3, 1, 1033)) + and (font_postscript := font["name"].getDebugName(6)) is not None + and (font_version := font["name"].getDebugName(5)) is not None + ): + font_name = name_record.toUnicode() + + version_match = REG_FONT_VER.search(font_version) + font_version = ( + version_match.group(1).lstrip("0") if version_match else None + ) + return font_postscript, {"name": font_name, "version": font_version} def get_fonts_from_folder(folder: str | os.PathLike[str]) -> dict[str, FontDetails]: diff --git a/src/utils/github.py b/src/utils/github.py index c3cc5a2f..5e262c6b 100644 --- a/src/utils/github.py +++ b/src/utils/github.py @@ -1,16 +1,19 @@ from collections.abc import Callable +from logging import getLogger from typing import Literal from backoff import expo, on_exception from limits import RateLimitItemPerHour from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter -from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from omnitils.exceptions import return_on_exception from omnitils.rate_limit import rate_limit from pydantic import BaseModel, RootModel from requests import RequestException, get -from src import CONSOLE +from src.utils.logging import log_on_exception + +_logger = getLogger(__name__) # Rate limiter to safely limit GitHub requests _rate_limit_storage = MemoryStorage() @@ -54,15 +57,13 @@ class GitHubRelease(BaseModel): def github_request_wrapper[T, **P]( - fallback: T, logr: ExceptionLogger | None = None + fallback: T, ) -> Callable[[Callable[P, T]], Callable[P, T]]: - logr = logr or CONSOLE - def decorator(func: Callable[P, T]): @return_on_exception(fallback) - @log_on_exception(logr) + @log_on_exception(logger=_logger) @rate_limit(limiter=_rate_limiter, limit=_rate_limit) - @on_exception(expo, RequestException, max_tries=2, max_time=1) + @on_exception(expo, RequestException, logger=None, max_tries=1, max_time=1) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 5528f15f..4de9e262 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -1,11 +1,14 @@ """ * Handles Requests to the hexproof.io API """ -from collections.abc import Callable + +from asyncio import gather, to_thread +from collections.abc import Awaitable, Callable from contextlib import suppress from datetime import datetime from functools import cache from io import BytesIO +from logging import getLogger from pathlib import Path from zipfile import ZipFile @@ -16,17 +19,19 @@ from limits import RateLimitItemPerSecond from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter -from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from omnitils.exceptions import return_on_exception from omnitils.files import dump_data_file from omnitils.rate_limit import rate_limit from requests import RequestException, get -from src import CON, CONSOLE, ENV, PATH +from src import CON, ENV from src._loader import SymbolsManifest, get_symbols_manifest -from src._state import HexproofSet, HexproofSets +from src._state import PATH, AppEnvironment, HexproofSet, HexproofSets from src.utils.download import HEADERS from src.utils.github import GitHubReleaseAsset, get_github_releases +_logger = getLogger(__name__) + """ * Hexproof.io Objects """ @@ -40,11 +45,13 @@ hexproof_http_header = HEADERS.Default.copy() """ -* Scryfall Error Handling +* Error Handling """ -def hexproof_request_wrapper[T, **P](fallback: T, logr: ExceptionLogger | None = None) -> Callable[[Callable[P,T]], Callable[P, T]]: +def hexproof_request_wrapper[T, **P]( + fallback: T, +) -> Callable[[Callable[P, T]], Callable[P, T]]: """Wrapper for a Hexproof.io request function to handle retries, rate limits, and a final exception catch. Args: @@ -53,16 +60,26 @@ def hexproof_request_wrapper[T, **P](fallback: T, logr: ExceptionLogger | None = Returns: Wrapped function. """ - logr = logr or CONSOLE - def decorator(func: Callable[P,T]): + def decorator(func: Callable[P, T]): @return_on_exception(fallback) - @log_on_exception(logr) @rate_limit(limiter=_hexproof_rate_limit, limit=_rate_limit) - @on_exception(expo, requests.exceptions.RequestException, max_tries=2, max_time=1) + @on_exception( + expo, + requests.exceptions.RequestException, + logger=None, + max_tries=1, + max_time=1, + ) def wrapper(*args: P.args, **kwargs: P.kwargs): - return func(*args, **kwargs) + try: + return func(*args, **kwargs) + except Exception: + _logger.exception("Hexproof request failed") + raise + return wrapper + return decorator @@ -71,7 +88,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs): """ -@hexproof_request_wrapper('') +@hexproof_request_wrapper("") def get_api_key(key: str) -> str: """Get an API key from https://api.hexproof.io. @@ -87,10 +104,36 @@ def get_api_key(key: str) -> str: url = HexURL.API.Keys.All / key res = requests.get(str(url), headers=hexproof_http_header, timeout=(3, 3)) if res.status_code == 200: - return res.json().get('key', '') + return res.json().get("key", "") raise RequestException( - res.json().get('details', f"Failed to get key: '{key}'"), - response=res) + res.json().get("details", f"Failed to get key: '{key}'"), response=res + ) + + +async def check_api_keys(env: AppEnvironment) -> bool: + get_operations: list[Awaitable[None]] = [] + + # Check if API keys are valid + if not env.API_GOOGLE: + + async def set_google_api_key(): + env.API_GOOGLE = await to_thread(get_api_key, "proxyshop.google.drive") + if not env.API_GOOGLE: + _logger.warning("Couldn't get Google Drive API key") + + get_operations.append(set_google_api_key()) + if not env.API_AMAZON: + + async def set_amazon_api_key(): + env.API_AMAZON = await to_thread(get_api_key, "proxyshop.amazon.s3") + if not env.API_AMAZON: + _logger.warning("Couldn't get Amazon API key") + + get_operations.append(set_amazon_api_key()) + + await gather(*get_operations) + + return bool(env.API_GOOGLE or env.API_AMAZON) @hexproof_request_wrapper({}) @@ -103,16 +146,18 @@ def get_metadata() -> dict[str, Meta]: Raises: RequestException if request was unsuccessful. """ - res = requests.get(str(HexURL.API.Meta.All), headers=hexproof_http_header, timeout=(3, 3)) + res = requests.get( + str(HexURL.API.Meta.All), headers=hexproof_http_header, timeout=(3, 3) + ) if res.status_code == 200: return {k: Meta(**v) for k, v in res.json().items()} raise RequestException( - res.json().get('details', "Failed to get metadata!"), - response=res) + res.json().get("details", "Failed to get metadata!"), response=res + ) @hexproof_request_wrapper({}) -def get_sets() -> dict[str,HexproofSet]: +def get_sets() -> dict[str, HexproofSet]: """Retrieve the current 'Set' data manifest from https://api.hexproof.io. Returns: @@ -121,18 +166,21 @@ def get_sets() -> dict[str,HexproofSet]: Raises: RequestException if request was unsuccessful. """ - res = requests.get(str(HexURL.API.Sets.All), headers=hexproof_http_header, timeout=(5, 5)) + res = requests.get( + str(HexURL.API.Sets.All), headers=hexproof_http_header, timeout=(5, 5) + ) if res.status_code == 200: return HexproofSets.model_validate_json(res.content).root raise RequestException( - res.json().get('details', 'Failed to get set data!'), - response=res) + res.json().get("details", "Failed to get set data!"), response=res + ) """ * Data Caching """ + def _download_symbols(url: str) -> SymbolsManifest | None: response = get(url) if response.status_code == 200: @@ -154,14 +202,14 @@ def update_hexproof_cache() -> tuple[bool, str | None]: meta: dict[str, Meta] = get_metadata() # Check against current metadata - new_set_data: dict[str,HexproofSet] | None = None - _current, _next = CON.metadata.get('sets'), meta.get('sets') + new_set_data: dict[str, HexproofSet] | None = None + _current, _next = CON.metadata.get("sets"), meta.get("sets") if not _current or not _next or _current.version != _next.version: try: # Download updated 'Set' data new_set_data = get_sets() updated = True - except (RequestException, ValueError, OSError): + except RequestException, ValueError, OSError: return False, "Unable to update 'Set' data from hexproof.io!" # Check against current symbol data @@ -200,14 +248,14 @@ def update_hexproof_cache() -> tuple[bool, str | None]: # Download and unpack updated 'Symbols' assets new_symbols = _download_symbols(symbols_dl_url) updated = updated or bool(new_symbols) - except (RequestException, FileNotFoundError): + except RequestException, FileNotFoundError: return False, "Unable to download symbols package!" # Update metadata try: if not updated: return updated, None - + # Ensure that all symbols are present in set data if new_symbols: symbs = new_symbols.set.symbols @@ -242,10 +290,11 @@ def update_hexproof_cache() -> tuple[bool, str | None]: dump_data_file( obj={k: v.model_dump() for k, v in meta.items()}, - path=PATH.SRC_DATA_HEXPROOF_META) + path=PATH.SRC_DATA_HEXPROOF_META, + ) return updated, None - except (FileNotFoundError, OSError, ValueError): - return False, 'Unable to update metadata from hexproof.io!' + except FileNotFoundError, OSError, ValueError: + return False, "Unable to update metadata from hexproof.io!" """ @@ -286,7 +335,7 @@ def get_watermark_svg_from_set(code: str) -> Path | None: return # Check if this symbol code matches a supported watermark - p = PATH.SRC_IMG_SYMBOLS / 'set' / set_obj.code_symbol.upper() / 'WM.svg' + p = PATH.SRC_IMG_SYMBOLS / "set" / set_obj.code_symbol.upper() / "WM.svg" return p if p.is_file() else None @@ -299,5 +348,5 @@ def get_watermark_svg(wm: str) -> Path | None: Returns: Path to a watermark SVG file if found, otherwise None. """ - p = (PATH.SRC_IMG_SYMBOLS / 'watermark' / wm.lower()).with_suffix('.svg') + p = (PATH.SRC_IMG_SYMBOLS / "watermark" / wm.lower()).with_suffix(".svg") return p if p.is_file() else get_watermark_svg_from_set(wm) diff --git a/src/utils/images.py b/src/utils/images.py new file mode 100644 index 00000000..282c6c9c --- /dev/null +++ b/src/utils/images.py @@ -0,0 +1,94 @@ +from logging import getLogger +from os import PathLike +from pathlib import Path +from typing import IO + +from PIL import Image +from pydantic import ValidationError + +from src.cards import CardDetails, parse_card_info +from src.utils.data_structures import find_index +from src.utils.scryfall import ScryfallCard + +_logger = getLogger(__name__) + +IMAGE_ENCODING_TO_SUFFIX_MAPPING: dict[str, str] = { + "PNG": ".png", + "JPEG": ".jpg", + "WebP": ".webp", +} + + +def save_scaled_card_image( + input_path: str | PathLike[str] | IO[bytes], + output_path: Path, + image_format: str, + downscale_width: int | None = None, + quality: int = 95, +): + with Image.open(input_path) as f: + if downscale_width is not None: + img_width, img_height = f.size + ratio = downscale_width / img_width + if ratio < 1: + f.thumbnail( + (downscale_width, round(ratio * img_height)), + resample=Image.Resampling.LANCZOS, + ) + image_format = image_format.lower() + if image_format == "png": + save_kwargs = {"optimize": True} + elif image_format == "jpeg": + save_kwargs = {"optimize": True, "quality": quality} + elif image_format == "webp": + save_kwargs = {"quality": quality, "method": 6} + else: + save_kwargs = {} + f.save(output_path, format=image_format, **save_kwargs) + + +def match_images_with_data_files( + paths: list[Path], +) -> list[CardDetails | tuple[CardDetails, ScryfallCard]]: + """ + Pairs data files (.json) with image files that share the same name. + + Raises: + Pydantic.ValidationError: if some of the data files don't conform to the data model + """ + data_files = [pth for pth in paths if pth.suffix == ".json"] + image_files = [pth for pth in paths if pth.suffix != ".json"] + + results: list[CardDetails | tuple[CardDetails, ScryfallCard]] = [] + + for path in image_files: + card = parse_card_info(path) + card_name = card["name"] + + idx = find_index(data_files, lambda item: item.stem == card_name) + if idx > -1: + data_file = data_files.pop(idx) + try: + results.append( + ( + card, + ScryfallCard.model_validate_json(data_file.read_bytes()), + ) + ) + except ValidationError: + _logger.exception( + f"Data file {data_file} failed to validate. Please correct the reported errors in the data and then try again. Since the file selection was invalid nothing will be added to the render queue." + ) + return [] + else: + results.append(card) + + if data_files: + _logger.warning( + f"Couldn't find a matching image file for files:
{ + '
'.join([str(pth) for pth in data_files]) + }When selecting JSON files for rendering make sure to also select an image whose card name part matches the JSON file's name, e.g. my_custom_card (artist).png and my_custom_card.json. Since the file selection was invalid nothing will be added to the render queue." + ) + return [] + + return results diff --git a/src/utils/lazy.py b/src/utils/lazy.py new file mode 100644 index 00000000..d631ad8f --- /dev/null +++ b/src/utils/lazy.py @@ -0,0 +1,14 @@ +from collections.abc import Callable +from functools import cached_property + + +class LazyLoader[T]: + def __init__(self, factory: Callable[[], T]) -> None: + self._factory = factory + + @cached_property + def _cached_value(self) -> T: + return self._factory() + + def __call__(self) -> T: + return self._cached_value diff --git a/src/utils/logging.py b/src/utils/logging.py new file mode 100644 index 00000000..5af75892 --- /dev/null +++ b/src/utils/logging.py @@ -0,0 +1,24 @@ +from collections.abc import Callable +from functools import wraps +from logging import Logger, getLogger + +_logger = getLogger(__name__) + + +def log_on_exception[**P, T, E: Exception]( + msg: str | Callable[[E], str] = "", + exception_type: type[E] = Exception, + logger: Logger = _logger, +) -> Callable[[Callable[P, T]], Callable[P, T]]: + def decorator(func: Callable[P, T]): + @wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs): + try: + return func(*args, **kwargs) + except exception_type as e: + logger.exception(msg if isinstance(msg, str) else msg(e)) + raise e + + return wrapper + + return decorator diff --git a/src/utils/mtg.py b/src/utils/mtg.py index 93d8d071..0a2d8546 100644 --- a/src/utils/mtg.py +++ b/src/utils/mtg.py @@ -2,8 +2,8 @@ * MTG Related Utiltiies """ -from src.schema.colors import SymbolColorMap, ColorObject from src.enums.mtg import CardTextPatterns as _P +from src.schema.colors import ColorObject, SymbolColorMap """ * MTG Color Utilities @@ -13,7 +13,7 @@ def get_symbol_colors( symbol: str, chars: str, color_map: SymbolColorMap ) -> list[ColorObject | None]: - """Determines the colors of a symbol (represented as Scryfall string) and returns an array Symbol colors. + """Determines the colors of a symbol (represented as Scryfall string) and returns a list of Symbol colors. Args: symbol: Symbol to determine the colors of. diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index a5030f69..f9d1b885 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -4,36 +4,28 @@ from collections.abc import Callable, Sequence from datetime import date +from logging import getLogger from pathlib import Path from shutil import copyfileobj -from typing import ( - Generic, - Literal, - NotRequired, - ParamSpec, - SupportsInt, - TypedDict, - TypeVar, - Unpack, -) +from typing import Literal, NotRequired, ParamSpec, SupportsInt, TypedDict, TypeVar from uuid import UUID import requests import yarl -from backoff import expo, on_exception from hexproof.scryfall.enums import ScryURL from limits import RateLimitItemPerSecond from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter -from omnitils.exceptions import ExceptionLogger, log_on_exception, return_on_exception +from omnitils.exceptions import return_on_exception from omnitils.rate_limit import rate_limit from pydantic import BaseModel, Field, HttpUrl, ValidationError from requests.exceptions import RequestException -from src import CONSOLE, PATH -from src.console import get_bullet_points +from src._state import PATH from src.utils.download import HEADERS +_logger = getLogger() + """ * Types """ @@ -306,7 +298,7 @@ class ScryfallCard(BaseModel): front: bool = Field(default=True, exclude=True) -class ScryfallList(BaseModel, Generic[T]): +class ScryfallList[T](BaseModel): object: Literal["list"] data: list[T] has_more: bool @@ -365,7 +357,10 @@ class ScryfallError(BaseModel): # Rate limiter to safely limit Scryfall requests _rate_limit_storage = MemoryStorage() _scryfall_rate_limit = MovingWindowRateLimiter(_rate_limit_storage) -_rate_limit = RateLimitItemPerSecond(10) +# Using a limit of 10 per second seems to trigger Scryfall's rate limiting, +# even though that's what Scryfall recommends. We don't need to make that many +# simultaneous requests, so a lower limit is used. +_rate_limit = RateLimitItemPerSecond(5) # Scryfall HTTP header scryfall_http_header = HEADERS.Default.copy() @@ -391,61 +386,51 @@ def __init__( *args: object, request: requests.Request | requests.PreparedRequest | None = None, response: requests.Response | None = None, - **kwargs: Unpack[ScryfallExceptionKwargs], + exception: Exception | None = None, + card_name: str | None = None, + card_set: str | None = None, + card_number: str | None = None, + lang: str | None = None, ) -> None: """Allow details relating to the exception to be passed. - Keyword Args: + Args: exception (Exception): Caught exception to pull potential request details from. card_name (str): Name of a card. card_set (str): Set code of a card. card_number (str): Collector number of a card. lang (str): Language of a card. """ - - # Check for our kwargs - exception = kwargs.get("exception", None) - params = { - "Name": kwargs.get("card_name", None), - "Set": kwargs.get("card_set", None), - "Num": kwargs.get("card_number", None), - "Lang": kwargs.get("lang", None), - } - # Compile error message - msg = "Scryfall request failed!" - if any(params.values()): - # List the params provided - msg += "\nParams: " - p = [f"{k}: '{v}'" for k, v in params.items() if v] - msg += ", ".join(p) + msg = "Scryfall request failed." + if details := [ + f"{key}: '{value}'" + for key, value in ( + ("Name", card_name), + ("Set", card_set), + ("Num", card_number), + ("Lang", lang), + ) + if value + ]: + msg += f"\nParams: {', '.join(details)}" + if exception and isinstance(exception, RequestException) and exception.request: # Provide the URL which failed msg += f"\nAPI URL: {exception.request.url}" - if exception: - # Provide the exception cause - msg += f"\nReason: {exception}" - super().__init__(msg) + request = exception.request + response = exception.response + super().__init__(msg, request=request, response=response) -def scryfall_request_wrapper( - logger: ExceptionLogger | None = None, -) -> Callable[[Callable[P, T]], Callable[P, T]]: +def scryfall_request_wrapper() -> Callable[[Callable[P, T]], Callable[P, T]]: """Wrapper for a Scryfall request function to handle retries, rate limits, and a final exception catch. - Args: - logr: Logger object to output any exception messages. - Returns: Wrapped function. """ - logr = logger or CONSOLE def decorator(func: Callable[P, T]): - @log_on_exception(logr) - @on_exception( - expo, requests.exceptions.RequestException, max_tries=2, max_time=1 - ) @rate_limit(limiter=_scryfall_rate_limit, limit=_rate_limit) def wrapper(*args: P.args, **kwargs: P.kwargs): return func(*args, **kwargs) @@ -455,10 +440,14 @@ def wrapper(*args: P.args, **kwargs: P.kwargs): return decorator -def get_error( +def _get_scryfall_exception( error: ScryfallError, + request: requests.Request | requests.PreparedRequest | None = None, response: requests.Response | None = None, - **kwargs: Unpack[ScryfallExceptionKwargs], + card_name: str | None = None, + card_set: str | None = None, + card_number: str | None = None, + lang: str | None = None, ) -> ScryfallException: """Returns a ScryfallException object created using data from a ScryfallError object. @@ -471,10 +460,17 @@ def get_error( A ScryfallException object. """ msg = error.details - if warns := error.warnings: - msg += get_bullet_points(warns, " -") - kwargs["exception"] = RequestException(msg, response=response) - return ScryfallException(**kwargs) + if error.warnings: + msg += "
".join(error.warnings) + return ScryfallException( + request=request, + response=response, + exception=RequestException(msg, request=request, response=response), + card_name=card_name, + card_set=card_set, + card_number=card_number, + lang=lang, + ) """ @@ -484,20 +480,21 @@ def get_error( @scryfall_request_wrapper() def get_card_via_url(url: str) -> ScryfallCard: - res = requests.get(url=str(url), headers=scryfall_http_header) + res = requests.get(url=url, headers=scryfall_http_header) try: card = ScryfallCard.model_validate_json(res.content) - if is_playable_card(card): - return card except ValidationError as exc: # Handle Scryfall error try: err = ScryfallError.model_validate_json(res.content) - raise get_error(error=err, response=res) + raise _get_scryfall_exception(error=err, response=res) except ValidationError: raise ScryfallException(exception=exc) + if is_playable_card(card): + return card + # No playable result raise ScryfallException( exception=RequestException( @@ -518,8 +515,8 @@ def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> Scryfa card_number: Collector number of the card lang: Lang code to look for, ex: en - Returns: - Card dict or ScryfallException + Raises: + ScryfallException: if the request fails """ # Establish API pathing url = ScryURL.API.Cards.Main / card_set.lower() / card_number @@ -530,13 +527,11 @@ def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> Scryfa try: card = ScryfallCard.model_validate_json(res.content) - if is_playable_card(card): - return card except ValidationError as exc: # Handle Scryfall error try: err = ScryfallError.model_validate_json(res.content) - raise get_error( + raise _get_scryfall_exception( error=err, response=res, card_set=card_set, @@ -551,6 +546,9 @@ def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> Scryfa lang=lang, ) + if is_playable_card(card): + return card + # No playable result raise ScryfallException( exception=RequestException( @@ -589,8 +587,8 @@ def get_card_search( name, set, released, rarity, color, usd, tix, eur, cmc, power, toughness, edhrec, penny, artist, review - Returns: - Card dict or ScryfallException + Raises: + ScryfallException: if the request fails """ # Query Scryfall res = requests.get( @@ -609,14 +607,11 @@ def get_card_search( try: list_data = ScryfallCardList.model_validate_json(res.content) - for card in list_data.data: - if is_playable_card(card): - return card except ValidationError as exc: # Handle Scryfall error try: err = ScryfallError.model_validate_json(res.content) - raise get_error( + raise _get_scryfall_exception( error=err, response=res, card_name=card_name, @@ -628,8 +623,13 @@ def get_card_search( exception=exc, card_name=card_name, card_set=card_set, lang=lang ) + for card in list_data.data: + if is_playable_card(card): + return card + # No playable results raise ScryfallException( + response=res, exception=RequestException( "No playable card found with the provided search terms.", response=res ), @@ -673,9 +673,13 @@ def get_cards_paged( # Handle Scryfall error try: err = ScryfallError.model_validate_json(res.content) - raise get_error(error=err, response=res) + scry_exc = _get_scryfall_exception(error=err, response=res) except ValidationError: - raise ScryfallException(exception=exc) + scry_exc = ScryfallException(exception=exc) + _logger.error( + "Couldn't retrieve a paginated card list from Scryfall.", exc_info=scry_exc + ) + return [] @scryfall_request_wrapper() @@ -712,8 +716,8 @@ def get_cards_oracle( @scryfall_request_wrapper() -@return_on_exception({}) -def get_set(card_set: str) -> ScryfallSet: +@return_on_exception(None) +def get_set(card_set: str) -> ScryfallSet | None: """Grab Set data from Scryfall. Args: @@ -733,13 +737,17 @@ def get_set(card_set: str) -> ScryfallSet: # Handle Scryfall error try: err = ScryfallError.model_validate_json(res.content) - raise get_error( + scry_exc = _get_scryfall_exception( error=err, response=res, card_set=card_set, ) except ValidationError: - raise ScryfallException(exception=exc, card_set=card_set) + scry_exc = ScryfallException(exception=exc, card_set=card_set) + _logger.error( + "Couldn't retrieve set data from Scryfall.", + exc_info=scry_exc, + ) """ @@ -749,7 +757,7 @@ def get_set(card_set: str) -> ScryfallSet: @scryfall_request_wrapper() @return_on_exception(None) -def get_card_scan(img_url: str) -> Path: +def get_card_scan(img_url: str) -> Path | None: """Downloads scryfall art from URL Args: @@ -763,7 +771,10 @@ def get_card_scan(img_url: str) -> Path: """ res = requests.get(img_url, stream=True) if res.status_code != 200: - raise RequestException("Couldn't retrieve image from scryfall.", response=res) + _logger.warning( + f"Couldn't retrieve image from scryfall. {res.status_code}
{res.text}" + ) + return None with open(PATH.LOGS_SCAN, "wb") as f: copyfileobj(res.raw, f) return PATH.LOGS_SCAN diff --git a/src/utils/tests.py b/src/utils/tests.py new file mode 100644 index 00000000..9827340e --- /dev/null +++ b/src/utils/tests.py @@ -0,0 +1,34 @@ +from pydantic import RootModel + +from src._config import AppConfig +from src._state import PATH +from src.enums.mtg import LayoutType +from src.layouts import SplitLayout +from src.render.setup import RenderOperation +from src.utils.data_structures import parse_model + +TestCards = RootModel[dict[LayoutType, dict[str, str]]] +"""dict[ layout, dict[ image_file_name, test_case_name ]]""" + + +def get_template_render_test_cases() -> dict[LayoutType, dict[str, str]]: + return parse_model(PATH.SRC_DATA_TEMPLATE_RENDER_TEST_CASES, TestCards).root + + +def prepare_test_render(render_operation: RenderOperation, config: AppConfig): + """Modifies render operation so that the render process won't pause for manual editing. + Call this after loading the config for a test render.""" + config.generative_fill = False + + render_operation.do_not_pause = True + + if template := render_operation.template_instance: + path = PATH.OUT / "test_renders" / template.__class__.__name__ + path.mkdir(mode=777, parents=True, exist_ok=True) + template.output_directory = path + art_image_override = ( + PATH.SRC_IMG_TEST_FULL_ART if template.is_fullart else PATH.SRC_IMG_TEST + ) + template.layout.art_file = art_image_override + if isinstance(template.layout, SplitLayout): + template.layout.art_files = [art_image_override, art_image_override] diff --git a/src/utils/threading.py b/src/utils/threading.py index 4c24b70d..3b9b7ed7 100644 --- a/src/utils/threading.py +++ b/src/utils/threading.py @@ -1,16 +1,13 @@ from collections.abc import Callable from functools import cached_property from threading import Thread -from typing import Generic, TypeVar -from src.utils.event import SubscriptableEvent +from src.utils.event import SubscribableEvent -T = TypeVar("T") - -class ThreadInitializedInstance(Generic[T]): +class ThreadInitializedInstance[T]: def __init__(self, factory: Callable[[], T]) -> None: - self._on_ready: SubscriptableEvent[T] = SubscriptableEvent() + self._on_ready: SubscribableEvent[T] = SubscribableEvent() self._factory = factory self.ready: bool = False self._initialization: Thread | None = None diff --git a/src/utils/windows.py b/src/utils/windows.py index 393efaf6..a8d1f796 100644 --- a/src/utils/windows.py +++ b/src/utils/windows.py @@ -1,10 +1,14 @@ from enum import IntEnum +from logging import getLogger + import pywintypes import win32api import win32con import win32gui import win32process +_logger = getLogger() + # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow class WindowState(IntEnum): @@ -72,4 +76,4 @@ def enum_callback(handle: int, extra: list[int]): except pywintypes.error: pass except Exception as exc: - print("An exception occurred while getting Photoshop's window handle:", exc) + _logger.warning("Couldn't get Photoshop's window handle:", exc_info=exc) From 8ebf3819c6a9cd8daa3168c955ffa6392d7b8638 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 17 Feb 2026 17:48:33 +0200 Subject: [PATCH 061/190] docs(README.md): Update the README's descriptions to match the new PySide6 GUI and fix broken Discord links --- README.md | 138 +++++++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index b035b8d9..816f0a27 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ ![Showcase Image](src/img/cover-photo.png) Proxyshop is a Photoshop automation app that generates high-quality Magic the Gathering card renders. Inspired by Chilli-Axe's [original Photoshop automation scripts](https://github.com/chilli-axe/mtg-photoshop-automation). -If you need help with this app or wish to troubleshoot an issue, [please join our discord](https://discord.gg/magicproxies)! +If you need help with this app or wish to troubleshoot an issue, [please join our discord](https://discord.gg/magic-proxies-889831317066358815)! ![Photoshop](https://img.shields.io/badge/photoshop-CC_2017+-informational?style=plastic) -![Python](https://img.shields.io/badge/python-3.11_|_3.12_|_3.13-blue?style=plastic) -[![Discord](https://img.shields.io/discord/889831317066358815?style=plastic&label=discord&color=brightgreen)](https://discord.gg/magicproxies) +![Python](https://img.shields.io/badge/python-3.14-blue?style=plastic) +[![Discord](https://img.shields.io/discord/889831317066358815?style=plastic&label=discord&color=brightgreen)](https://discord.gg/magic-proxies-889831317066358815) ![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/m/MrTeferi/Proxyshop?style=plastic&label=commits&color=brightgreen) [![Patreon](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dmpcfill%26type%3Dpatrons&style=plastic&color=red&logo=none)](https://patreon.com/mpcfill) [![GitHub](https://img.shields.io/github/license/MrTeferi/Proxyshop?color=red&style=plastic)](https://github.com/MrTeferi/Proxyshop/blob/main/LICENSE) @@ -16,9 +16,9 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou # 🛠️ Requirements -- Photoshop (2017-2024 Supported) +- Photoshop (2017-2026 Supported) - Windows (currently incompatible with Mac/Linux) -- [The Photoshop templates](https://drive.google.com/drive/u/1/folders/1moEdGmpAJloW4htqhrdWZlleyIop_z1W) (Can be downloaded in the app) +- [The Photoshop templates](https://drive.google.com/drive/u/1/folders/1moEdGmpAJloW4htqhrdWZlleyIop_z1W) (Can be downloaded in the app, which is recommended over manual download) - Required fonts (included in `fonts/`): - **Beleren Proxy Bold** — For Card Name, Typeline, Power/Toughness - **Proxyglyph** — For mana symbols, a fork of Chilli's NDPMTG font @@ -35,11 +35,14 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou # 🚀 Setup Guide 1. Download the [latest release](https://github.com/MrTeferi/MTG-Proxyshop/releases), extract it to a folder of your choice. 2. Install the fonts included in the `fonts/` folder, please note that `Proxyglyph` may need to be updated in future releases. -3. Place card arts for cards you wish to render in the `art/` folder. These arts should be named according to the card (see [Art File Naming](#-art-file-naming) for more info). -4. Launch `Proxyshop.exe`. Click the **Update** button. Proxyshop will load templates available to download, grab what you want. -5. Hit **Render All** to render every card art in the `art/` folder. Hit **Render Target** to render one or more specific card arts. -6. You can also drag art images or folders containing art images onto the Proxyshop app, Proxyshop will automatically start rendering those cards. -7. During the render process the console at the bottom will display the current progress and prompt you if any failures occur. + 1. Select all the fonts + 2. right click + 3. Install +3. Card arts should be named according to the card (see [Art File Naming](#-art-file-naming) for more info). +4. Launch `Proxyshop.exe`. Press the **Updater** button. Proxyshop will load templates available to download, grab what you want. +5. Press **Render** to render one or more card arts. +6. You can also drag art images onto the Proxyshop app to start rendering. +7. The log at the bottom will keep you updated on events happening in the app. # 🎨 Art File Naming - Art file types currently supported are: `jpg`, `jpeg`, `jpf`, `png`, `tif`, and `webp`. **NOTE**: `webp` requires Photoshop 2022+. @@ -49,7 +52,7 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou ``` Damnation [TSR].jpg ``` - - **Collector Number** `{num}` — Only works if **Set** tag was also provided, render a version of that card with the exact **set code** and **number** combination. This is particularly useful in cases where a set has multiple versions of the same card, for example Secret Lair (SLD) has 3 different versions of **Brainstorm**. + - **Collector Number** `{num}` — Only works if **Set** tag was also provided. Renders a version of that card with the exact **set code** and **number** combination. This is particularly useful in cases where a set has multiple versions of the same card, for example Secret Lair (SLD) has 3 different versions of **Brainstorm**. ``` Brainstorm [SLD] {175}.jpg ``` @@ -64,44 +67,36 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou # 💻 Using the Proxyshop GUI -### Render Cards Tab -- The main tab for rendering authentic Magic the Gathering cards. -- **Render All**: Renders a card image using each art image found in the `art/` folder. -- **Render Target**: Opens file select in Photoshop, renders a card image using each art image you select. -- **Global Settings**: Opens a settings panel used to change app-wide options for: - - **Main settings**: Affects template behavior, can be modified for individual templates. When you click the ⚙️ icon next to a template, a config file is generated for **that** template which overrides these settings. - - **System settings**: Affects the entire application and cannot be changed for individual templates. -- The set of tabs below these buttons represent **template types**, e.g. Normal, MDFC, Transform, etc. - - **Template types** represent different kinds of templates which require different frame elements or different rendering techniques. - - If the **Normal** tab is active, and you click on a template button, that template becomes selected for the **Normal** template type. Cards which match the **Normal** type will now render using that template. - - That template **DOES NOT** become selected for other types. For example, if **Borderless** is selected in the **Normal** tab, but **Normal** is selected in the **MDFC** tab. Cards that match the **MDFC** type will render using **Normal MDFC**. -- Next to each template in the template list there are two icons: - - ⚙️ Lets you change the **Main Settings** for this template, some templates will also have their own specially designed settings you can change as well. - - 🧹 Deletes the separate config file generated for this template, effectively returning this template back to default settings. Ensures **Main Settings** for this template are governed by the **Global Settings** panel. -- The dark grey area below the templates selector is the **Console**, this is where status messages will be displayed tracking render progress and other user actions. -- To the right of the **Console** are some useful buttons: - - 📌 Pins the Proxyshop window, so it remains above all other running programs - - 📷 Takes a screenshot of the Proxyshop window, saves to: `out/screenshots/` - - 🌍 Opens your default web browser, navigating to Proxyshop's GitHub page - - ❔ Opens your default web browser, navigating to our community Discord server - - **Continue**: Becomes active when app is waiting for a user response, either when manual editing is enabled or an error has occurred. - - **Cancel**: Becomes active when cards are being rendered, can cancel the render operation at any time or if an error occurs. - - **Update**: Opens the **Updater** panel which allows you to download new templates and update existing ones. - -### Custom Creator Tab -- This tab controls the custom card creator. -- This feature is currently considered **experimental beta** and may have issues. -- You can currently render **Normal**, **Planeswalker**, or **Saga** cards, just fill in the appropriate data and hit **Render Custom**. -- More features and card types will be added in the near future. - -### Tools Tab -- This tab contains a growing list of helpful tools and utilities. -- **Render All Showcases**: Generates a bordered showcase image for each card image in the `out/` folder, showcases will be placed in `out/showcase/`. -- **Render Target Showcase**: Opens a file select in Photoshop, generates a bordered showcase image for each card image you select. -- **Compress Renders**: This tool reduces the size of card images stored in the `out/` folder. The settings are: - - **Quality**: JPEG save quality of the compressed image, supports a number between 1 and 100. (**Recommended**: 95-99) - - **Optimize**: Enables Pillow's automatic "optimize" flag. Lowers filesize by a small margin for no discernible downside. (**Recommended**: On) - - **800 DPI**: Downscales card images above 800 DPI to a maximum of 800 DPI. Most Proxyshop templates are 1200 DPI which is much higher than anyone really needs. Most printing services do not print above 800 DPI. (**Recommended**: On) +### Rendering + +- **Render**: Opens a file picker for choosing images to render. Template used for rendering is determined based on the tab you are in and what templates you have selected. Completed renders are saved to `out/` under Proxyshop's directory. +- **Templates tab**: Allows selecting a specific template to render cards in. If a card has a layout that isn't supporeted by the selected template it won't be rendered. +- **Batch mode tab**: Allows selecting a different template per card layout (e.g. Normal, Transform, Planeswalker, etc.). If some layout is left without a selection cards of that type are skipped. The batch mode is similar to how the old Proxyshop GUI used to function. +- **Queue**: Allows viewing what cards have been queued for rendering. Individual entries can be removed from the queue or the whole queue can be cleared. +- **Cancel/Resume**: Cancel aborts rendering. The currently active operation is removed but the rest of the queue is left intact. Resume can be used to proceed with the rest of the queue after a cancel. + +### Updater + +The updater window allows downloading and updating template files. The files can originate from Proxyshop or installed plugins. + +- **Install**: Downloads a tempalte file. +- **Update**: Downloads an updated version of a template file. Be aware that if you have made local changes to the file that is about to be updated those changes will be lost as the old file is overwritten. Please note that Proxyshop records templates' versions only when downloading them via the updater, so if you source templates manually from elsewhere they will be assumed to have some default version and even if they don't actually have a newer version available the update option is still offered, assuming the templates can be downloaded via the updater as well. + +### Settings + +The settings window allows modifying Proxyshop's behavior. The settings can be accessed via the *Settings* button or via the template specific settings buttons within the templates list. + +- **Application**: App wide settings. +- **Template defaults**: These settings are used when rendering if the used template doesn't have template specific settings. +- **Templates**: A tree of template specific settings. If a template is given template specific settings they completely override the **Template defaults**. Template specific settings become active by simply selecting a template entry in the settings tree. Press **Clear** when some template specific settings are open to remove them from use, consequently returning to use **Template defaults** for that template. + +### Custom cards + +You may supply Proxyshop with image and JSON pairs to render cards with custom specifications. The image's card name part and the JSON file's name, excluding suffix, should be the same, e.g. *my_custom_card (artist).png* and *my_custom_card.json*. The JSON file should contain a valid [Scryfall card specification](https://scryfall.com/docs/api/cards). Look up `class ScryfallCard` from the source code if you want to know the specifics of the data validation. An easy way to make the JSON is to look up a similar card from Scryfall, download the JSON for that card via Scryfall's web GUI (*Copy-pasteable JSON* on the card's page) and modify the downloaded JSON. + +### Tools + +- **Transform images**: Allows re-encoding and downscaling chosen images. Completed transformations are saved to `compressed/` next to the input image. # 🐍 Setup Guide (Python Environment) Setting up the Python environment for Proxyshop is intended for advanced users, contributors, and anyone who wants to @@ -125,22 +120,17 @@ See `pyproject.toml` for supported Python versions. cd proxyshop poetry install ``` -4. Install the fonts included in the `fonts/` folder. Do not delete these after install, some are used by the GUI. -5. Create a folder called `art` in the root directory. This is where you place art images for cards you wish to batch render. -6. Run the app. +4. Install the fonts included in the `fonts/` folder. +5. Run the app. ```bash - # OPTION 1) Execute via poetry - poetry run python main.py - - # OPTION 2) Enter the poetry environment, then execute with cli - poetry shell - proxyshop gui - - # OPTION 3) Activate the virtual environment and run the app's entrypoint with Python + # OPTION 1) Activate the virtual environment and run the app's entrypoint with Python ./.venv/Scripts/Activate python main.py + + # OPTION 2) Execute via poetry + poetry run python main.py ``` -7. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. +6. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. # 🖥 Development Environment @@ -150,14 +140,34 @@ If you want to contribute to Proxyshop you should ensure that your code plays we - [Python Environments](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-python-envs) - [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) +Additionally if you want to do UI development the extensions below will help with that. These aren't needed for template plugin development. +- [Qt Core](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-core) +- [Qt Python](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-python) +- [Qt Qml](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-qml) + +After installing the Qt extensions add your absolute path to `./src/gui` to the `qt-qml.qmlls.additionalImportPaths` setting in VS Code. Without it qmllint won't recognize local Qml imports. At the time of writing the qmllint, provided by the Qt Qml extension, doesn't recognize context defined in Python so warnings within the Qml files are expected. + # 💾 Download Templates Manually If you wish to download the templates manually, visit [this link](https://drive.google.com/drive/u/1/folders/1sgJ3Xu4FabxNgDl0yeI7OjDZ7fqlI4p3). These archives must be extracted to the `/templates` directory. The archives found within the **Investigamer** and **SilvanMTG** drive folders must be extracted to `/plugins/Investigamer/templates` and `/plugins/SilvanMTG/templates` respectively. +# 🔩 Build the App + +If you want to make a distributable executable of Proxyshop run the following command: +```bash +pyinstaller -n Proxyshop --onefile --icon "./src/img/favicon.ico" --distpath "./dist" --console --add-data "src/gui/qml:src/gui/qml" --add-data "src/img/favicon.ico:src/img/favicon.ico" main.py +``` + +Additionally the following directories and their contents should be distributed alongside the executable: +- /fonts +- /plugins +- /templates +- /src/data +- /src/img # 💌 How can I support Proxyshop? -Feel free to [join our discord](http://discord.gg/magicproxies) and participate in the `#Proxyshop` channel where we are constantly brainstorming and +Feel free to [join our discord](https://discord.gg/magic-proxies-889831317066358815) and participate in the `#Proxyshop` channel where we are constantly brainstorming and testing new features, dropping beta releases, and sharing new plugins and templates. Also, please consider supporting [our Patreon](http://patreon.com/mpcfill) which pays for S3 + Cloudfront hosting of Proxyshop templates and allows us the freedom to work on the app, as well as other applications like MPC Autofill, MTG Art Downloader, and more! If Patreon isn't your thing, you can also buy @@ -201,8 +211,8 @@ may cause errors on some templates. Where is a good place to find high quality MTG art? -Your best resource is going to be [MTG Pics](https://mtgpics.com), to improve art quality even more you can look into upscaling with Topaz/Chainner/ESRGAN. -On our [discord](https://discord.gg/magicproxies) we provide a lot of resources for learning how to upscale art easily and effectively. +Your best resource is going to be [MTG Pics](https://mtgpics.com), to improve art quality even more you can look into upscaling with Topaz/Chainner/Upscayl/ComfyUI. +On our [discord](https://discord.gg/magic-proxies-889831317066358815) we provide a lot of resources for learning how to upscale art easily and effectively. For mass downloading art, view my other project: [MTG Art Downloader](https://github.com/MrTeferi/MTG-Art-Downloader) From d735b2b690c1ced5ad0f2733e34b6dd2ed9c6764 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 17 Feb 2026 19:45:42 +0200 Subject: [PATCH 062/190] feat: Write better log messages for test calls --- src/gui/qml/models/batch_rendering_model.py | 5 +++++ src/gui/qml/models/template_list_model.py | 4 +++- src/gui/qml/models/test_renders_model.py | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index 4a11e4d6..0fc59e9f 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -245,6 +245,11 @@ async def action() -> None: if layout_category: break + _logger.info( + f"Queueing { + layout_category.value + ' ' if layout_category else '' + }tests for batch mode selections." + ) await gather(*preparation_routines) cancel_with_render(ensure_future(action()), self._render_queue) diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 19d7efe5..f22bbf83 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -204,7 +204,9 @@ async def action() -> None: ) _logger.info( - f"Queueing {len(preparation_routines)} template test entries for render." + f"Queueing { + layout_category.value + ' ' if layout_category else '' + }tests for template {template.name}." ) await gather(*preparation_routines) diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index 73331319..4a0fedab 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -108,7 +108,9 @@ async def test_all_renders( ) _logger.info( - f"Queueing all {len(preparation_routines)} test entries for render." + f"Queueing all { + layout_category.value + ' ' if layout_category else '' + }tests for render." ) await gather(*preparation_routines) From edc5879ed2eb4cf5b0b14af12807c0c102b1e0eb Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 17 Feb 2026 20:07:39 +0200 Subject: [PATCH 063/190] fix(AssembledTemplate): Mark partially installed templates as usable in the template list --- src/_loader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_loader.py b/src/_loader.py index 7c889c26..b2dd6d2a 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -1482,9 +1482,9 @@ def is_installed(self, layout_category: LayoutCategory | None = None) -> bool: if layout_category and layout_category not in template.layout_categories: continue - if not template.parent.is_installed: - return False - return True + if template.parent.is_installed: + return True + return False def has_config(self, layout_category: LayoutCategory | None = None) -> bool: for template in self.templates: From 321d94b865e0c1ce141ad463eaf22cf10021a2de Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 18 Feb 2026 07:49:33 +0200 Subject: [PATCH 064/190] fix(FormattedTextField): Fix right aligning of quote credit --- src/text_layers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_layers.py b/src/text_layers.py index af701042..910e07fc 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -722,7 +722,7 @@ def format_text(self): para_range.putInteger(idFrom, self.input.find('"\r—') + 2) para_range.putInteger(idTo, self.flavor_end) para_style.putBoolean(APP.instance.sID("styleSheetHasParent"), True) - para_range.putEnumerated( + para_style.putEnumerated( APP.instance.sID("align"), APP.instance.sID("alignmentType"), APP.instance.sID("right"), From 90eba9508810053c09da854c5ab91564dd050f18 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 18 Feb 2026 07:50:53 +0200 Subject: [PATCH 065/190] fix(AppConfig): Correctly use default template config if template specific config is not set --- src/_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/_config.py b/src/_config.py index c3d18855..d6648869 100644 --- a/src/_config.py +++ b/src/_config.py @@ -331,7 +331,11 @@ def load(self, config: ConfigHandler | None = None) -> None: self.file = CustomConfigParser(default_section="", allow_no_value=True) # Combine app and base/template configs self.file.read_dict(self.app_config.setting_values) - self.file.read_dict((config if config else self.base_config).setting_values) + self.file.read_dict( + ( + config if config and config.has_config else self.base_config + ).setting_values + ) self.update_definitions() def copy(self, config: ConfigHandler | None = None) -> AppConfig: From 535b1ebd276a6b2f5e3529ccfcab7d600adbae38 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 20 Feb 2026 10:08:13 +0200 Subject: [PATCH 066/190] feat: Allow using Photoshop's "Remove" action for filling and expose more settings for manipulating the filling selection --- src/_config.py | 26 ++++++--- src/data/config/app.toml | 12 ----- src/data/config/base.toml | 36 +++++++++++++ src/enums/settings.py | 7 +++ src/helpers/design.py | 108 +++++++++++++++++++++++++++++--------- src/render/setup.py | 2 +- src/templates/_core.py | 59 +++++++++++++++------ src/utils/tests.py | 3 +- 8 files changed, 192 insertions(+), 61 deletions(-) diff --git a/src/_config.py b/src/_config.py index d6648869..ea70e5a1 100644 --- a/src/_config.py +++ b/src/_config.py @@ -11,6 +11,7 @@ BorderColor, CollectorMode, CollectorPromo, + FillMode, HasDefault, OutputFileType, ScryfallSorting, @@ -118,15 +119,9 @@ def update_definitions(self): ) # APP - RENDER - self.generative_fill = self.file.getboolean( - "APP.RENDER", "Generative.Fill", fallback=False - ) self.select_variation = self.file.getboolean( "APP.RENDER", "Select.Variation", fallback=False ) - self.feathered_fill = self.file.getboolean( - "APP.RENDER", "Feathered.Fill", fallback=False - ) self.vertical_fullart = self.file.getboolean( "APP.RENDER", "Vertical.Fullart", fallback=False ) @@ -177,9 +172,28 @@ def update_definitions(self): ) # BASE - TEMPLATES + self.fill_mode: FillMode = FillMode( + self.file.get( + "BASE.TEMPLATES", + "Border.Fill.Mode", + fallback=FillMode.CONTENT_AWARE_FILL.value, + ) + ) + self.fill_contract = self.file.getint( + "BASE.TEMPLATES", "Border.Fill.Contract", fallback=10 + ) + self.fill_smooth = self.file.getint( + "BASE.TEMPLATES", "Border.Fill.Smooth", fallback=0 + ) + self.fill_feather = self.file.getint( + "BASE.TEMPLATES", "Border.Fill.Feather", fallback=5 + ) self.exit_early = self.file.getboolean( "BASE.TEMPLATES", "Manual.Edit", fallback=False ) + self.pause_for_manual_art_alignment = self.file.getboolean( + "BASE.TEMPLATES", "Manual.Art.Alignment.Pause", fallback=False + ) self.minimize_photoshop = self.file.getboolean( "BASE.TEMPLATES", "Minimize.Photoshop", fallback=False ) diff --git a/src/data/config/app.toml b/src/data/config/app.toml index 6fdaeecb..ad637ff6 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -108,24 +108,12 @@ default = 0 [RENDER] title = "Render Settings" -[RENDER."Generative.Fill"] -title = "Enable Generative Fill" -desc = """When enabled, fullart and extended templates will fill empty space using Generative Fill instead of Content Aware Fill. This feature will not work unless running the Photoshop BETA version.""" -type = "bool" -default = 0 - [RENDER."Select.Variation"] title = "Select Generative Fill Variation" desc = """When enabled, render sequence will pause after 'Generative Fill' is applied and allow you to select a variation.""" type = "bool" default = 0 -[RENDER."Feathered.Fill"] -title = 'Feathered Fill Selections' -desc = """When enabled, Proxyshop will feather (dramatically smooth) selections made for Content Aware Fill and Generative Fill. This usually improves the quality of fill results, especially on Generative Fill, at the cost of potentially replacing more original pixels at the edge of the art image.""" -type = "bool" -default = 0 - [RENDER."Vertical.Fullart"] title = "Force Vertical Framing on Fullart Templates" desc = """When enabled, Fullart templates will frame all art using the vertical 'fullart' frame, even when horizontal art is provided. As a result, less area will be Content Aware or Generative Filled on horizontal arts, but the art will be 'zoomed in'.""" diff --git a/src/data/config/base.toml b/src/data/config/base.toml index 7cdc6b71..0471dd39 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -119,12 +119,48 @@ default = 1 [TEMPLATES] title = "Template Options" +[TEMPLATES."Border.Fill.Mode"] +title = "Border Fill Mode" +desc = """Choose the method to use for filling card borders in borderless renders.""" +type = "options" +default = "Content-Aware Fill" +options = [ + "No Fill", + "Content-Aware Fill", + "Generative Fill", + "Remove Content Fill", +] + +[TEMPLATES."Border.Fill.Contract"] +title = "Border Fill Contract" +desc = """How much to contract the selection before filling.""" +type = "int" +default = 10 + +[TEMPLATES."Border.Fill.Smooth"] +title = "Border Fill Smooth" +desc = """How much to smooth the selection before filling.""" +type = "int" +default = 0 + +[TEMPLATES."Border.Fill.Feather"] +title = "Border Fill Feather" +desc = """How much to feather the selection before filling.""" +type = "int" +default = 5 + [TEMPLATES."Manual.Edit"] title = "Manual Editing Mode" desc = """Pause the render process before saving to allow for manual edits.""" type = "bool" default = 0 +[TEMPLATES."Manual.Art.Alignment.Pause"] +title = "Pause for manual art alignment" +desc = """Pause the render process after automatic art alignment to allow for manual adjustments.""" +type = "bool" +default = 0 + [TEMPLATES."Minimize.Photoshop"] title = "Minimize Photoshop" desc = """Minimize Photoshop when rendering starts. The rendering might be faster when Photoshop is minimized.""" diff --git a/src/enums/settings.py b/src/enums/settings.py index b37d5a1f..4364c65c 100644 --- a/src/enums/settings.py +++ b/src/enums/settings.py @@ -83,6 +83,13 @@ class WatermarkMode(StrEnum): Default = nonmember(Disabled) +class FillMode(StrEnum): + NO_FILL = "No Fill" + CONTENT_AWARE_FILL = "Content-Aware Fill" + GENERATIVE_FILL = "Generative Fill" + REMOVE_CONTENT_FILL = "Remove Content Fill" + + """ * Template: Borderless """ diff --git a/src/helpers/design.py b/src/helpers/design.py index 4d12ee3f..74a2407f 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -5,18 +5,16 @@ from contextlib import suppress from logging import getLogger -from photoshop.api import ( - ActionDescriptor, - ActionReference, +from photoshop.api import ActionDescriptor, ActionReference, SolidColor +from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document +from photoshop.api.enumerations import ( BlendMode, DialogModes, ElementPlacement, RasterizeType, SaveOptions, - SolidColor, ) -from photoshop.api._artlayer import ArtLayer -from photoshop.api._document import Document from src import APP from src.helpers.colors import fill_layer_primary, rgb_black @@ -85,7 +83,7 @@ def fill_empty_area(reference: ArtLayer, color: SolidColor | None = None) -> Art def content_aware_fill_edges( - layer: ArtLayer | None = None, feather: bool = False + layer: ArtLayer | None = None, contract: int = 10, smooth: int = 0, feather: int = 5 ) -> None: """Fills pixels outside art layer using content-aware fill. @@ -99,7 +97,9 @@ def content_aware_fill_edges( docref.activeLayer = layer active_layer = layer elif not isinstance((active_layer := docref.activeLayer), ArtLayer): - _logger.warning("Skipping content aware fill as active layer is not an ArtLayer.") + _logger.warning( + "Skipping content aware fill as active layer is not an ArtLayer." + ) return active_layer.rasterize(RasterizeType.EntireLayer) @@ -109,21 +109,20 @@ def content_aware_fill_edges( # Guard against no selection made try: - # Create a feathered or smoothed selection, then invert + # Adjust selection, then invert + if contract: + selection.contract(contract) + if smooth: + selection.smooth(smooth) if feather: - selection.contract(22) - selection.feather(8) - else: - selection.contract(6) - # selection.smooth(4) - selection.feather(2) - # selection.contract(10) - # selection.feather(3) + selection.feather(feather) selection.invert() content_aware_fill() - except PS_EXCEPTIONS: + except PS_EXCEPTIONS as exc: # Unable to fill due to invalid selection - _logger.warning("Couldn't make a valid selection. Skipping automated fill.") + _logger.warning( + "Couldn't make a valid selection. Skipping automated fill.", exc_info=exc + ) # Clear selection with suppress(Exception): @@ -132,7 +131,9 @@ def content_aware_fill_edges( def generative_fill_edges( layer: ArtLayer | None = None, - feather: bool = False, + contract: int = 10, + smooth: int = 0, + feather: int = 5, close_doc: bool = True, docref: Document | None = None, ) -> Document | None: @@ -175,13 +176,13 @@ def generative_fill_edges( # Guard against no selection made try: - # Create a feathered or smoothed selection, then invert + # Adjust selection, then invert + if contract: + selection.contract(contract) + if smooth: + selection.smooth(smooth) if feather: - selection.contract(22) - selection.feather(8) - else: - selection.contract(10) - selection.smooth(5) + selection.feather(feather) selection.invert() try: generative_fill() @@ -209,6 +210,54 @@ def generative_fill_edges( return +def remove_content_fill_edges( + layer: ArtLayer | None = None, contract: int = 10, smooth: int = 0, feather: int = 5 +) -> None: + """Fills pixels outside art layer using remove content fill. + + Args: + layer: Layer to use for the fill. Uses active if not provided. + feather: Whether to feather the selection before performing the fill operation. + """ + # Set active layer if needed + docref = APP.instance.activeDocument + if layer: + docref.activeLayer = layer + active_layer = layer + elif not isinstance((active_layer := docref.activeLayer), ArtLayer): + _logger.warning( + "Skipping remove content fill as active layer is not an ArtLayer." + ) + return + + # Select pixels of the active layer + select_layer_pixels(active_layer) + selection = docref.selection + + # Guard against no selection made + try: + # Adjust selection, then invert + if contract: + selection.contract(contract) + if smooth: + selection.smooth(smooth) + if feather: + selection.feather(feather) + selection.invert() + + remove_content_fill() + except PS_EXCEPTIONS as exc: + # Unable to fill due to invalid selection + _logger.warning( + "Couldn't complete remove content fill. Skipping automated fill.", + exc_info=exc, + ) + + # Clear selection + with suppress(Exception): + selection.deselect() + + def content_aware_fill() -> None: """Fills the current selection using content aware fill.""" desc = ActionDescriptor() @@ -270,6 +319,13 @@ def generative_fill() -> None: ) +def remove_content_fill() -> None: + """Call Photoshop's "Remove" action on the current selection.""" + APP.instance.executeAction( + APP.instance.sID("removeButton"), display_dialogs=DialogModes.DisplayNoDialogs + ) + + def repair_edges(edge: int = 6) -> None: """Select a small area at the edges of an image and content aware fill to repair upscale damage. diff --git a/src/render/setup.py b/src/render/setup.py index 997a68ec..6588cfd0 100644 --- a/src/render/setup.py +++ b/src/render/setup.py @@ -119,7 +119,7 @@ async def pause( return True if waiting := self._waiting: - return await waiting + await waiting self._waiting = get_running_loop().create_future() if show_photoshop and self.template_instance: diff --git a/src/templates/_core.py b/src/templates/_core.py index 5ccccdea..873bc6db 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -12,11 +12,12 @@ from typing import TYPE_CHECKING, Any, Unpack from omnitils.files import get_unique_filename -from photoshop.api import BlendMode, ElementPlacement, SaveOptions, SolidColor +from photoshop.api import SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection +from photoshop.api.enumerations import BlendMode, ElementPlacement, SaveOptions from PIL import Image import src.helpers as psd @@ -29,6 +30,7 @@ BorderColor, CollectorMode, CollectorPromo, + FillMode, OutputFileType, WatermarkMode, ) @@ -438,7 +440,7 @@ def is_content_aware_enabled(self) -> bool: or all([n not in self.art_reference.name for n in ["Full", "Borderless"]]) ): # By default, fill when we want a fullart image but didn't receive one - return True + return self.config.fill_mode != FillMode.NO_FILL return False @cached_property @@ -786,27 +788,40 @@ def load_artwork( # Frame the artwork psd.frame_layer(layer=art_layer, ref=art_reference) + if self.config.pause_for_manual_art_alignment: + if not self.pause("Adjust the art alignment manually."): + return + # Perform content aware fill if needed if self.is_content_aware_enabled: - # Perform a generative fill - if self.config.generative_fill: + if self.config.fill_mode == FillMode.CONTENT_AWARE_FILL: + psd.content_aware_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + ) + elif self.config.fill_mode == FillMode.GENERATIVE_FILL: if _doc_generated := psd.generative_fill_edges( layer=art_layer, - feather=self.config.feathered_fill, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, close_doc=bool(not self.config.select_variation), docref=self.docref, ): # Document reference was returned, await user intervention - self.render_operation.pause_sync( - "Select a Generative Fill variation, then click Continue ...", - ) + if not self.pause("Select a Generative Fill variation."): + return _doc_generated.close(SaveOptions.SaveChanges) return - - # Perform a content aware fill - psd.content_aware_fill_edges( - layer=art_layer, feather=self.config.feathered_fill - ) + elif self.config.fill_mode == FillMode.REMOVE_CONTENT_FILL: + psd.remove_content_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + ) def paste_scryfall_scan( self, rotate: bool = False, visible: bool = True @@ -1500,6 +1515,20 @@ def rules_text_and_pt_layers(self) -> None: def cancel(self) -> None: self.event.set() + def pause(self, message: str | None, show_photoshop: bool = True) -> bool: + response = self.render_operation.pause_sync(message, show_photoshop) + if self.config.minimize_photoshop: + self.app.set_window_state(WindowState.MINIMIZE) + return response + + async def pause_async( + self, message: str | None, show_photoshop: bool = True + ) -> bool: + response = await self.render_operation.pause(message, show_photoshop) + if self.config.minimize_photoshop: + self.app.set_window_state(WindowState.MINIMIZE) + return response + async def execute(self) -> bool: """Perform actions to render the card using this template. @@ -1515,7 +1544,7 @@ async def execute(self) -> bool: return False if self.config.minimize_photoshop: - APP.instance.set_window_state(WindowState.MINIMIZE) + self.app.set_window_state(WindowState.MINIMIZE) # Pre-process layout data if not self.run_tasks( @@ -1592,7 +1621,7 @@ async def execute(self) -> bool: # Manual edit step? if self.config.exit_early: - await self.render_operation.pause("Rendering paused for manual editing.") + await self.pause_async("Rendering paused for manual editing.") # Save the document if not self.run_tasks( diff --git a/src/utils/tests.py b/src/utils/tests.py index 9827340e..623478f6 100644 --- a/src/utils/tests.py +++ b/src/utils/tests.py @@ -3,6 +3,7 @@ from src._config import AppConfig from src._state import PATH from src.enums.mtg import LayoutType +from src.enums.settings import FillMode from src.layouts import SplitLayout from src.render.setup import RenderOperation from src.utils.data_structures import parse_model @@ -18,7 +19,7 @@ def get_template_render_test_cases() -> dict[LayoutType, dict[str, str]]: def prepare_test_render(render_operation: RenderOperation, config: AppConfig): """Modifies render operation so that the render process won't pause for manual editing. Call this after loading the config for a test render.""" - config.generative_fill = False + config.fill_mode = FillMode.NO_FILL render_operation.do_not_pause = True From cc0f7ad25ed401faf6a72fdc45b8e7c97cf59379 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 20 Feb 2026 10:13:15 +0200 Subject: [PATCH 067/190] fix(scryfall.py): Prevent validation errors from getting obscured when Scryfall requests don't pass validation --- src/cards.py | 29 +++++++++++++---------------- src/utils/scryfall.py | 24 ++++++++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/cards.py b/src/cards.py index 29bbd65e..8778ebd6 100644 --- a/src/cards.py +++ b/src/cards.py @@ -3,7 +3,6 @@ * Handles raw card data fetching and processing """ -from contextlib import suppress from logging import Logger, getLogger from pathlib import Path from typing import TypedDict @@ -129,16 +128,19 @@ def get_card_data( ) # Query the card in English, retry with extras if failed - with suppress(Exception): + try: return action(*params, **kwargs) - if not number and not cfg.scry_extras: - # Retry with extras included, case: Planar cards - try: - kwargs['include_extras'] = 'True' - data = action(*params, **kwargs) - return data - except ScryfallException: - _logger.exception("Couldn't retrieve card from Scryfall even with include_extras enabled.") + except Exception as exc: + if not number and not cfg.scry_extras: + # Retry with extras included, case: Planar cards + try: + kwargs['include_extras'] = 'True' + data = action(*params, **kwargs) + return data + except ScryfallException: + _logger.exception("Couldn't retrieve card from Scryfall even with include_extras enabled.") + else: + _logger.exception("Couldn't retrieve card from Scryfall.") """ @@ -474,9 +476,4 @@ def strip_reminder_text(text: str) -> str: text_stripped = CardTextPatterns.TEXT_REMINDER.sub("", text) # Remove any extra whitespace - text_stripped = CardTextPatterns.EXTRA_SPACE.sub('', text_stripped).strip() - - # Return the stripped text if it isn't empty - if text_stripped: - return text_stripped - return text + return CardTextPatterns.EXTRA_SPACE.sub('', text_stripped).strip() diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index f9d1b885..5d26fcc8 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -89,6 +89,7 @@ "waxingandwaningmoondfc", "showcase", "extendedart", + "fullart", "companion", "etched", "snow", @@ -490,7 +491,8 @@ def get_card_via_url(url: str) -> ScryfallCard: err = ScryfallError.model_validate_json(res.content) raise _get_scryfall_exception(error=err, response=res) except ValidationError: - raise ScryfallException(exception=exc) + pass + raise ScryfallException(response=res) from exc if is_playable_card(card): return card @@ -539,12 +541,13 @@ def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> Scryfa lang=lang, ) except ValidationError: - raise ScryfallException( - exception=exc, - card_set=card_set, - card_number=card_number, - lang=lang, - ) + pass + raise ScryfallException( + response=res, + card_set=card_set, + card_number=card_number, + lang=lang, + ) from exc if is_playable_card(card): return card @@ -619,9 +622,10 @@ def get_card_search( lang=lang, ) except ValidationError: - raise ScryfallException( - exception=exc, card_name=card_name, card_set=card_set, lang=lang - ) + pass + raise ScryfallException( + card_name=card_name, card_set=card_set, lang=lang + ) from exc for card in list_data.data: if is_playable_card(card): From 2b688c9d1ca4633957e7cfa6db22a4b50b064da0 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 20 Feb 2026 10:31:17 +0200 Subject: [PATCH 068/190] feat: Allow adjusting the PNG compression level --- src/_config.py | 3 +++ src/data/config/app.toml | 6 ++++++ src/helpers/document.py | 18 +++++++++++------- src/templates/_core.py | 4 +++- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/_config.py b/src/_config.py index ea70e5a1..dd3c3a91 100644 --- a/src/_config.py +++ b/src/_config.py @@ -107,6 +107,9 @@ def update_definitions(self): option="Output.File.Name", fallback="#name (#frame<, #suffix>) [#set] {#num}", ) + self.png_compression_level = self.file.getint( + "APP.FILES", "PNG.Compression.Level", fallback=3 + ) # APP - DATA self.use_printed_texts = self.file.getboolean( diff --git a/src/data/config/app.toml b/src/data/config/app.toml index ad637ff6..91ab7d0e 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -28,6 +28,12 @@ desc = """Overwrite rendered files with identical file names.""" type = "bool" default = 1 +[FILES."PNG.Compression.Level"] +title = "PNG Compression Level" +desc = """What compression level to use when saving PNG files. Accepts values between 1-9. Higher values result in slightly smaller files but can slow down the saving process significantly.""" +type = "int" +default = 3 + ### # * Scryfall Settings ### diff --git a/src/helpers/document.py b/src/helpers/document.py index c13a5d9d..bab1c12a 100644 --- a/src/helpers/document.py +++ b/src/helpers/document.py @@ -8,18 +8,20 @@ from photoshop.api import ( ActionDescriptor, ActionReference, - DialogModes, - ElementPlacement, - FormatOptionsType, JPEGSaveOptions, PhotoshopSaveOptions, PNGSaveOptions, - PurgeTarget, - SaveOptions, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import ( + DialogModes, + ElementPlacement, + FormatOptionsType, + PurgeTarget, + SaveOptions, +) from src import APP from src.helpers.layers import create_new_layer @@ -355,7 +357,9 @@ def trim_transparent_pixels() -> None: """ -def save_document_png(path: Path, docref: Document | None = None) -> None: +def save_document_png( + path: Path, docref: Document | None = None, compression: int = 3 +) -> None: """Save the current document as a PNG. Args: @@ -364,7 +368,7 @@ def save_document_png(path: Path, docref: Document | None = None) -> None: """ docref = docref or APP.instance.activeDocument png_options = PNGSaveOptions() - png_options.compression = 9 + png_options.compression = compression png_options.interlaced = False docref.saveAs( file_path=str(path.with_suffix(".png")), options=png_options, asCopy=True diff --git a/src/templates/_core.py b/src/templates/_core.py index 873bc6db..262c42b1 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -236,7 +236,9 @@ def RGB_WHITE(self) -> SolidColor: def save_mode(self) -> Callable[[Path, Document | None], None]: """Callable: Function called to save the rendered image.""" if self.config.output_file_type == OutputFileType.PNG: - return psd.save_document_png + return lambda pth, doc: psd.save_document_png( + pth, doc, self.config.png_compression_level + ) if self.config.output_file_type == OutputFileType.PSD: return psd.save_document_psd return psd.save_document_jpeg From f3a67244daa92e6f02363a9573ac417fd0ebf23e Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 22 Feb 2026 06:30:04 +0200 Subject: [PATCH 069/190] fix(remove_content_fill_edges): Prevent "Remove Content" fill from sampling other layers except the art layer Remove Content fill requires the same workaround as Generate Fill for preventing, e.g., text layers being considered as part of the image to expand. --- src/helpers/design.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/helpers/design.py b/src/helpers/design.py index 74a2407f..ace8131a 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -230,8 +230,23 @@ def remove_content_fill_edges( ) return + # Create a fill layer the size of the document in order to + # ensure that the smart document retains the document's size + fill_layer: ArtLayer = docref.artLayers.add() + fill_layer.move(active_layer, ElementPlacement.PlaceAfter) + fill_layer_primary() + fill_layer.opacity = 0 + + # Create a smart layer document and enter it in order to + # ensure that content is sampled only from the art layer + select_layers([active_layer, fill_layer]) + smart_layer() + edit_smart_layer() + # Select pixels of the active layer - select_layer_pixels(active_layer) + docref = APP.instance.activeDocument + if isinstance((layer_in_smart_doc := docref.activeLayer), ArtLayer): + select_layer_pixels(layer_in_smart_doc) selection = docref.selection # Guard against no selection made @@ -257,6 +272,8 @@ def remove_content_fill_edges( with suppress(Exception): selection.deselect() + docref.close(SaveOptions.SaveChanges) + def content_aware_fill() -> None: """Fills the current selection using content aware fill.""" From dda1f22850baa522fc816d6c03f8411c41ad79e6 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 25 Feb 2026 08:33:15 +0200 Subject: [PATCH 070/190] feat: Allow hooking into the art fill area selection process --- src/helpers/design.py | 24 ++++++++++++++++++++++-- src/templates/_core.py | 10 ++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/helpers/design.py b/src/helpers/design.py index ace8131a..8ac8a789 100644 --- a/src/helpers/design.py +++ b/src/helpers/design.py @@ -2,12 +2,14 @@ * Helpers: Design """ +from collections.abc import Callable from contextlib import suppress from logging import getLogger from photoshop.api import ActionDescriptor, ActionReference, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document +from photoshop.api._selection import Selection from photoshop.api.enumerations import ( BlendMode, DialogModes, @@ -83,7 +85,11 @@ def fill_empty_area(reference: ArtLayer, color: SolidColor | None = None) -> Art def content_aware_fill_edges( - layer: ArtLayer | None = None, contract: int = 10, smooth: int = 0, feather: int = 5 + layer: ArtLayer | None = None, + contract: int = 10, + smooth: int = 0, + feather: int = 5, + art_selection_hook: Callable[[ArtLayer, Selection], None] | None = None, ) -> None: """Fills pixels outside art layer using content-aware fill. @@ -107,6 +113,9 @@ def content_aware_fill_edges( select_layer_pixels(active_layer) selection = docref.selection + if art_selection_hook: + art_selection_hook(active_layer, selection) + # Guard against no selection made try: # Adjust selection, then invert @@ -136,6 +145,7 @@ def generative_fill_edges( feather: int = 5, close_doc: bool = True, docref: Document | None = None, + art_selection_hook: Callable[[ArtLayer, Selection], None] | None = None, ) -> Document | None: """Fills pixels outside an art layer using AI powered generative fill. @@ -174,6 +184,9 @@ def generative_fill_edges( select_layer_pixels(active_layer) selection = docref.selection + if art_selection_hook and isinstance((art_layer := docref.activeLayer), ArtLayer): + art_selection_hook(art_layer, selection) + # Guard against no selection made try: # Adjust selection, then invert @@ -211,7 +224,11 @@ def generative_fill_edges( def remove_content_fill_edges( - layer: ArtLayer | None = None, contract: int = 10, smooth: int = 0, feather: int = 5 + layer: ArtLayer | None = None, + contract: int = 10, + smooth: int = 0, + feather: int = 5, + art_selection_hook: Callable[[ArtLayer, Selection], None] | None = None, ) -> None: """Fills pixels outside art layer using remove content fill. @@ -249,6 +266,9 @@ def remove_content_fill_edges( select_layer_pixels(layer_in_smart_doc) selection = docref.selection + if art_selection_hook and isinstance((art_layer := docref.activeLayer), ArtLayer): + art_selection_hook(art_layer, selection) + # Guard against no selection made try: # Adjust selection, then invert diff --git a/src/templates/_core.py b/src/templates/_core.py index 262c42b1..fc2427d4 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -746,6 +746,13 @@ def art_action(self) -> Callable[[], None] | None: """Function that is called to perform an action on the imported art.""" return + @cached_property + def art_fill_selection_hook(self) -> Callable[[ArtLayer, Selection], None] | None: + """A hook for adjusting the selected art area. + Area outside the selected area will be filled. + The hook runs before contract, feather, etc.""" + return None + def load_artwork( self, art_file: str | Path | None = None, @@ -802,6 +809,7 @@ def load_artwork( contract=self.config.fill_contract, smooth=self.config.fill_smooth, feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, ) elif self.config.fill_mode == FillMode.GENERATIVE_FILL: if _doc_generated := psd.generative_fill_edges( @@ -811,6 +819,7 @@ def load_artwork( feather=self.config.fill_feather, close_doc=bool(not self.config.select_variation), docref=self.docref, + art_selection_hook=self.art_fill_selection_hook, ): # Document reference was returned, await user intervention if not self.pause("Select a Generative Fill variation."): @@ -823,6 +832,7 @@ def load_artwork( contract=self.config.fill_contract, smooth=self.config.fill_smooth, feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, ) def paste_scryfall_scan( From 0594159b72a877da49e79e13d1484cf7d0272271 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 15:30:37 +0200 Subject: [PATCH 071/190] fix: Save app and default template configs on quit --- src/gui/qml/models/settings_tree_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/qml/models/settings_tree_model.py b/src/gui/qml/models/settings_tree_model.py index ffec4814..09e3bdc4 100644 --- a/src/gui/qml/models/settings_tree_model.py +++ b/src/gui/qml/models/settings_tree_model.py @@ -39,6 +39,7 @@ def __init__( ) -> None: super().__init__(parent) + self._app_config = app_config self._template_library = template_library self._root = TreeItem(data=SettingSectionItem(name="root")) self._selected_model_index: QModelIndex = QModelIndex() @@ -198,6 +199,8 @@ def select_settings_section( @Slot() def save_configs(self) -> None: + self._app_config.app_config.save() + self._app_config.base_config.save() self._save_template_configs( self._template_library.built_in_templates_by_name.values() ) From a187ff9ce0e4c7a9b22512bbcc74f5c7d579df63 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 15:41:34 +0200 Subject: [PATCH 072/190] feat: Reuse the same Photoshop document if the next render uses the same document file The old implementation already did this, so this can alse be thought as a regression fix. Additionally render operations are sorted so that the amount of document switches is minimized. --- src/gui/qml/models/batch_rendering_model.py | 29 +++++--- src/gui/qml/models/render_operations_model.py | 37 ++++------ src/gui/qml/models/template_list_model.py | 30 +++++--- src/render/render_queue.py | 73 ++++++++++++++++--- src/render/setup.py | 39 ++++------ src/utils/data_structures.py | 9 ++- 6 files changed, 140 insertions(+), 77 deletions(-) diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index 0fc59e9f..f47417cb 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -15,6 +15,7 @@ RenderableTemplate, TemplateLibrary, ) +from src.cards import CardDetails from src.enums.mtg import LayoutCategory from src.gui.qml.models.file_dialog_model import FileDialogModel from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel @@ -23,6 +24,7 @@ from src.render.render_queue import RenderQueue, cancel_with_render from src.render.setup import prepare_render_operations from src.utils.images import match_images_with_data_files +from src.utils.scryfall import ScryfallCard _logger = getLogger(__name__) @@ -183,20 +185,27 @@ def select_template_for_layout( q_idx = self.createIndex(layout_category_idx, 0) self.dataChanged.emit(q_idx, q_idx, [self.get_role("selected")]) - def render_files(self, paths: list[Path]) -> None: + async def render_files(self, paths: list[Path]) -> None: if paths: _logger.info( f"Queueing { len(paths) } batch mode entries for render. Do note that the actual amount of renders might be lower if you selected JSON files or art for split cards." ) - if render_operations := prepare_render_operations( - self.template_choices, - match_images_with_data_files(paths), - file_dialog=self._file_dialog_model, - message_dialog=self._message_dialog_model, - ): - self._render_queue.enqueue(*render_operations) + matched_inputs = match_images_with_data_files(paths) + + def add_render( + input: CardDetails | tuple[CardDetails, ScryfallCard], + ) -> None: + if render_operations := prepare_render_operations( + self.template_choices, + (input,), + file_dialog=self._file_dialog_model, + message_dialog=self._message_dialog_model, + ): + self._render_queue.enqueue(render_operations[0]) + + await gather(*[to_thread(add_render, input) for input in matched_inputs]) @Slot() def render_selections(self) -> None: @@ -210,7 +219,7 @@ async def action(): ], ) - self.render_files(selections) + await self.render_files(selections) cancel_with_render(ensure_future(action()), self._render_queue) @@ -223,7 +232,7 @@ def render_targets(self, urls: list[QUrl]) -> None: _logger.exception("Failed to process drag & dropped files.") if paths: cancel_with_render( - ensure_future(to_thread(self.render_files, paths)), self._render_queue + ensure_future(self.render_files(paths)), self._render_queue ) @Slot(str, bool) diff --git a/src/gui/qml/models/render_operations_model.py b/src/gui/qml/models/render_operations_model.py index 75f11b7a..ab95aa31 100644 --- a/src/gui/qml/models/render_operations_model.py +++ b/src/gui/qml/models/render_operations_model.py @@ -5,7 +5,7 @@ from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel from src.gui.qml.models.pydantic_q_list_model import PydanticQListModel -from src.render.render_queue import RenderQueue, RenderResult +from src.render.render_queue import QueuedOperation, RenderQueue, RenderResult from src.render.setup import PausedEventArgs, RenderOperation @@ -49,27 +49,22 @@ def __init__( # region Queue - def _on_queued(self, render_operations: tuple[RenderOperation, ...]) -> None: - render_opeartion_details: list[RenderOperationDetails] = [] - for render_operation in render_operations: - render_op_details = RenderOperationDetails( - image_name=render_operation.layout.art_file.name, - image_path=str(render_operation.layout.art_file), - card_name=render_operation.layout.name, - card_artist=render_operation.layout.artist, - card_set=render_operation.layout.set, - card_collector_number=render_operation.layout.collector_number_raw - or "", - layout_name=render_operation.layout.category, - class_name=render_operation.template_class.__name__, - ) - render_op_details.render_operation = render_operation - render_opeartion_details.append(render_op_details) - row_count = self.rowCount() - self.beginInsertRows( - QModelIndex(), row_count, row_count + len(render_operations) - 1 + def _on_queued(self, queued_operation: QueuedOperation) -> None: + render_operation = queued_operation["operation"] + idx = queued_operation["index"] + render_op_details = RenderOperationDetails( + image_name=render_operation.layout.art_file.name, + image_path=str(render_operation.layout.art_file), + card_name=render_operation.layout.name, + card_artist=render_operation.layout.artist, + card_set=render_operation.layout.set, + card_collector_number=render_operation.layout.collector_number_raw or "", + layout_name=render_operation.layout.category, + class_name=render_operation.template_class.__name__, ) - self.items.extend(render_opeartion_details) + render_op_details.render_operation = render_operation + self.beginInsertRows(QModelIndex(), idx, idx) + self.items.insert(idx, render_op_details) self.endInsertRows() def _on_dequeued(self, data: tuple[RenderOperation, int]) -> None: diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index f22bbf83..51a928f2 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -18,6 +18,7 @@ TemplateLibrary, sort_layout_categories, ) +from src.cards import CardDetails from src.enums.mtg import LayoutCategory from src.gui.qml.models.file_dialog_model import FileDialogModel from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel @@ -27,6 +28,7 @@ from src.render.setup import prepare_render_operations from src.utils.data_structures import first from src.utils.images import match_images_with_data_files +from src.utils.scryfall import ScryfallCard _logger = getLogger(__name__) @@ -135,7 +137,7 @@ def __init__( self._sort_items() - def render_files(self, paths: list[Path]) -> None: + async def render_files(self, paths: list[Path]) -> None: if paths: _logger.info( f"Queueing { @@ -150,13 +152,21 @@ def render_files(self, paths: list[Path]) -> None: ] else: template = self.built_in_templates[selected_template_entry.name] - if render_operations := prepare_render_operations( - template, - match_images_with_data_files(paths), - file_dialog=self._file_dialog_model, - message_dialog=self._message_dialog_model, - ): - self._render_queue.enqueue(*render_operations) + + matched_inputs = match_images_with_data_files(paths) + + def add_render( + input: CardDetails | tuple[CardDetails, ScryfallCard], + ) -> None: + if render_operations := prepare_render_operations( + template, + (input,), + file_dialog=self._file_dialog_model, + message_dialog=self._message_dialog_model, + ): + self._render_queue.enqueue(render_operations[0]) + + await gather(*[to_thread(add_render, input) for input in matched_inputs]) @Slot() def render_selections(self) -> None: @@ -170,7 +180,7 @@ async def action(): ], ) - self.render_files(selections) + await self.render_files(selections) cancel_with_render(ensure_future(action()), self._render_queue) @@ -183,7 +193,7 @@ def render_targets(self, urls: list[QUrl]) -> None: _logger.exception("Failed to process drag & dropped files.") if paths: cancel_with_render( - ensure_future(to_thread(self.render_files, paths)), self._render_queue + ensure_future(self.render_files(paths)), self._render_queue ) @Slot(str, bool) diff --git a/src/render/render_queue.py b/src/render/render_queue.py index a2d95fcb..5d177538 100644 --- a/src/render/render_queue.py +++ b/src/render/render_queue.py @@ -1,9 +1,11 @@ +from _ctypes import COMError from asyncio import EventLoop, Future, Task, run from logging import getLogger from threading import Lock, Thread from typing import TypedDict from src.render.setup import RenderOperation +from src.utils.data_structures import find_last_index from src.utils.event import SubscribableEvent _logger = getLogger(__name__) @@ -14,6 +16,11 @@ class RenderResult(TypedDict): result: bool +class QueuedOperation(TypedDict): + operation: RenderOperation + index: int + + class RenderQueue: """Queue for render operations. Runs the render loop in a separate thread.""" @@ -26,9 +33,7 @@ def __init__(self) -> None: self._active_operation: RenderOperation | None = None self._cancel: bool = False - self.queued: SubscribableEvent[tuple[RenderOperation, ...]] = ( - SubscribableEvent() - ) + self.queued: SubscribableEvent[QueuedOperation] = SubscribableEvent() self.dequeued: SubscribableEvent[tuple[RenderOperation, int]] = ( SubscribableEvent() ) @@ -40,10 +45,24 @@ def __init__(self) -> None: def active_operation(self) -> RenderOperation | None: return self._active_operation - def enqueue(self, *operations: RenderOperation) -> None: + def enqueue(self, operation: RenderOperation) -> None: with self._queue_lock: - self._queue.extend(operations) - self.queued.trigger(operations) + # Order renders so that that the template document has to be switched + # as few times as possible + if ( + idx := find_last_index( + self._queue, + lambda item: ( + item.layout.template_file == operation.layout.template_file + ), + ) + ) > -1: + idx += 1 + self._queue.insert(idx, operation) + else: + idx = len(self._queue) + self._queue.append(operation) + self.queued.trigger({"operation": operation, "index": idx}) self.execute_render() def dequeue(self, index: int) -> RenderOperation | None: @@ -73,15 +92,47 @@ async def loop_render(): if locked: try: try: - while not self._cancel and (next_render := self._queue.pop(0)): - self._active_operation = next_render - self.started.trigger(next_render) + while not self._cancel and ( + render_operation := self._queue.pop(0) + ): + self._active_operation = render_operation + self.started.trigger(render_operation) result = False try: - result = await next_render.render() + result = await render_operation.render() finally: + try: + # As an optimization the document isn't closed if + # the next render also uses the same document + if ( + render_operation.template_instance + and result + and ( + len(self._queue) == 0 + or self._queue[0].layout.template_file + != render_operation.layout.template_file + ) + ): + render_operation.template_instance.docref.close() + except COMError as exc: + _logger.debug( + f"Couldn't close the document for { + f'{ + render_operation.layout.display_name + } ({render_operation.layout.artist}) [{ + render_operation.layout.set + }] {{{ + render_operation.layout.collector_number_raw + }}} |{ + render_operation.layout.category.value + }| \\{ + render_operation.template_class.__name__ + }/' + }. It might have already been closed.", + exc_info=exc, + ) self.finished.trigger( - {"operation": next_render, "result": result} + {"operation": render_operation, "result": result} ) except IndexError: return diff --git a/src/render/setup.py b/src/render/setup.py index 6588cfd0..8e74a749 100644 --- a/src/render/setup.py +++ b/src/render/setup.py @@ -1,4 +1,3 @@ -from _ctypes import COMError from asyncio import CancelledError, Future, Task, create_task, get_running_loop from collections.abc import Callable, Iterable, Mapping from logging import getLogger @@ -77,29 +76,21 @@ async def render(self) -> bool: self.render_task = create_task(self.template_instance.execute()) try: - try: - result = await self.render_task - except CancelledError: - result = False - - _logger.info( - f"{'Finished' if result else 'Cancelled'} rendering { - card_display_name - }
Rendering took { - round(perf_counter() - start_time, 1) - } seconds" - ) - return result - finally: - try: - self.template_instance.docref.close() - except COMError as exc: - _logger.debug( - f"Couldn't close the document for { - card_display_name - }. It might have already been closed.", - exc_info=exc, - ) + result = await self.render_task + except CancelledError: + result = False + + if not result: + self.template_instance.docref.close() + + _logger.info( + f"{'Finished' if result else 'Cancelled'} rendering { + card_display_name + }
Rendering took { + round(perf_counter() - start_time, 1) + } seconds" + ) + return result except Exception: _logger.exception(f"Failed rendering {card_display_name}") return False diff --git a/src/utils/data_structures.py b/src/utils/data_structures.py index a37d18e2..792a7ad2 100644 --- a/src/utils/data_structures.py +++ b/src/utils/data_structures.py @@ -1,5 +1,5 @@ import tomllib -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from pathlib import Path import yaml @@ -17,6 +17,13 @@ def find_index[T](iterable: Iterable[T], condition: Callable[[T], bool]) -> int: return -1 +def find_last_index[T](sequence: Sequence[T], condition: Callable[[T], bool]) -> int: + for idx, item in enumerate(reversed(sequence)): + if condition(item): + return len(sequence) - 1 - idx + return -1 + + def parse_model[T: BaseModel](path: Path, model: type[T]) -> T: if path.suffix == ".json": return model.model_validate_json(path.read_bytes()) From 7004936ae71a3115761e8e4eb4ee29252b1e8fbd Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 15:42:59 +0200 Subject: [PATCH 073/190] fix(PlaneswalkerLayout): Remove reminder text from Planeswalker ability texts if requested --- src/layouts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index 86395420..32c437b5 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -1062,7 +1062,7 @@ def pw_size(self) -> int: @cached_property def pw_abilities(self) -> list[PlaneswalkerAbility]: """Processes Planeswalker text into listed abilities.""" - lines = CardTextPatterns.PLANESWALKER.findall(self.oracle_text_raw) + lines = CardTextPatterns.PLANESWALKER.findall(self.oracle_text) en_lines = lines.copy() # Process alternate language lines if needed From 600310f540b5717444f1e1ef5f7eda58a78bec90 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 15:52:16 +0200 Subject: [PATCH 074/190] fix(RenderQueue): Take active render operation into account when ordering new renders --- src/render/render_queue.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/render/render_queue.py b/src/render/render_queue.py index 5d177538..000bbcf2 100644 --- a/src/render/render_queue.py +++ b/src/render/render_queue.py @@ -49,14 +49,23 @@ def enqueue(self, operation: RenderOperation) -> None: with self._queue_lock: # Order renders so that that the template document has to be switched # as few times as possible + idx = find_last_index( + self._queue, + lambda item: ( + item.layout.template_file == operation.layout.template_file + ), + ) + + # Active operation has to be checked separately if no match was found otherwise if ( - idx := find_last_index( - self._queue, - lambda item: ( - item.layout.template_file == operation.layout.template_file - ), - ) - ) > -1: + idx == -1 + and self.active_operation + and operation.layout.template_file + == self.active_operation.layout.template_file + ): + idx = 0 + + if idx > -1: idx += 1 self._queue.insert(idx, operation) else: From 4a608de08f7ae066e2b3b829b3cf51e3982e4b86 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 15:53:31 +0200 Subject: [PATCH 075/190] build(poetry.lock): Update dependencies --- poetry.lock | 651 +++++++++++++++++++++++++--------------------------- 1 file changed, 317 insertions(+), 334 deletions(-) diff --git a/poetry.lock b/poetry.lock index 42c271a9..d06a4337 100644 --- a/poetry.lock +++ b/poetry.lock @@ -285,14 +285,14 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, - {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, ] [[package]] @@ -558,14 +558,14 @@ markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "commitizen" -version = "4.13.7" +version = "4.13.9" description = "Python commitizen client tool" optional = false python-versions = "<4.0,>=3.10" groups = ["dev"] files = [ - {file = "commitizen-4.13.7-py3-none-any.whl", hash = "sha256:7e179fa2a29626d330e38332e3f306caa3f11ac1cc0bc139f61dff167f224434"}, - {file = "commitizen-4.13.7.tar.gz", hash = "sha256:61b1872a3b5b9da69f1819a4e238249071a636ef422cf6f3735c491eb77e9526"}, + {file = "commitizen-4.13.9-py3-none-any.whl", hash = "sha256:d2af3d6a83cacec9d5200e17768942c5de6266f93d932c955986c60c4285f2db"}, + {file = "commitizen-4.13.9.tar.gz", hash = "sha256:2b4567ed50555e10920e5bd804a6a4e2c42ec70bb74f14a83f2680fe9eaf9727"}, ] [package.dependencies] @@ -757,14 +757,14 @@ files = [ [[package]] name = "filelock" -version = "3.24.2" +version = "3.25.0" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556"}, - {file = "filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b"}, + {file = "filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047"}, + {file = "filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3"}, ] [[package]] @@ -892,39 +892,6 @@ gitdb = ">=4.0.1,<5" doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] -[[package]] -name = "griffe" -version = "2.0.0" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa"}, -] - -[package.dependencies] -griffecli = "2.0.0" -griffelib = "2.0.0" - -[package.extras] -pypi = ["griffelib[pypi] (==2.0.0)"] - -[[package]] -name = "griffecli" -version = "2.0.0" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d"}, -] - -[package.dependencies] -colorama = ">=0.4" -griffelib = "2.0.0" - [[package]] name = "griffelib" version = "2.0.0" @@ -979,14 +946,14 @@ files = [ [[package]] name = "identify" -version = "2.6.16" +version = "2.6.17" description = "File identification library for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0"}, - {file = "identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980"}, + {file = "identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0"}, + {file = "identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d"}, ] [package.extras] @@ -1247,103 +1214,103 @@ files = [ [[package]] name = "librt" -version = "0.8.0" +version = "0.8.1" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "librt-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db63cf3586a24241e89ca1ce0b56baaec9d371a328bd186c529b27c914c9a1ef"}, - {file = "librt-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba9d9e60651615bc614be5e21a82cdb7b1769a029369cf4b4d861e4f19686fb6"}, - {file = "librt-0.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb4b3ad543084ed79f186741470b251b9d269cd8b03556f15a8d1a99a64b7de5"}, - {file = "librt-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d2720335020219197380ccfa5c895f079ac364b4c429e96952cd6509934d8eb"}, - {file = "librt-0.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726305d3e53419d27fc8cdfcd3f9571f0ceae22fa6b5ea1b3662c2e538f833e"}, - {file = "librt-0.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3d107f603b5ee7a79b6aa6f166551b99b32fb4a5303c4dfcb4222fc6a0335e"}, - {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41064a0c07b4cc7a81355ccc305cb097d6027002209ffca51306e65ee8293630"}, - {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c6e4c10761ddbc0d67d2f6e2753daf99908db85d8b901729bf2bf5eaa60e0567"}, - {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba581acad5ac8f33e2ff1746e8a57e001b47c6721873121bf8bbcf7ba8bd3aa4"}, - {file = "librt-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bdab762e2c0b48bab76f1a08acb3f4c77afd2123bedac59446aeaaeed3d086cf"}, - {file = "librt-0.8.0-cp310-cp310-win32.whl", hash = "sha256:6a3146c63220d814c4a2c7d6a1eacc8d5c14aed0ff85115c1dfea868080cd18f"}, - {file = "librt-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:bbebd2bba5c6ae02907df49150e55870fdd7440d727b6192c46b6f754723dde9"}, - {file = "librt-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369"}, - {file = "librt-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6"}, - {file = "librt-0.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa"}, - {file = "librt-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f"}, - {file = "librt-0.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef"}, - {file = "librt-0.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9"}, - {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4"}, - {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6"}, - {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da"}, - {file = "librt-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1"}, - {file = "librt-0.8.0-cp311-cp311-win32.whl", hash = "sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258"}, - {file = "librt-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817"}, - {file = "librt-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4"}, - {file = "librt-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645"}, - {file = "librt-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467"}, - {file = "librt-0.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a"}, - {file = "librt-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45"}, - {file = "librt-0.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d"}, - {file = "librt-0.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c"}, - {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f"}, - {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9"}, - {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a"}, - {file = "librt-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79"}, - {file = "librt-0.8.0-cp312-cp312-win32.whl", hash = "sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c"}, - {file = "librt-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8"}, - {file = "librt-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e"}, - {file = "librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1"}, - {file = "librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf"}, - {file = "librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8"}, - {file = "librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad"}, - {file = "librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01"}, - {file = "librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada"}, - {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae"}, - {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d"}, - {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3"}, - {file = "librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b"}, - {file = "librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935"}, - {file = "librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab"}, - {file = "librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2"}, - {file = "librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda"}, - {file = "librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556"}, - {file = "librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06"}, - {file = "librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376"}, - {file = "librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816"}, - {file = "librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e"}, - {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52"}, - {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da"}, - {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab"}, - {file = "librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3"}, - {file = "librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a"}, - {file = "librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7"}, - {file = "librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4"}, - {file = "librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb"}, - {file = "librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5"}, - {file = "librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c"}, - {file = "librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546"}, - {file = "librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944"}, - {file = "librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e"}, - {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61"}, - {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05"}, - {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25"}, - {file = "librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c"}, - {file = "librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447"}, - {file = "librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9"}, - {file = "librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc"}, - {file = "librt-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b705f85311ee76acec5ee70806990a51f0deb519ea0c29c1d1652d79127604d"}, - {file = "librt-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7ce0a8cb67e702dcb06342b2aaaa3da9fb0ddc670417879adfa088b44cf7b3b6"}, - {file = "librt-0.8.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:aaadec87f45a3612b6818d1db5fbfe93630669b7ee5d6bdb6427ae08a1aa2141"}, - {file = "librt-0.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56901f1eec031396f230db71c59a01d450715cbbef9856bf636726994331195d"}, - {file = "librt-0.8.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b055bb3abaf69abed25743d8fc1ab691e4f51a912ee0a6f9a6c84f4bbddb283d"}, - {file = "librt-0.8.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ef3bd856373cf8e7382402731f43bfe978a8613b4039e49e166e1e0dc590216"}, - {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e0ffe88ebb5962f8fb0ddcbaaff30f1ea06a79501069310e1e030eafb1ad787"}, - {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82e61cd1c563745ad495387c3b65806bfd453badb4adbc019df3389dddee1bf6"}, - {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:667e2513cf69bfd1e1ed9a00d6c736d5108714ec071192afb737987955888a25"}, - {file = "librt-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b6caff69e25d80c269b1952be8493b4d94ef745f438fa619d7931066bdd26de"}, - {file = "librt-0.8.0-cp39-cp39-win32.whl", hash = "sha256:02a9fe85410cc9bef045e7cb7fd26fdde6669e6d173f99df659aa7f6335961e9"}, - {file = "librt-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:de076eaba208d16efb5962f99539867f8e2c73480988cb513fcf1b5dbb0c9dcf"}, - {file = "librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b"}, + {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, + {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, + {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, + {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, + {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, + {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, + {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, + {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, + {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, + {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, + {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, + {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, + {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, + {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, + {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, + {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, + {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, + {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, + {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, + {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, + {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, + {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, + {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, + {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, + {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, + {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, + {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, + {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, + {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, + {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, + {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, + {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, + {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, + {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, + {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, + {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, + {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, + {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, + {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, + {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, + {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, + {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, + {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, ] [[package]] @@ -1798,14 +1765,14 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.7.1" +version = "9.7.3" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c"}, - {file = "mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8"}, + {file = "mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d"}, + {file = "mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22"}, ] [package.dependencies] @@ -1822,9 +1789,9 @@ pymdown-extensions = ">=10.2" requests = ">=2.30" [package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<12.0)"] -recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] +git = ["mkdocs-git-committers-plugin-2 (>=1.1)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4)"] +imaging = ["cairosvg (>=2.6)", "pillow (>=10.2)"] +recommended = ["mkdocs-minify-plugin (>=0.7)", "mkdocs-redirects (>=1.2)", "mkdocs-rss-plugin (>=1.6)"] [[package]] name = "mkdocs-material-extensions" @@ -1914,18 +1881,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "2.0.2" +version = "2.0.3" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0"}, - {file = "mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8"}, + {file = "mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12"}, + {file = "mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8"}, ] [package.dependencies] -griffe = ">=1.13" +griffelib = ">=2.0" mkdocs-autorefs = ">=1.4" mkdocstrings = ">=0.30" @@ -2300,7 +2267,7 @@ yarl = "^1.22.0" type = "git" url = "https://github.com/pappnu/omnitils.git" reference = "dev" -resolved_reference = "3c9acacc479067b092354c8ac345f24d24ec33d1" +resolved_reference = "dab468c228d746a1832f421a092d902b4a3f5fb8" [[package]] name = "packaging" @@ -2396,7 +2363,7 @@ wheel = "^0.46.3" type = "git" url = "https://github.com/pappnu/photoshop-python-api.git" reference = "type-annotation" -resolved_reference = "cee5fe194c5c37b5da1279871cc892b5ed28a4af" +resolved_reference = "4cd7f34986a17da959295dc698650d5e5b58e3f0" [[package]] name = "pillow" @@ -2703,51 +2670,52 @@ files = [ [[package]] name = "psd-tools" -version = "1.12.1" +version = "1.13.1" description = "Python package for working with Adobe Photoshop PSD files" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "psd_tools-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9e99464f6368997b9dce5b504fab9734dd13e45080a78e4666f23c0395705942"}, - {file = "psd_tools-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:57cf15e1b2a40d532e6e22c5c739483e4d65b8783f68abd2c28e3536c2cb63e6"}, - {file = "psd_tools-1.12.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e73bbebe496749a5b4a9f2e001a107132cc56368050c18624c6a1721cc354dc"}, - {file = "psd_tools-1.12.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e9be3c9098cc565fe4d8e2d48894172620fba9915c12fc924b8e87dbaeb292e6"}, - {file = "psd_tools-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb7f01057e0a01ea460f60b289ac4169e3670f5567df7a8709c10b5948e60041"}, - {file = "psd_tools-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d739ad4353105c49116ac58ab1de1063af4bb996981579ce90aae647683edca5"}, - {file = "psd_tools-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:c11291c4790fa93933128e4e1c444c8429a2d4dc504fb9dc004a337356a07ef7"}, - {file = "psd_tools-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c87fbf36682d36f2f30659542f90cbfbd3f58306f3767209c80fbd0314c4176"}, - {file = "psd_tools-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8791f0c04ceb7d709140590821d502eed186b53f215bfd6b594fb555f14db09c"}, - {file = "psd_tools-1.12.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8432db9e9cdc64e74de0cc587c28a93061ef72168de0045ab5f3fb6d73bc4099"}, - {file = "psd_tools-1.12.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e85101b347094edf7e9cbd109fd39309cbdc472cd636850088e42af84b1251fc"}, - {file = "psd_tools-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:44d0d1716cb1898f1b546c19068b347d7b5dfa7849953bc65cfe54489bbd3b56"}, - {file = "psd_tools-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46e556a01ee90dafeb58e5ea2ee7d86ffe47c794f341f41705e4a48000c17b4d"}, - {file = "psd_tools-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf93c133adb01755a58d261fb76a34c2ebcbc772ab696b711538f75f7fff772c"}, - {file = "psd_tools-1.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:705acfb3eac10aa0f05900860bb79ff19b6229a854ae953fc9c644c3aa506789"}, - {file = "psd_tools-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:93aa190b84205a991ad43432683be4627adb39a4b81f548b0052c3f49b65b4f3"}, - {file = "psd_tools-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d9034ede9d50ddb10aeda7d11188424dae087945f80d45badf2bc73a4879ba1"}, - {file = "psd_tools-1.12.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9f0c0929cb4b69120c545c27ddfe4b7aee22d8a754319f987ebb21da0d90c0"}, - {file = "psd_tools-1.12.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38628432f87b00d914acf914a52385f434fed9ec6b9f19a825b6798e4f5deab8"}, - {file = "psd_tools-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a162fbe2a24eb71e27d402c6356c6f1924c44617cfc676e0d96567f832148cdf"}, - {file = "psd_tools-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:041e9a452e8c283b7fb8d04ff5e481556b9d904df9c87e407b9554097e2ff5a4"}, - {file = "psd_tools-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:d3c8c8d9359deb6bbec9feb8ef074a716e14491cef136d9a040d36ac8f351f8f"}, - {file = "psd_tools-1.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:ccca70398ada1eb9f79917ac9ea8c521a75ce7779364ba5c6621a6d9357bc4f3"}, - {file = "psd_tools-1.12.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:a5cbd9b140490836137962528671c4d450e4a3b7e19171c566d5ea1c45a5e0c8"}, - {file = "psd_tools-1.12.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:2787b8caa2f8c7089de5705833a0a4d5cb787a76de435e3845fb62b9c6211a9e"}, - {file = "psd_tools-1.12.1-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad52c3ae9fe3c57bd5bb95cd3d8524256d9fb7542cb6f5b32af5bc9ff7e033e2"}, - {file = "psd_tools-1.12.1-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a09ed4a97c5a5634c672fb5a8c1732378bda9a548bebe92087508d9dfef65b7"}, - {file = "psd_tools-1.12.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a7a57edb16dbdece015cbdcdfaaa232382443a056deb4ff507e1856d4e04a064"}, - {file = "psd_tools-1.12.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:568ed2bd02153015bb87243faa77bb337be2859a13b853e6e45281db072defed"}, - {file = "psd_tools-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:a835c014116b8d5f739a51f7e3c51892451c66635d8b39bf0c870b2cbc917593"}, - {file = "psd_tools-1.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:8547002c4336c3eddda47a717065015170706cd818369f13ec0ccaf72b58fc1d"}, - {file = "psd_tools-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:3b3658928238cbdf0ff3ff9d5bd6f730ff2527b8ca12fe3123263ee8b46fd9f3"}, - {file = "psd_tools-1.12.1-cp314-cp314-win_arm64.whl", hash = "sha256:f642eea09aff7d56993c8a49c444b807d2cac7ac4eb1be736066748d77ec4865"}, + {file = "psd_tools-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35f743f0b794756febf5bf5f3cf49b4853e9246bd82bf9736d0430563905bde0"}, + {file = "psd_tools-1.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d47b3aa2595ff099a9a4ac2cfae6534e377f0bf4b7be2fb2f53e7b477f079290"}, + {file = "psd_tools-1.13.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:258081498acf9e251f59c9f420c5fc566d7b712323fed23a33c4fa46b90b3851"}, + {file = "psd_tools-1.13.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:16101523054fde08a97131e2a03e8d3e27c1a64a4d30b939c63af141a161396f"}, + {file = "psd_tools-1.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4e783a8c5a5f705bb91b4059659732427c1fdd608e1081776dda64adbf925efe"}, + {file = "psd_tools-1.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9cc381f8fa01c9726eeb74f1a2f808ad0c2f2a645b9a5038661dcfe1c976cc10"}, + {file = "psd_tools-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:f11631ddaaec43a983fa7670ba9096b2f21d96562c88694941de8d00a41ee2c1"}, + {file = "psd_tools-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c9df9f4bfe42e3a5cebcb0c7bdd4a67e5d002475e3ec57ff3e91562747e156f"}, + {file = "psd_tools-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37e6f876fddb6bc0f8ff1ca683bfc2d63cf0ce919dd2dd6d29a7e8219cd55735"}, + {file = "psd_tools-1.13.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ab92e844107b2c2d2e0a3df3e1417e0f10dab8dba412d707b1c0740e2fb9fc"}, + {file = "psd_tools-1.13.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f76643bff1e8b0b79b8ce0c6db7f77635198a69d4665ee70e176df5e56930aa"}, + {file = "psd_tools-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97fea076e6cdddbaf8c0c9764b8a8f9d7a231f644aec1fc8670069f52dc827e7"}, + {file = "psd_tools-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a435804aa1d5c08d58a4f9d03835e3cdda0adf21e2a69bd68cbac2685bc107c0"}, + {file = "psd_tools-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:1d3d69d92b1b7c8e66f3f1c2e9b75482a3156dd2607bf533707a55f7fac1d3ce"}, + {file = "psd_tools-1.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:dde29ca13834a1d3f229e8ebec006e667e86a561654937c19d55edb84fe2095b"}, + {file = "psd_tools-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f8cb18642bca7c625b0b5f3d3176c260279ee0769b514dc10c92b08134043561"}, + {file = "psd_tools-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a08e550e4d1810315ef909d2bb2fb3d30f12ced30d834d340de2e227b54c3334"}, + {file = "psd_tools-1.13.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f601ec0ee41f9a369576d8fd3dce1c151f368404a883731a5ad18b54b3a105b3"}, + {file = "psd_tools-1.13.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7faf5d57c6c88c77faf8d62712e5eff4c4265049cacf0a354a3d39bcb22b813e"}, + {file = "psd_tools-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fe00ab68cd212da4e7194d505f77ea02cf1c87a7799328c61a255994003c6c8"}, + {file = "psd_tools-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f5659f91b18fb5b53ca6b0a86e5c8049cc27c15f20fb01c203b80f5ebd4a9749"}, + {file = "psd_tools-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b55146f3145cc2d1217cdfd15eb297206432c8562c8998b613c66534cc0852"}, + {file = "psd_tools-1.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:7846b7adc7bce5a640e740671d111495e9e3b8a040ab565bf8f2917d5b7b0d1b"}, + {file = "psd_tools-1.13.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:3b203ecbb2bcb13b3dba60f054ec97a2a8c0a0c57a0b585020e8e3d0d988be22"}, + {file = "psd_tools-1.13.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:6b7ea99f024b2c49de83da356a90b8483e20cd04196f4cfc95aab556b8253729"}, + {file = "psd_tools-1.13.1-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68466fbe62b5fa60afb378264ad09067e7a3ab3803aefebd9dc80dc45c3c73f"}, + {file = "psd_tools-1.13.1-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457da0c2fe999f6e3712c0ff370c92e45289c2bf1d3ff833b7fb569ebbedebdf"}, + {file = "psd_tools-1.13.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9826975864428fd60e556bdff9fb2cbf1b562ed85ba89a702f4ee74cea932629"}, + {file = "psd_tools-1.13.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e45d879a6c7ad48bc9982b1f79b94617b66616203451cb2b773dd09372b5b403"}, + {file = "psd_tools-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:b77ab02b84fc3b065915d283a6057e94b385276b5ed035add0513194faa230a1"}, + {file = "psd_tools-1.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:3245c695df71bd6a9661771f39da8bb0474439fea8d1e620a3b976208de43605"}, + {file = "psd_tools-1.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:9bfdd95230d263c90b2a76ee1f9e396732549a1c5ffb9b644cdd8f40dc63c34f"}, + {file = "psd_tools-1.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:059661d2f2caeeffe8f5a2b1140e9e0941f711d05e9bf11276334393e4a58238"}, ] [package.dependencies] attrs = ">=23.0.0" numpy = "*" Pillow = ">=10.3.0" +typing-extensions = ">=4.0" [package.extras] composite = ["aggdraw (>=1.3.16) ; sys_platform != \"win32\"", "aggdraw (>=1.3.16,<1.4.1) ; sys_platform == \"win32\" and python_version < \"3.11\"", "aggdraw (>=1.4.1) ; sys_platform == \"win32\" and python_version >= \"3.11\"", "scikit-image", "scipy"] @@ -3120,14 +3088,14 @@ typing-extensions = ">=4.14.1" [[package]] name = "pydantic-settings" -version = "2.13.0" +version = "2.13.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pydantic_settings-2.13.0-py3-none-any.whl", hash = "sha256:d67b576fff39cd086b595441bf9c75d4193ca9c0ed643b90360694d0f1240246"}, - {file = "pydantic_settings-2.13.0.tar.gz", hash = "sha256:95d875514610e8595672800a5c40b073e99e4aae467fa7c8f9c263061ea2e1fe"}, + {file = "pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237"}, + {file = "pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025"}, ] [package.dependencies] @@ -3194,14 +3162,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2026.0" +version = "2026.1" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2026.0-py3-none-any.whl", hash = "sha256:0590db8edeba3e6c30c8474937021f5cd39c0602b4d10f74a064c73911efaca5"}, - {file = "pyinstaller_hooks_contrib-2026.0.tar.gz", hash = "sha256:0120893de491a000845470ca9c0b39284731ac6bace26f6849dea9627aaed48e"}, + {file = "pyinstaller_hooks_contrib-2026.1-py3-none-any.whl", hash = "sha256:66ad4888ba67de6f3cfd7ef554f9dd1a4389e2eb19f84d7129a5a6818e3f2180"}, + {file = "pyinstaller_hooks_contrib-2026.1.tar.gz", hash = "sha256:a5f0891a1e81e92406ab917d9e76adfd7a2b68415ee2e35c950a7b3910bc361b"}, ] [package.dependencies] @@ -3413,16 +3381,36 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-discovery" +version = "1.1.0" +description = "Python interpreter discovery" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b"}, + {file = "python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268"}, +] + +[package.dependencies] +filelock = ">=3.15.4" +platformdirs = ">=4.3.6,<5" + +[package.extras] +docs = ["furo (>=2025.12.19)", "sphinx (>=9.1)", "sphinx-autodoc-typehints (>=3.6.3)", "sphinxcontrib-mermaid (>=2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] + [[package]] name = "python-dotenv" -version = "1.2.1" +version = "1.2.2" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, - {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, + {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, + {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, ] [package.extras] @@ -3608,14 +3596,14 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "14.3.2" +version = "14.3.3" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["main"] files = [ - {file = "rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69"}, - {file = "rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8"}, + {file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"}, + {file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"}, ] [package.dependencies] @@ -3889,24 +3877,21 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "20.37.0" +version = "21.1.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.37.0-py3-none-any.whl", hash = "sha256:5d3951c32d57232ae3569d4de4cc256c439e045135ebf43518131175d9be435d"}, - {file = "virtualenv-20.37.0.tar.gz", hash = "sha256:6f7e2064ed470aa7418874e70b6369d53b66bcd9e9fd5389763e96b6c94ccb7c"}, + {file = "virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07"}, + {file = "virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = {version = ">=3.24.2,<4", markers = "python_version >= \"3.10\""} platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "pre-commit-uv (>=4.1.4)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinx-autodoc-typehints (>=3.6.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2025.12.21.14)", "sphinxcontrib-mermaid (>=2)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "pytest-xdist (>=3.5)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +python-discovery = ">=1" [[package]] name = "watchdog" @@ -4101,142 +4086,140 @@ dev = ["pytest", "setuptools"] [[package]] name = "yarl" -version = "1.22.0" +version = "1.23.0" description = "Yet another URL library" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, - {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, - {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, - {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, - {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, - {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, - {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, - {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, - {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, - {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, - {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, - {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, - {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, - {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, - {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, - {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, - {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, - {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, - {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, - {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, - {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, - {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, - {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, - {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, - {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, - {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, - {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, - {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, - {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, - {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, - {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, - {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, - {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, - {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, - {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, - {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, - {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, - {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, - {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, - {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, - {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, - {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, - {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, - {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, - {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, - {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, - {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, - {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, - {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, - {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, - {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, - {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, + {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, + {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, + {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, + {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, + {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, + {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, + {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, + {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, + {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, + {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, + {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, + {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, + {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, + {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, + {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, + {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, + {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, + {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, + {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, + {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, + {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, + {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, + {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, + {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, + {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, + {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, + {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, + {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, + {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, + {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, + {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, + {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, + {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, + {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, + {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, + {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, + {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, + {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, ] [package.dependencies] From 78ef13aefb319a5a91cc2d4379b5d4ed663989fe Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 2 Mar 2026 16:06:01 +0200 Subject: [PATCH 076/190] fix(AppPlugin): Ensure that a plugin's _module attribute can be accessed before calling load_module --- src/_loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_loader.py b/src/_loader.py index b2dd6d2a..ce2f8e05 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -567,6 +567,8 @@ def __init__( path: Path, template_file_versions: dict[str, str], ): + self._module: ModuleType | None = None + # Save a reference to the global application constants self.con: AppConstants = con self.env: AppEnvironment = env From 9157f60a4fa1d4e25b86889f16eecf27232ed57f Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 08:53:06 +0200 Subject: [PATCH 077/190] feat(ENV): Allow specifying the logging level via LOG_LEVEL env variable and remove unnecessary existing DEV_MODE and TEST_MODE variables --- plugins/Investigamer/py/templates.py | 9 +--- src/_state.py | 4 +- src/console.py | 15 ++++-- src/data/env.default.yml | 10 ++-- src/helpers/layers.py | 68 ++++++++++++++++------------ 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/plugins/Investigamer/py/templates.py b/plugins/Investigamer/py/templates.py index 87d387fd..cf166d2e 100644 --- a/plugins/Investigamer/py/templates.py +++ b/plugins/Investigamer/py/templates.py @@ -8,7 +8,6 @@ from photoshop.api._artlayer import ArtLayer import src.helpers as psd -from src import ENV from src.enums.layers import LAYERS from src.templates import ExtendedMod, NormalTemplate, TransformMod @@ -33,14 +32,8 @@ class SketchTemplate(NormalTemplate): @cached_property def art_action(self) -> Callable[[], None] | None: - # Skip action if in test mode - if ENV.TEST_MODE: - return action = self.config.get_setting( - section="ACTION", - key="Sketch.Action", - default="Advanced Sketch", - is_bool=False, + section="ACTION", key="Sketch.Action", default="Advanced Sketch" ) if action == "Advanced Sketch": return lambda: pencilsketch.run( diff --git a/src/_state.py b/src/_state.py index 93456d71..6c3f6bf4 100644 --- a/src/_state.py +++ b/src/_state.py @@ -5,6 +5,7 @@ import os import sys from contextlib import suppress +from logging import INFO from pathlib import Path from threading import Lock from typing import TypedDict @@ -170,8 +171,7 @@ class AppEnvironment(BaseSettings): PS_ERROR_DIALOG: bool = False PS_VERSION: str | None = None HEADLESS: bool = False - DEV_MODE: bool = bool(not hasattr(sys, "_MEIPASS")) - TEST_MODE: bool = False + LOG_LEVEL: int = INFO APP_UPDATES_REPO: str = "" SYMBOL_UPDATES_REPO: str = "" FORCE_RELOAD: bool = False diff --git a/src/console.py b/src/console.py index 158c2293..3aed41c3 100644 --- a/src/console.py +++ b/src/console.py @@ -18,24 +18,31 @@ getLogger, ) +from src import ENV from src._state import PATH DEFAULT_LOG_FORMAT = "[%(asctime)s.%(msecs)03d][%(levelname)s] %(message)s" +DETAILED_LOG_FORMAT = ( + "[%(asctime)s.%(msecs)03d][%(levelname)s][%(name)s][%(funcName)s] %(message)s" +) DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" DEFAULT_LOG_FORMATTER = Formatter( fmt=DEFAULT_LOG_FORMAT, datefmt=DEFAULT_LOG_DATE_FORMAT, ) +DETAILED_LOG_FORMATTER = Formatter( + fmt=DETAILED_LOG_FORMAT, datefmt=DEFAULT_LOG_DATE_FORMAT +) _logger = getLogger() -_logger.setLevel(INFO) +_logger.setLevel(ENV.LOG_LEVEL) _default_console_handler = StreamHandler() -_default_console_handler.setLevel(DEBUG) -_default_console_handler.setFormatter(DEFAULT_LOG_FORMATTER) +_default_console_handler.setLevel(ENV.LOG_LEVEL) +_default_console_handler.setFormatter(DETAILED_LOG_FORMATTER) _logger.addHandler(_default_console_handler) _error_log_file_handler = FileHandler(PATH.LOGS_ERROR, "a", encoding="utf-8") _error_log_file_handler.setLevel(ERROR) -_error_log_file_handler.setFormatter(DEFAULT_LOG_FORMATTER) +_error_log_file_handler.setFormatter(DETAILED_LOG_FORMATTER) _logger.addHandler(_error_log_file_handler) # region Enums diff --git a/src/data/env.default.yml b/src/data/env.default.yml index 75ffd575..b5bdbb94 100644 --- a/src/data/env.default.yml +++ b/src/data/env.default.yml @@ -32,17 +32,15 @@ # Force the app to use headless console (ADVANCED) # HEADLESS: False -# Force the app into development mode -# DEV_MODE: False - -# Force the app into testing mode -# TEST_MODE: False +# Set Python logging level. DEBUG = 10, INFO = 20 and so on. +# LOG_LEVEL: 20 ### # * Update Checks ### -# Specify using format "/" +# GitHub repositories to use for updates. +# Specify using format "/". APP_UPDATES_REPO: Investigamer/Proxyshop # SYMBOL_UPDATES_REPO: "" diff --git a/src/helpers/layers.py b/src/helpers/layers.py index 881fb5d7..96740700 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -3,19 +3,15 @@ """ from collections.abc import Iterable, Sequence +from logging import getLogger -from photoshop.api import ( - ActionDescriptor, - ActionReference, - BlendMode, - DialogModes, - ElementPlacement, -) +from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import BlendMode, DialogModes, ElementPlacement -from src import APP, ENV +from src import APP from src.utils.adobe import ( PS_EXCEPTIONS, LayerContainer, @@ -23,6 +19,8 @@ ReferenceLayer, ) +_logger = getLogger(__name__) + """ * Searching Layers """ @@ -68,14 +66,18 @@ def getLayer( return layer_set.artLayers[name] # ArtLayer can't be located raise OSError("ArtLayer invalid") - except PS_EXCEPTIONS: + except PS_EXCEPTIONS as exc: # Layer couldn't be found - if ENV.DEV_MODE: - print(f'Layer "{name}" could not be found!') - if isinstance(group, LayerSet): - print(f"LayerSet reference used: {group.name}") - elif group and isinstance(group, str): - print(f"LayerSet reference used: {group}") + _logger.debug( + f'Layer "{name}" could not be found.{ + f" Used LayerSet reference: {group.name}" + if isinstance(group, LayerSet) + else f" Used LayerSet reference: {group}" + if group and isinstance(group, str) + else "" + }', + exc_info=exc + ) def getLayerSet( @@ -118,14 +120,18 @@ def getLayerSet( return group.layerSets[name] # LayerSet can't be located raise OSError("LayerSet invalid") - except PS_EXCEPTIONS: + except PS_EXCEPTIONS as exc: # LayerSet couldn't be found - if ENV.DEV_MODE: - print(f'LayerSet "{name}" could not be found!') - if isinstance(group, LayerSet): - print(f"LayerSet reference used: {group.name}") - elif group and isinstance(group, str): - print(f"LayerSet reference used: {group}") + _logger.debug( + f'LayerSet "{name}" could not be found.{ + f" Used LayerSet reference: {group.name}" + if isinstance(group, LayerSet) + else f" Used LayerSet reference: {group}" + if group and isinstance(group, str) + else "" + }', + exc_info=exc + ) def get_reference_layer( @@ -152,13 +158,17 @@ def get_reference_layer( # Select the reference layer return ReferenceLayer(parent=group.artLayers.app[name], app=APP.instance) - except PS_EXCEPTIONS: - if ENV.DEV_MODE: - print(f'ReferenceLayer "{name}" could not be found!') - if isinstance(group, LayerSet): - print(f"LayerSet reference used: {group.name}") - elif group and isinstance(group, str): - print(f"LayerSet reference used: {group}") + except PS_EXCEPTIONS as exc: + _logger.debug( + f'ReferenceLayer "{name}" could not be found.{ + f" Used LayerSet reference: {group.name}" + if isinstance(group, LayerSet) + else f" Used LayerSet reference: {group}" + if group and isinstance(group, str) + else "" + }', + exc_info=exc + ) """ From 6f0465bec122260452aeb76e8be094e5c40be25b Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 08:54:49 +0200 Subject: [PATCH 078/190] feat(selection.py): Allow extending selection with selection helpers --- src/helpers/selection.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/helpers/selection.py b/src/helpers/selection.py index 46e0435f..80edbdc6 100644 --- a/src/helpers/selection.py +++ b/src/helpers/selection.py @@ -2,23 +2,18 @@ * Helpers: Selection """ -# Standard Library Imports from contextlib import suppress -# Third Party Imports +from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection -from photoshop.api import ActionDescriptor, ActionReference, DialogModes, LayerKind +from photoshop.api.enumerations import DialogModes, LayerKind -# Local Imports from src import APP from src.utils.adobe import PS_EXCEPTIONS -# Photoshop infrastructure -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Making Selections """ @@ -67,7 +62,9 @@ def select_overlapping(layer: ArtLayer) -> None: ref2.putProperty(idChannel, APP.instance.sID("selection")) desc1.putReference(APP.instance.sID("with"), ref2) APP.instance.executeAction( - APP.instance.sID("interfaceIconFrameDimmed"), desc1, NO_DIALOG + APP.instance.sID("interfaceIconFrameDimmed"), + desc1, + DialogModes.DisplayNoDialogs, ) @@ -94,7 +91,9 @@ def select_canvas(docref: Document | None = None, bleed: int = 0): """ -def select_layer_pixels(layer: ArtLayer | None = None) -> None: +def select_layer_pixels( + layer: ArtLayer | None = None, add_to_selection: bool = False +) -> None: """Select pixels of the active layer, or a target layer. Args: @@ -115,10 +114,16 @@ def select_layer_pixels(layer: ArtLayer | None = None) -> None: if layer: ref2.putIdentifier(APP.instance.sID("layer"), layer.id) des1.putReference(APP.instance.sID("to"), ref2) - APP.instance.executeAction(APP.instance.sID("set"), des1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("add" if add_to_selection else "set"), + des1, + DialogModes.DisplayNoDialogs, + ) -def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: +def select_vector_layer_pixels( + layer: ArtLayer | None = None, add_to_selection: bool = False +) -> None: """Select pixels of the active vector layer, or a target layer. Args: @@ -139,7 +144,11 @@ def select_vector_layer_pixels(layer: ArtLayer | None = None) -> None: desc1.putReference(APP.instance.sID("to"), ref2) desc1.putInteger(APP.instance.sID("version"), 1) desc1.putBoolean(APP.instance.sID("vectorMaskParams"), True) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("add" if add_to_selection else "set"), + desc1, + DialogModes.DisplayNoDialogs, + ) """ From d126e6ee9152bfd46d77c4c03a8694920ebbd380 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 08:58:35 +0200 Subject: [PATCH 079/190] feat: Make message severity more apparent at a glance in the GUI's console This is achieved by coloring the left side of each message according to severity. --- src/gui/qml/models/console_model.py | 9 ++++++- src/gui/qml/views/Console.qml | 37 ++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/gui/qml/models/console_model.py b/src/gui/qml/models/console_model.py index 39abe7ef..53499145 100644 --- a/src/gui/qml/models/console_model.py +++ b/src/gui/qml/models/console_model.py @@ -39,6 +39,7 @@ def format(self, record: LogRecord) -> str: class LogEntry(BaseModel): message: str severity: int + color: str class ConsoleModel(PydanticQListModel[LogEntry]): @@ -62,7 +63,13 @@ def __init__( def _add_to_log(self, message: str, severity: MessageSeverity) -> None: row_count = self.rowCount() self.beginInsertRows(QModelIndex(), row_count, row_count) - self.items.append(LogEntry(message=message, severity=severity)) + self.items.append( + LogEntry( + message=message, + severity=severity, + color=LOG_MESSAGE_COLORS_MAP[severity], + ) + ) self.endInsertRows() @Slot() diff --git a/src/gui/qml/views/Console.qml b/src/gui/qml/views/Console.qml index d7fce4f0..284596cb 100644 --- a/src/gui/qml/views/Console.qml +++ b/src/gui/qml/views/Console.qml @@ -58,7 +58,7 @@ ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true - leftMargin: 10 + leftMargin: 0 rightMargin: 10 orientation: ListView.Vertical boundsBehavior: Flickable.StopAtBounds @@ -66,23 +66,38 @@ ColumnLayout { clip: true highlightFollowsCurrentItem: false model: root.logModel - delegate: SelectableText { + delegate: RowLayout { id: logDelegate required property int index required property string message required property int severity + required property color color width: logList.width - color: root.systemPalette.text - text: message - font.family: root.monospaceFontFamily - onLinkActivated: Qt.openUrlExternally(hoveredLink) - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : undefined + spacing: 0 + + Rectangle { + Layout.fillHeight: true + + implicitWidth: 5 + color: logDelegate.color + } + SelectableText { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 5 + + color: root.systemPalette.text + text: logDelegate.message + font.family: root.monospaceFontFamily + onLinkActivated: Qt.openUrlExternally(hoveredLink) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : undefined + } } } From cac5a4f8f0d372b71ab613ebcdd32fabb6748126 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 09:03:42 +0200 Subject: [PATCH 080/190] feat(fonts.py): Add `is_font_available_in_ps` for easily checking availability of some font in Photoshop --- src/utils/fonts.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/utils/fonts.py b/src/utils/fonts.py index ff98b14c..0bc95b43 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -2,13 +2,14 @@ * Font Utils """ -import ctypes import os import os.path as osp import re from contextlib import suppress -from ctypes import wintypes +from ctypes import COMError, windll, wintypes +from functools import cached_property from logging import getLogger +from pathlib import Path from typing import TypedDict from fontTools.ttLib import TTFont, TTLibError @@ -24,6 +25,17 @@ # Precompile font version pattern REG_FONT_VER: re.Pattern[str] = re.compile(r"\b(\d+\.\d+)\b") +class FontCache: + @cached_property + def user_fonts_dir(self) -> Path: + return Path.home() / "AppData/Local/Microsoft/Windows/Fonts" + + @cached_property + def user_font_files(self) -> list[Path]: + return [path for path in self.user_fonts_dir.iterdir() if path.is_file()] + +FONT_CACHE = FontCache() + """ * Types """ @@ -50,14 +62,14 @@ def register_font(ps_app: PhotoshopHandler, font_path: str) -> bool: Returns: True if succeeded, False if failed. """ - result = ctypes.windll.gdi32.AddFontResourceW(osp.abspath(font_path)) + result = windll.gdi32.AddFontResourceW(osp.abspath(font_path)) if result != 0: # Font Resource added successfully try: # Notify all programs _logger.debug(f"Font '{osp.basename(font_path)}' added to font cache.") hwnd_broadcast = wintypes.HWND(-1) - ctypes.windll.user32.SendMessageW( + windll.user32.SendMessageW( hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) ) ps_app.refreshFonts() @@ -77,14 +89,14 @@ def unregister_font(ps_app: PhotoshopHandler, font_path: str) -> bool: Returns: True if succeeded, False if failed. """ - result = ctypes.windll.gdi32.RemoveFontResourceW(osp.abspath(font_path)) + result = windll.gdi32.RemoveFontResourceW(osp.abspath(font_path)) if result != 0: # Font Resource removed successfully try: # Notify all programs _logger.debug(f"Font {osp.basename(font_path)} removed from font cache!") hwnd_broadcast = wintypes.HWND(-1) - ctypes.windll.user32.SendMessageW( + windll.user32.SendMessageW( hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) ) ps_app.refreshFonts() @@ -98,6 +110,12 @@ def unregister_font(ps_app: PhotoshopHandler, font_path: str) -> bool: * Photoshop Font Utils """ +def is_font_available_in_ps(ps_app: PhotoshopHandler, font_post_script_name: str) -> bool: + try: + return bool(ps_app.fonts[font_post_script_name]) + except (COMError, KeyError): + return False + def get_ps_font_dict(ps_app: PhotoshopHandler) -> dict[str, str]: """Gets a dictionary of every font accessible in Photoshop. @@ -215,10 +233,9 @@ def get_installed_fonts_dict() -> dict[str, FontDetails]: Dictionary with postScriptName as key, and tuple of display name and version as value. """ with suppress(Exception): - installed_fonts_dir = os.path.expandvars(r'%userprofile%\AppData\Local\Microsoft\Windows\Fonts') system_fonts_dir = os.path.join(os.path.join(os.environ['WINDIR']), 'Fonts') return { - **get_fonts_from_folder(installed_fonts_dir), + **get_fonts_from_folder(FONT_CACHE.user_fonts_dir), **get_fonts_from_folder(system_fonts_dir) } return {} From 1794914de573f976dcda46e388f638eeb95650c5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 09:06:41 +0200 Subject: [PATCH 081/190] feat(text.py): Add `override_text_style_ranges` for restyling specific character ranges in a Photoshop text item --- src/helpers/text.py | 110 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 14 deletions(-) diff --git a/src/helpers/text.py b/src/helpers/text.py index ae1b95fe..e17c3018 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -2,7 +2,7 @@ * Helpers: Text Items """ -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from logging import getLogger from typing import Literal, overload @@ -10,12 +10,11 @@ ActionDescriptor, ActionList, ActionReference, - DialogModes, - LayerKind, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DialogModes, LayerKind from photoshop.api.text_item import TextItem from src import APP @@ -122,21 +121,21 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: replaced = False # Find the range where target text exists - style_range = text_key.getList(idTextStyleRange) - for i in range(style_range.count): - style = style_range.getObjectValue(i) - idxFrom = style.getInteger(idFrom) - idxTo = style.getInteger(idTo) + style_ranges = text_key.getList(idTextStyleRange) + for i in range(style_ranges.count): + style_range = style_ranges.getObjectValue(i) + idxFrom = style_range.getInteger(idFrom) + idxTo = style_range.getInteger(idTo) if not replaced and find in current_text[idxFrom:idxTo]: # Found the text to replace replaced = True - style.putInteger(idTo, idxTo + offset) - style_range.putObject(idTextStyleRange, style) + style_range.putInteger(idTo, idxTo + offset) + style_ranges.putObject(idTextStyleRange, style_range) elif replaced: # Replacement already made, offset the remaining ranges - style.putInteger(idFrom, idxFrom + offset) - style.putInteger(idTo, idxTo + offset) - style_range.putObject(idTextStyleRange, style) + style_range.putInteger(idFrom, idxFrom + offset) + style_range.putInteger(idTo, idxTo + offset) + style_ranges.putObject(idTextStyleRange, style_range) # Skip applying changes if no replacement could be made if not replaced: @@ -145,7 +144,7 @@ def replace_text(layer: ArtLayer, find: str, replace: str) -> None: # Apply changes text_key.putString(idTextKey, current_text.replace(find, replace)) - text_key.putList(idTextStyleRange, style_range) + text_key.putList(idTextStyleRange, style_ranges) apply_text_key(layer, text_key) @@ -306,6 +305,89 @@ def remove_leading_text(layer: ArtLayer, idx: int) -> None: apply_text_key(layer, key) +def override_text_style_ranges( + layer: ArtLayer, + ranges: Iterable[tuple[int, int]], + font: str | None = None, + size: float | None = None, +) -> None: + """Override style properties in specified character ranges of a text layer. + + Existing style ranges are split and adjusted as necessary. New ranges inherit + the existing style from the portions they replace, then apply the overrides. + + Args: + layer: Text layer to modify. + ranges: List of (start, end) tuples, where end is exclusive (0-based indices). + font: Font post script name. + size: Text size in points. + """ + # Get IDs + text_key_id = APP.instance.sID("textKey") + text_style_range_id = APP.instance.sID("textStyleRange") + from_id = APP.instance.sID("from") + to_id = APP.instance.sID("to") + text_style_id = APP.instance.sID("textStyle") + points_unit_id = APP.instance.sID("pointsUnit") + font_post_script_id = APP.instance.sID("fontPostScriptName") + size_id = APP.instance.sID("size") + + # Get text key and current text + text_key = get_text_key(layer) + current_text = text_key.getString(text_key_id) + text_len = len(current_text) + + # Get existing style ranges + style_ranges = text_key.getList(text_style_range_id) + + # Collect all split positions + positions = set([0, text_len]) + for start, end in ranges: + positions.add(max(0, start)) + positions.add(min(text_len, end)) + positions = sorted(positions) + + # Create new style ranges + new_style_ranges = ActionList() + for i in range(len(positions) - 1): + start = positions[i] + end = positions[i + 1] + + # Find the original range containing this start + orig_range = None + for j in range(style_ranges.count): + r = style_ranges.getObjectValue(j) + if r.getInteger(from_id) <= start < r.getInteger(to_id): + orig_range = r + break + if not orig_range: + continue + + # Create new range + new_range = ActionDescriptor() + new_range.putInteger(from_id, start) + new_range.putInteger(to_id, end) + + # Copy the text style from original + text_style = orig_range.getObjectValue(text_style_id) + + # Check if this range is overridden + overridden = any(ov_start <= start < ov_end for ov_start, ov_end in ranges) + if overridden: + # Apply overrides + if font is not None: + text_style.putString(font_post_script_id, font) + if size is not None: + text_style.putUnitDouble(size_id, points_unit_id, size) + + new_range.putObject(text_style_id, text_style_id, text_style) + new_style_ranges.putObject(text_style_range_id, new_range) + + # Update the text key + text_key.putList(text_style_range_id, new_style_ranges) + apply_text_key(layer, text_key) + + """ * Text Item Size """ From a1094749fde518f4f04f070d367dff8447852c27 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 09:12:04 +0200 Subject: [PATCH 082/190] fix: Try to thread safely handle Future results --- src/gui/qml/models/file_dialog_model.py | 9 +++------ .../qml/models/message_dialog_content_model.py | 10 ++++------ src/render/setup.py | 6 +++--- src/utils/asynchronic.py | 16 +++++++++++++++- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/gui/qml/models/file_dialog_model.py b/src/gui/qml/models/file_dialog_model.py index 94151609..36a3959f 100644 --- a/src/gui/qml/models/file_dialog_model.py +++ b/src/gui/qml/models/file_dialog_model.py @@ -7,6 +7,7 @@ from src._state import PATH from src.gui.qml.models.base_dialog_model import BaseDialogModel +from src.utils.asynchronic import try_threadsafe_set_result class FileMode(IntEnum): @@ -91,17 +92,13 @@ def dialog_id(self, value: str) -> None: @Slot("QVariantList") def on_accepted(self, files: list[QUrl]) -> None: if self._response_future: - self._response_future.get_loop().call_soon_threadsafe( - self._response_future.set_result, files - ) + try_threadsafe_set_result(self._response_future, files) @Slot() def on_rejected(self) -> None: if self._response_future: arg: list[QUrl] = [] - self._response_future.get_loop().call_soon_threadsafe( - self._response_future.set_result, arg - ) + try_threadsafe_set_result(self._response_future, arg) # endregion Events diff --git a/src/gui/qml/models/message_dialog_content_model.py b/src/gui/qml/models/message_dialog_content_model.py index be1c1fa5..8bf5969a 100644 --- a/src/gui/qml/models/message_dialog_content_model.py +++ b/src/gui/qml/models/message_dialog_content_model.py @@ -4,6 +4,8 @@ from PySide6.QtCore import Property, QObject, Signal, Slot +from src.utils.asynchronic import try_threadsafe_set_result + class MessageDialogContentModel(QObject): def __init__( @@ -122,11 +124,7 @@ async def open_message_dialog_async( text=text, informative_text=informative_text, detailed_text=detailed_text, - ok_callback=lambda: future.get_loop().call_soon_threadsafe( - future.set_result, True - ), - cancel_callback=lambda: future.get_loop().call_soon_threadsafe( - future.set_result, False - ), + ok_callback=lambda: try_threadsafe_set_result(future, True), + cancel_callback=lambda: try_threadsafe_set_result(future, False), ) return await future diff --git a/src/render/setup.py b/src/render/setup.py index 8e74a749..0b2d10b3 100644 --- a/src/render/setup.py +++ b/src/render/setup.py @@ -11,7 +11,7 @@ from src.enums.mtg import LayoutCategory from src.layouts import NormalLayout, assign_layout, join_dual_card_layouts from src.templates._core import BaseTemplate -from src.utils.asynchronic import async_to_sync +from src.utils.asynchronic import async_to_sync, try_threadsafe_set_result from src.utils.event import SubscribableEvent from src.utils.scryfall import ScryfallCard @@ -101,7 +101,7 @@ def cancel(self) -> None: if (task := self.render_task) and not task.done(): task.get_loop().call_soon_threadsafe(task.cancel) if (waiting := self._waiting) and not waiting.done(): - waiting.get_loop().call_soon_threadsafe(waiting.set_result, False) + try_threadsafe_set_result(waiting, False) async def pause( self, message: str | None = None, show_photoshop: bool = True @@ -126,7 +126,7 @@ def pause_sync( def resume(self) -> None: if (waiting := self._waiting) and not waiting.done(): - waiting.get_loop().call_soon_threadsafe(waiting.set_result, True) + try_threadsafe_set_result(waiting, True) def prepare_render_operations( diff --git a/src/utils/asynchronic.py b/src/utils/asynchronic.py index 9b972653..7ae9be94 100644 --- a/src/utils/asynchronic.py +++ b/src/utils/asynchronic.py @@ -1,8 +1,11 @@ -from asyncio import EventLoop, get_running_loop, run +from asyncio import EventLoop, Future, InvalidStateError, get_running_loop, run from collections.abc import Coroutine from concurrent.futures import ThreadPoolExecutor +from logging import getLogger from typing import Any +_logger = getLogger(__name__) + _thread_pool = ThreadPoolExecutor(max_workers=4) @@ -20,3 +23,14 @@ def target(): run(coro, loop_factory=EventLoop) get_running_loop().run_in_executor(_thread_pool, target) + + +def try_threadsafe_set_result[T](task: Future[T], result: T) -> None: + def set_result_if_not_done(): + if not task.done(): + try: + task.set_result(result) + except InvalidStateError as err: + _logger.warning("Failed to set future result", exc_info=err) + + task.get_loop().call_soon_threadsafe(set_result_if_not_done) From 9fa66514f83a27b6f8b68a6d855551d1212bcc1c Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 09:22:08 +0200 Subject: [PATCH 083/190] fix: Use list instead of Literal["QVariantList"] as Qt Property type in order to avoid Pyright type errors --- src/gui/qml/models/file_dialog_model.py | 2 +- src/gui/qml/models/image_transform_model.py | 2 +- src/gui/qml/models/test_renders_model.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/qml/models/file_dialog_model.py b/src/gui/qml/models/file_dialog_model.py index 36a3959f..aef51fef 100644 --- a/src/gui/qml/models/file_dialog_model.py +++ b/src/gui/qml/models/file_dialog_model.py @@ -63,7 +63,7 @@ def file_mode(self, value: FileMode) -> None: _name_filters_changed = Signal() - @Property("QVariantList", notify=_name_filters_changed) + @Property(list, notify=_name_filters_changed) def name_filters(self) -> list[str]: # pyright: ignore[reportRedeclaration] return self._name_filters diff --git a/src/gui/qml/models/image_transform_model.py b/src/gui/qml/models/image_transform_model.py index dee7dff4..17f086af 100644 --- a/src/gui/qml/models/image_transform_model.py +++ b/src/gui/qml/models/image_transform_model.py @@ -42,7 +42,7 @@ def _thread_pool(self) -> ThreadPoolExecutor: _image_file_formats_changed = Signal() - @Property("QVariantList", notify=_image_file_formats_changed) + @Property(list, notify=_image_file_formats_changed) def image_file_formats(self) -> list[str]: # pyright: ignore[reportRedeclaration] return self._image_file_formats diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index 4a0fedab..7c9d3a40 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -43,7 +43,7 @@ def template_render_test_cases(self) -> dict[LayoutType, dict[str, str]]: _layout_categories_changed = Signal() - @Property("QVariantList", notify=_layout_categories_changed) + @Property(list, notify=_layout_categories_changed) def layout_categories(self) -> list[LayoutCategory]: # pyright: ignore[reportRedeclaration] return self._layout_categories From 2751118d0207cf057a97103f54959430ea49a399 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 14 Mar 2026 14:04:23 +0200 Subject: [PATCH 084/190] feat: Allow supplying nickname via the art filename and generalize nickname handling Original idea and implementation by Malacath-92 . Their work heavily inspired this implementation. --- src/_config.py | 13 +++++ src/cards.py | 5 +- src/data/config/BorderlessVectorTemplate.toml | 6 --- src/data/config/base.toml | 33 +++++++++++++ src/enums/mtg.py | 3 +- src/enums/settings.py | 6 +++ src/layouts.py | 34 +++++++++++-- src/templates/_core.py | 29 ++++++----- src/templates/_cosmetic.py | 49 ++++++++++--------- src/templates/normal.py | 14 ++---- 10 files changed, 134 insertions(+), 58 deletions(-) diff --git a/src/_config.py b/src/_config.py index dd3c3a91..330e15aa 100644 --- a/src/_config.py +++ b/src/_config.py @@ -13,6 +13,7 @@ CollectorPromo, FillMode, HasDefault, + NicknameShorten, OutputFileType, ScryfallSorting, ScryfallUnique, @@ -145,6 +146,18 @@ def update_definitions(self): self.collector_promo = self.get_option( "BASE.TEXT", "Collector.Promo", CollectorPromo ) + self.nickname_allow = self.file.getboolean( + "BASE.TEXT", "Nickname", fallback=True + ) + self.nickname_prompt = self.file.getboolean( + "BASE.TEXT", "Nickname.Prompt", fallback=False + ) + self.nickname_in_oracle_text = self.file.getboolean( + "BASE.TEXT", "Nickname.In.Oracle", fallback=True + ) + self.nickname_shorten_in_oracle_text = self.get_option( + "BASE.TEXT", "Nickname.Shorten.In.Oracle", NicknameShorten + ) # BASE - SYMBOLS self.symbol_enabled = self.file.getboolean( diff --git a/src/cards.py b/src/cards.py index 8778ebd6..92fb50fb 100644 --- a/src/cards.py +++ b/src/cards.py @@ -44,6 +44,7 @@ class CardDetails(TypedDict): artist: str creator: str file: Path + kwargs: dict[str, str] class FrameDetails(TypedDict): @@ -130,7 +131,7 @@ def get_card_data( # Query the card in English, retry with extras if failed try: return action(*params, **kwargs) - except Exception as exc: + except Exception: if not number and not cfg.scry_extras: # Retry with extras included, case: Planar cards try: @@ -168,6 +169,7 @@ def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDe artist = CardTextPatterns.PATH_ARTIST.search(file_name) number = CardTextPatterns.PATH_NUM.search(file_name) code = CardTextPatterns.PATH_SET.search(file_name) + kwargs_match: list[tuple[str,str]] = CardTextPatterns.PATH_KWARGS.findall(file_name) # Return dictionary return { @@ -177,6 +179,7 @@ def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDe 'artist': artist.group(1) if artist else '', 'number': number.group(1) if number and code else '', 'creator': name_split[-1] if '$' in file_name else '', + 'kwargs': {key: value for key, value in kwargs_match} } diff --git a/src/data/config/BorderlessVectorTemplate.toml b/src/data/config/BorderlessVectorTemplate.toml index f5482c98..91ba14c1 100644 --- a/src/data/config/BorderlessVectorTemplate.toml +++ b/src/data/config/BorderlessVectorTemplate.toml @@ -23,12 +23,6 @@ default = 1 [TEXT] title = "Text Options" -[TEXT.Nickname] -title = "Use Nickname" -desc = """When enabled, card will be rendered with both a 'real' name and a 'nickname' you can enter manually.""" -type = "bool" -default = 0 - [TEXT."Drop.Shadow"] title = "Text Drop Shadow" desc = """When enabled, card text will have a drop shadow.""" diff --git a/src/data/config/base.toml b/src/data/config/base.toml index 0471dd39..04049b77 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -44,6 +44,39 @@ type = "options" default = "automatic" options = ["automatic", "always", "never"] +[TEXT.Nickname] +title = "Allow Nickname" +desc = """When enabled, if a nickname is available and the template has support for it, the card will be rendered with both a real name and a nickname. Some Scryfall cards come with a nickname by default, e.g. Gemrazer [IKO] {376}. Nickname can also be specified via the art filename with [nick=...]. E.g. +Arcane Signet [nick=Manastone] +Keep Out (Artist) [ECL] [nick=Access Denied] {19} +""" +type = "bool" +default = 1 + +[TEXT."Nickname.Prompt"] +title = "Prompt for Nickname" +desc = """When enabled if no nickname is available via other means the rendering is paused for +manual nickname input.""" +type = "bool" +default = 0 + +[TEXT."Nickname.In.Oracle"] +title = "Nickname in Oracle Text" +desc = """When enabled, cards that are rendered with a nickname will replace the card name +in their oracle text with their nickname.""" +type = "bool" +default = 1 + +[TEXT."Nickname.Shorten.In.Oracle"] +title = "Shorten Nickname in Oracle Text" +desc = """Whether to shorten nicknames that contain commas in the oracle text. Shortened name contains only the part before the first comma. +No: Name is not shortened. +All but first: The first mention of the name is kept as is but others are shortened. +All: All mentions of the name are shortened.""" +type = "options" +default = "All but first" +options = ["No", "All but first", "All"] + ### # * Symbol Settings ### diff --git a/src/enums/mtg.py b/src/enums/mtg.py index 2fd2bfa5..874a7d59 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -370,7 +370,8 @@ class CardTextPatterns: # Filename - Card Art PATH_ARTIST: re.Pattern[str] = re.compile(r"\(+(.*?)\)") PATH_SPLIT: re.Pattern[str] = re.compile(r"[\[({$]") - PATH_SET: re.Pattern[str] = re.compile(r"\[(.*)]") + PATH_KWARGS: re.Pattern[str] = re.compile(r"\[([^\]=]+)=([^\]]*)]") + PATH_SET: re.Pattern[str] = re.compile(r"\[([^\]=]+)]") PATH_NUM: re.Pattern[str] = re.compile(r"\{(.*)}") PATH_CONDITION: re.Pattern[str] = re.compile(r'<([^>]*)>') diff --git a/src/enums/settings.py b/src/enums/settings.py index 4364c65c..15c5a2b3 100644 --- a/src/enums/settings.py +++ b/src/enums/settings.py @@ -90,6 +90,12 @@ class FillMode(StrEnum): REMOVE_CONTENT_FILL = "Remove Content Fill" +class NicknameShorten(StrEnum): + NO = "No" + ALL_BUT_FIRST = "All but first" + ALL = "All" + + """ * Template: Borderless """ diff --git a/src/layouts.py b/src/layouts.py index 32c437b5..df75cfc4 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -32,7 +32,7 @@ layout_map_types, planeswalkers_tall, ) -from src.enums.settings import CollectorMode, WatermarkMode +from src.enums.settings import CollectorMode, NicknameShorten, WatermarkMode from src.frame_logic import ( check_hybrid_mana_cost, get_frame_details, @@ -354,9 +354,8 @@ def name_raw(self) -> str: @cached_property def nickname(self) -> str: """Nickname, typically set inside template logic but can be passed in filename.""" - # Todo: Add user-provided text if isinstance(self.card, ScryfallCard): - return self.card.flavor_name or "" + return self.file["kwargs"].get("nick") or self.card.flavor_name or "" return "" @cached_property @@ -386,7 +385,34 @@ def oracle_text(self) -> str: @cached_property def oracle_text_raw(self) -> str: """Card rules text, enforced English representation.""" - return self.card.oracle_text or "" + txt = self.card.oracle_text or "" + + if ( + self.config.nickname_allow + and self.config.nickname_in_oracle_text + and self.nickname + ): + if "," in self.name: + # Some cards use a shorter name and some don't, + # so we have to ensure that replacements work as expected. + name_shortener = re.compile(r"\,.*") + original_short_name = name_shortener.sub("", self.name).strip() + txt = txt.replace(self.name, original_short_name) + + if self.config.nickname_shorten_in_oracle_text == NicknameShorten.NO: + txt = txt.replace(original_short_name, self.nickname) + else: + nick_short_name = name_shortener.sub("", self.nickname).strip() + if ( + self.config.nickname_shorten_in_oracle_text + == NicknameShorten.ALL_BUT_FIRST + ): + txt = txt.replace(original_short_name, self.nickname, 1) + txt = txt.replace(original_short_name, nick_short_name) + else: + txt = txt.replace(self.name, self.nickname) + + return txt @cached_property def flavor_text(self) -> str: diff --git a/src/templates/_core.py b/src/templates/_core.py index fc2427d4..a34851a5 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -540,6 +540,11 @@ def mask_group(self) -> LayerSet | None: * Text Layers """ + @cached_property + def text_layer_artist(self) -> ArtLayer | None: + """Card artist text layer.""" + return psd.getLayer(LAYERS.ARTIST, self.legal_group) + @cached_property def text_layer_creator(self) -> ArtLayer | None: """Optional[ArtLayer]: Proxy creator name text layer.""" @@ -903,7 +908,6 @@ def collector_info_basic(self) -> None: """Called to generate basic collector info.""" # Collector layers - artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group) set_layer = psd.getLayer(LAYERS.SET, self.legal_group) set_TI = set_layer.textItem if set_layer else None @@ -911,8 +915,8 @@ def collector_info_basic(self) -> None: if self.border_color != BorderColor.Black: if set_TI: set_TI.color = self.RGB_BLACK - if artist_layer: - artist_layer.textItem.color = self.RGB_BLACK + if self.text_layer_artist: + self.text_layer_artist.textItem.color = self.RGB_BLACK # Fill optional collector star if set_layer and self.is_collector_promo: @@ -922,8 +926,8 @@ def collector_info_basic(self) -> None: if set_layer and self.layout.lang != "en": psd.replace_text(set_layer, "EN", self.layout.lang.upper()) - if artist_layer: - psd.replace_text(artist_layer, "Artist", self.layout.artist) + if self.text_layer_artist: + psd.replace_text(self.text_layer_artist, "Artist", self.layout.artist) if set_TI: set_TI.contents = self.layout.set + set_TI.contents @@ -932,8 +936,8 @@ def collector_info_authentic(self) -> None: """Called to generate realistic collector info.""" # Hide basic layers - if layer := psd.getLayer(LAYERS.ARTIST, self.legal_group): - layer.visible = False + if self.text_layer_artist: + self.text_layer_artist.visible = False if layer := psd.getLayer(LAYERS.SET, self.legal_group): layer.visible = False @@ -970,17 +974,16 @@ def collector_info_artist_only(self) -> None: """Called to generate 'Artist Only' collector info.""" # Collector layers - artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group) if layer := psd.getLayer(LAYERS.SET, self.legal_group): layer.visible = False # Correct color for non-black border - if artist_layer and self.border_color != BorderColor.Black: - artist_layer.textItem.color = self.RGB_BLACK + if self.text_layer_artist and self.border_color != BorderColor.Black: + self.text_layer_artist.textItem.color = self.RGB_BLACK # Insert artist name - if artist_layer: - psd.replace_text(artist_layer, "Artist", self.layout.artist) + if self.text_layer_artist: + psd.replace_text(self.text_layer_artist, "Artist", self.layout.artist) """ * Expansion Symbol @@ -1239,7 +1242,7 @@ def check_photoshop(self) -> None: return # Connection with Photoshop couldn't be established, try again? - if not self.render_operation.pause_sync( + if not self.pause( f"{get_photoshop_error_message(check)}\nPress OK to try again, or Cancel to end the render.", ): # Cancel the operation diff --git a/src/templates/_cosmetic.py b/src/templates/_cosmetic.py index 5c02c7ed..a8ee7352 100644 --- a/src/templates/_cosmetic.py +++ b/src/templates/_cosmetic.py @@ -249,14 +249,12 @@ def enable_companion_layers(self) -> None: """ -class NicknameMod(VectorTemplate): - """Modifier for adding Nickname support to a template. Nickname features were introduced after - VectorTemplate architecture reached maturity, so there are no plans to add Nickname support - to Raster-based templates, and this Mod is entirely based around VectorTemplate design practices. +class NicknameMod(BaseTemplate): + """Modifier for adding Nickname support to a template. Adds: - - `is_nickname`: Boolean property which toggles Nickname behavior (default: True). - - 'nickname_group': Defines the group containing some OR all Nickname frame elements. + - `is_nickname`: Boolean property which toggles Nickname behavior. + - 'nickname_group': Defines the group containing Nickname frame elements. - `text_layer_nickname`: Defines the text layer the original card name gets moved to, so Nickname text may be added to the Card Name layer. - `nickname_shape`: Defines the shape layer used for the Nickname plat backing, also used @@ -264,15 +262,17 @@ class NicknameMod(VectorTemplate): Modifies: - Hooks the `basic_text_layers` method to swap the Card Name text with the Nickname text, and - allow the user to manually enter the Nickname text before text formatting occurs, if + allows the user to manually enter the Nickname text before text formatting occurs, if nickname text wasn't provided automatically. """ @cached_property def post_text_methods(self) -> list[Callable[[], None]]: """Add nickname text layers.""" - funcs = [self.format_nickname_text] if self.is_nickname else [] - return [*super().post_text_methods, *funcs] + funcs = super().post_text_methods + if self.is_nickname: + funcs.append(self.format_nickname_text) + return funcs """ * Flags @@ -280,9 +280,10 @@ def post_text_methods(self) -> list[Callable[[], None]]: @cached_property def is_nickname(self) -> bool: - """Toggles nickname behavior. Can be overwritten to implement - conditional logic.""" - return True + """Toggles nickname behavior. Can be overwritten to implement conditional logic.""" + return self.config.nickname_allow and ( + bool(self.layout.nickname) or self.config.nickname_prompt + ) """ * Text Layers @@ -320,14 +321,6 @@ def nickname_shape(self) -> ReferenceLayer | None: return _layer return psd.get_reference_layer(LAYERS.NORMAL, _shape_group) - @cached_property - def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: - """Add Nickname shape if needed.""" - _shapes = super().enabled_shapes - if self.is_nickname: - _shapes.append(self.nickname_shape) - return _shapes - """ * Overwrite Methods """ @@ -370,7 +363,17 @@ def prompt_nickname_text(self) -> None: _textItem = self.text_layer_name.textItem _textItem.contents = "ENTER NICKNAME" select_layer(self.text_layer_name) - self.render_operation.pause_sync( - "Enter nickname text." - ) + self.pause("Enter nickname text.") self.layout.nickname = _textItem.contents + + +class NicknameVectorMod(NicknameMod, VectorTemplate): + """Modifier for adding Nickname support to a vector template.""" + + @cached_property + def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: + """Add Nickname shape if needed.""" + _shapes = super().enabled_shapes + if self.is_nickname: + _shapes.append(self.nickname_shape) + return _shapes diff --git a/src/templates/normal.py b/src/templates/normal.py index 336afb9d..5a4a6fb3 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -4,9 +4,10 @@ from collections.abc import Callable, Iterable, Sequence from functools import cached_property -from photoshop.api import AnchorPosition, SolidColor +from photoshop.api import SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import AnchorPosition import src.helpers as psd from src import CON @@ -23,7 +24,7 @@ from src.helpers.layers import get_reference_layer from src.schema.adobe import EffectBevel, EffectColorOverlay from src.schema.colors import ColorObject, GradientConfig, pinlines_color_map -from src.templates import NicknameMod +from src.templates import NicknameVectorMod from src.templates._core import NormalTemplate from src.templates._cosmetic import ( CompanionMod, @@ -1291,7 +1292,7 @@ def enable_crown(self) -> None: class BorderlessVectorTemplate( - NicknameMod, VectorBorderlessMod, VectorMDFCMod, VectorTransformMod, VectorTemplate + NicknameVectorMod, VectorBorderlessMod, VectorMDFCMod, VectorTransformMod, VectorTemplate ): """Borderless template first used in the Womens Day Secret Lair, redone with vector shapes.""" @@ -1541,13 +1542,6 @@ def is_textless(self) -> bool: return True return self.config.get_bool_setting(section="FRAME", key="Textless", default=False) - @cached_property - def is_nickname(self) -> bool: - """Return True if this a nickname render.""" - if self.layout.nickname: - return True - return self.config.get_bool_setting(section="TEXT", key="Nickname", default=False) - @cached_property def is_colored_nickname(self) -> bool: """Return True if nickname plate should be colored.""" From b8237ba9c602949aaca04135992e099eef2d31f5 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 26 Mar 2026 16:23:57 +0200 Subject: [PATCH 085/190] fix(SettingsWindow.qml): Correctly update bool settings and ensure modified settings are saved on window close --- src/gui/qml/models/settings_model.py | 4 +-- src/gui/qml/views/SettingsWindow.qml | 51 +++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/gui/qml/models/settings_model.py b/src/gui/qml/models/settings_model.py index 2eb53529..c925a79b 100644 --- a/src/gui/qml/models/settings_model.py +++ b/src/gui/qml/models/settings_model.py @@ -92,7 +92,7 @@ def setData( /, role: int = Qt.ItemDataRole.EditRole, ) -> bool: - if index.isValid() or role in self._roles and self._roles[role] == "value": + if index.isValid() or self._roles.get(role) == "value": item = self.items[index.row()] if self._current_config_handler and self._current_config_handler.set_value( item.section, item.key, value @@ -203,5 +203,5 @@ def _on_clear(self, conf_handler: ConfigHandler) -> None: _valid_changed = Signal() @Property(bool, notify=_valid_changed) - def valid(self) -> bool: # pyright: ignore[reportRedeclaration] + def valid(self) -> bool: return self._current_config_handler is not None diff --git a/src/gui/qml/views/SettingsWindow.qml b/src/gui/qml/views/SettingsWindow.qml index bb02e27e..4a04a7b9 100644 --- a/src/gui/qml/views/SettingsWindow.qml +++ b/src/gui/qml/views/SettingsWindow.qml @@ -288,7 +288,20 @@ ApplicationWindow { text: settingsListDelegate.value color: settingsWindow.systemPalette.text - onEditingFinished: settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingTextInput.text) + function onSetValue() { + settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingTextInput.text); + } + + onEditingFinished: onSetValue() + + Connections { + target: settingsWindow + + function onClosing(): void { + if (settingTextInput.activeFocus) + settingTextInput.onSetValue(); + } + } } } Component { @@ -300,7 +313,7 @@ ApplicationWindow { systemPalette: settingsWindow.systemPalette checked: settingsListDelegate.value - onClicked: settingsWindow.settingsModel.bool_value_changed(settingsListDelegate.index, !settingsListDelegate.value) + onClicked: settingsWindow.settingsModel.bool_value_changed(settingsListDelegate.index, settingCheckbox.checked) } } Component { @@ -314,7 +327,23 @@ ApplicationWindow { to: 2147483647 value: settingsListDelegate.value - onValueModified: settingsWindow.settingsModel.int_value_changed(settingsListDelegate.index, settingSpinBoxInt.value) + function onSetValue(): void { + settingsWindow.settingsModel.int_value_changed(settingsListDelegate.index, settingSpinBoxInt.value); + } + + onValueModified: onSetValue() + + Connections { + target: settingsWindow + + function onClosing(): void { + if (settingSpinBoxInt.activeFocus) { + // Forcibly trigger onValueModified on window close + settingSpinBoxInt.focus = false; + settingSpinBoxInt.focus = true; + } + } + } } } Component { @@ -352,9 +381,23 @@ ApplicationWindow { return Math.round(parseFloat(text) * decimalFactor); } - onValueModified: { + function onSetValue(): void { settingsWindow.settingsModel.float_value_changed(settingsListDelegate.index, settingSpinBoxDouble.realValue); } + + onValueModified: onSetValue() + + Connections { + target: settingsWindow + + function onClosing(): void { + if (settingSpinBoxDouble.activeFocus) { + // Forcibly trigger onValueModified on window close + settingSpinBoxDouble.focus = false; + settingSpinBoxDouble.focus = true; + } + } + } } } Component { From 673dffdc88895dfec69f7529298c5be7ca14951c Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 26 Mar 2026 16:34:06 +0200 Subject: [PATCH 086/190] feat(SettingsWindow.qml): Show default values for settings --- src/gui/qml/views/SettingsWindow.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gui/qml/views/SettingsWindow.qml b/src/gui/qml/views/SettingsWindow.qml index 4a04a7b9..28cc052c 100644 --- a/src/gui/qml/views/SettingsWindow.qml +++ b/src/gui/qml/views/SettingsWindow.qml @@ -246,6 +246,16 @@ ApplicationWindow { color: settingsWindow.systemPalette.text visible: Boolean(settingsListDelegate.desc) } + SelectableText { + id: defaultText + + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + text: "Default: " + settingsListDelegate.default_value + color: settingsWindow.systemPalette.placeholderText + visible: !settingsListDelegate.isTitle + } } Loader { id: inputLoader From bdf4bcc35820b17741418375de3daba09c2880a7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 26 Mar 2026 16:35:33 +0200 Subject: [PATCH 087/190] style(base.toml): Remove trailing line break from "Allow Nickname" setting's description --- src/data/config/base.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data/config/base.toml b/src/data/config/base.toml index 04049b77..fe46892d 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -48,8 +48,7 @@ options = ["automatic", "always", "never"] title = "Allow Nickname" desc = """When enabled, if a nickname is available and the template has support for it, the card will be rendered with both a real name and a nickname. Some Scryfall cards come with a nickname by default, e.g. Gemrazer [IKO] {376}. Nickname can also be specified via the art filename with [nick=...]. E.g. Arcane Signet [nick=Manastone] -Keep Out (Artist) [ECL] [nick=Access Denied] {19} -""" +Keep Out (Artist) [ECL] [nick=Access Denied] {19}""" type = "bool" default = 1 From f219e50164ae8fc4dbcf9063cfa46235465551e5 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 1 Apr 2026 15:44:45 +0200 Subject: [PATCH 088/190] Implement basic render-spec --- src/render_spec.py | 98 +++++++++++++++++++++++++++++++++++++++++++++ src/utils/images.py | 32 +++++++++++---- 2 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 src/render_spec.py diff --git a/src/render_spec.py b/src/render_spec.py new file mode 100644 index 00000000..81295786 --- /dev/null +++ b/src/render_spec.py @@ -0,0 +1,98 @@ +""" +* Render Spec Module +* Handles parsing render spec file, aka text files that contain a set of configurations and cards to render +""" + +# Standard Library Imports +import re as regex +from pathlib import Path +from typing import Dict, TypedDict, Optional, Any + +# Local Imports +from src.cards import CardDetails, parse_card_info + +""" +* Types +""" + + +class RenderConfiguration(TypedDict): + name: str + info: str + + +class RenderSpec(TypedDict): + """Render spec obtained from parsing the file.""" + + name: str + file: Path + configs: Dict[str, RenderConfiguration] + cards: list[CardDetails] + + +""" +* File parsing +""" + + +def parse_render_spec(file_path: Path, logger: Optional[Any] = None) -> RenderSpec: + """Retrieve render spec from the input file. + + Args: + file_path: Path to the text file. + logger: Console or other logger object used to relay warning messages. + + Returns: + Dict containing the configurations and cards in this spec. + """ + + # Extract just the spec name + file_name = file_path.stem + + # Load all the content and get rid of empty lines and comments + spec_lines = open(file_path, "r").read().splitlines() + spec_lines = [l for l in spec_lines if l.split("#")[0].strip()] + + # Split lines with configs and lines with cards + config_lines = [l for l in spec_lines if regex.match(r"([a-zA-Z0-9_ ]+):(.*)", l)] + card_lines = [l for l in spec_lines if l not in config_lines] + + # Find all the configurations first + configs = {} + for l in config_lines: + [config_name, config_info] = map(str.strip, l.split(":")) + configs[config_name] = { + "name": config_name, + "info": config_info, + } + + # Now find all the cards and parse them by using the configs + cards = [] + for l in card_lines: + parts = list(map(str.strip, l.split("|"))) + full_card_spec = parts[0] + used_configs = parts[1:] + invalid_configs = [c for c in used_configs if c not in configs] + if invalid_configs: + if logger: + logger.update( + f"Card ignored due to unknown render spec configurations: {full_card_spec.split()[0]}" + ) + for c in invalid_configs: + logger.update(f"\nUnknown render spec configuration: {c}") + continue + for c in used_configs: + spec_info = configs[c]["info"] + full_card_spec += f" {spec_info}" + + # Pretend this is a file and parse that + full_card_path = file_path.with_name(full_card_spec) + cards.append(parse_card_info(full_card_path)) + + # Return dictionary + return { + "name": file_name, + "file": file_path, + "configs": configs, + "cards": cards, + } diff --git a/src/utils/images.py b/src/utils/images.py index 282c6c9c..c444cd97 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -2,11 +2,13 @@ from os import PathLike from pathlib import Path from typing import IO +from dataclasses import dataclass from PIL import Image from pydantic import ValidationError from src.cards import CardDetails, parse_card_info +from src.render_spec import parse_render_spec from src.utils.data_structures import find_index from src.utils.scryfall import ScryfallCard @@ -57,12 +59,16 @@ def match_images_with_data_files( Pydantic.ValidationError: if some of the data files don't conform to the data model """ data_files = [pth for pth in paths if pth.suffix == ".json"] - image_files = [pth for pth in paths if pth.suffix != ".json"] + render_specs = [pth for pth in paths if pth.suffix == ".txt"] + image_files = [pth for pth in paths if pth.suffix not in (".json", ".txt")] results: list[CardDetails | tuple[CardDetails, ScryfallCard]] = [] - for path in image_files: - card = parse_card_info(path) + @dataclass + class _ValidationError(ValidationError): + file: Path + + def add_card(card: CardDetails) -> None: card_name = card["name"] idx = find_index(data_files, lambda item: item.stem == card_name) @@ -76,13 +82,25 @@ def match_images_with_data_files( ) ) except ValidationError: - _logger.exception( - f"Data file {data_file} failed to validate. Please correct the reported errors in the data and then try again. Since the file selection was invalid nothing will be added to the render queue." - ) - return [] + raise _ValidationError(data_file) else: results.append(card) + try: + for path in render_specs: + cards = parse_render_spec(path)["cards"] + for card in cards: + add_card(card) + + for path in image_files: + card = parse_card_info(path) + add_card(card) + except _ValidationError as e: + _logger.exception( + f"Data file {e.file} failed to validate. Please correct the reported errors in the data and then try again. Since the file selection was invalid nothing will be added to the render queue." + ) + return [] + if data_files: _logger.warning( f"Couldn't find a matching image file for files:
{ From d4955a9368812f129ab418e4faf6a775ce5f00dd Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 6 Jan 2024 12:25:30 +0100 Subject: [PATCH 089/190] Add option to specify art file in additional_cfg --- src/templates/_core.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/templates/_core.py b/src/templates/_core.py index a34851a5..6a01b0b6 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -426,7 +426,7 @@ def is_flipside_creature(self) -> bool: @cached_property def is_art_vertical(self) -> bool: """bool: Returns True if art provided is vertically oriented, False if it is horizontal.""" - with Image.open(self.layout.art_file) as image: + with Image.open(self.art_file) as image: width, height = image.size if height > (width * 1.1): # Vertical orientation @@ -746,6 +746,15 @@ def process_layout_data(self) -> None: * Loading Artwork """ + @cached_property + def art_file(self) -> Path: + """Path to the art file to load.""" + art_file = self.layout.file.get('kwargs', {}).get('art', None) + if art_file is not None: + return self.layout.art_file.with_name(art_file) + else: + return self.layout.art_file + @cached_property def art_action(self) -> Callable[[], None] | None: """Function that is called to perform an action on the imported art.""" @@ -767,7 +776,7 @@ def load_artwork( """Loads the specified art file into the specified layer. Args: - art_file: Optional path (as str or Path) to art file. Will use `self.layout.art_file` + art_file: Optional path (as str or Path) to art file. Will use `self.art_file` if not provided. art_layer: Optional `ArtLayer` where art image should be placed when imported. Will use `self.art_layer` property if not provided. @@ -776,7 +785,7 @@ def load_artwork( """ # Set default values - art_file = art_file or self.layout.art_file + art_file = art_file or self.art_file art_layer = art_layer or self.art_layer art_reference = art_reference or self.art_reference From 3ddeba96c42696f155063cfdc47e3680b01dbb11 Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 6 Jan 2024 13:40:03 +0100 Subject: [PATCH 090/190] Add groups in a render-spec --- src/render_spec.py | 50 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 81295786..38d6bcb6 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -68,26 +68,62 @@ def parse_render_spec(file_path: Path, logger: Optional[Any] = None) -> RenderSp # Now find all the cards and parse them by using the configs cards = [] + groups = [] for l in card_lines: + # Entering a group + if l.startswith("{"): + groups.append([]) + l = l[1:].strip() + if not l: + continue + parts = list(map(str.strip, l.split("|"))) - full_card_spec = parts[0] + full_line_spec = parts[0] used_configs = parts[1:] invalid_configs = [c for c in used_configs if c not in configs] if invalid_configs: if logger: logger.update( - f"Card ignored due to unknown render spec configurations: {full_card_spec.split()[0]}" + f"Card ignored due to unknown render spec configurations: {full_line_spec.split()[0]}" ) for c in invalid_configs: logger.update(f"\nUnknown render spec configuration: {c}") continue for c in used_configs: spec_info = configs[c]["info"] - full_card_spec += f" {spec_info}" - - # Pretend this is a file and parse that - full_card_path = file_path.with_name(full_card_spec) - cards.append(parse_card_info(full_card_path)) + full_line_spec += f" {spec_info}" + + def append_card(card_spec): + # Pretend this is a file and parse that + full_card_path = file_path.with_stem(card_spec) + cards.append(parse_card_info(full_card_path)) + + # If part of a group we need to just accumulate + if groups: + if l.startswith("}"): + # The group ended, so we assign this configuration to all the cards in it + full_line_spec = full_line_spec[1:].strip() + ended_group = groups.pop() + ended_group = [c + f" {full_line_spec}" for c in ended_group] + + # Replace special variables + ended_group = [ + c.replace("${GROUP_INDEX}", str(i)) + for (i, c) in enumerate(ended_group) + ] + + if groups: + # If this was a nested group we just put these into the outer group + groups[-1].extend(ended_group) + else: + # Otherwise append to the render spec + for card in ended_group: + append_card(card) + else: + # Append the card to the group + groups[-1].append(full_line_spec) + else: + append_card(full_line_spec) # Return dictionary return { From d65fbd9ae013c3ff8ceddb9a33c1c6c87dfc2e40 Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 6 Jan 2024 13:41:28 +0100 Subject: [PATCH 091/190] Add inner and outer group index variables --- src/render_spec.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 38d6bcb6..0f9a329d 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -106,16 +106,30 @@ def append_card(card_spec): ended_group = groups.pop() ended_group = [c + f" {full_line_spec}" for c in ended_group] - # Replace special variables - ended_group = [ - c.replace("${GROUP_INDEX}", str(i)) - for (i, c) in enumerate(ended_group) - ] - if groups: + # Replace special variables + ended_group = [ + c.replace("${GROUP_INDEX}", str(i)) + for (i, c) in enumerate(ended_group) + ] + ended_group = [ + c.replace("${INNER_GROUP_INDEX}", str(i)) + for (i, c) in enumerate(ended_group) + ] + # If this was a nested group we just put these into the outer group groups[-1].extend(ended_group) else: + # Replace special variables + ended_group = [ + c.replace("${GROUP_INDEX}", str(i)) + for (i, c) in enumerate(ended_group) + ] + ended_group = [ + c.replace("${OUTER_GROUP_INDEX}", str(i)) + for (i, c) in enumerate(ended_group) + ] + # Otherwise append to the render spec for card in ended_group: append_card(card) From e577aee349b4e1308b40789cb75f3881e8e27757 Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 6 Jan 2024 13:56:05 +0100 Subject: [PATCH 092/190] Allow inline configurations in render specs instead of requiring everything to be defined with a name --- src/render_spec.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 0f9a329d..73579220 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -35,12 +35,11 @@ class RenderSpec(TypedDict): """ -def parse_render_spec(file_path: Path, logger: Optional[Any] = None) -> RenderSpec: +def parse_render_spec(file_path: Path) -> RenderSpec: """Retrieve render spec from the input file. Args: file_path: Path to the text file. - logger: Console or other logger object used to relay warning messages. Returns: Dict containing the configurations and cards in this spec. @@ -51,7 +50,8 @@ def parse_render_spec(file_path: Path, logger: Optional[Any] = None) -> RenderSp # Load all the content and get rid of empty lines and comments spec_lines = open(file_path, "r").read().splitlines() - spec_lines = [l for l in spec_lines if l.split("#")[0].strip()] + spec_lines = [l.split("#")[0].strip() for l in spec_lines] + spec_lines = [l for l in spec_lines if l] # Split lines with configs and lines with cards config_lines = [l for l in spec_lines if regex.match(r"([a-zA-Z0-9_ ]+):(.*)", l)] @@ -80,18 +80,12 @@ def parse_render_spec(file_path: Path, logger: Optional[Any] = None) -> RenderSp parts = list(map(str.strip, l.split("|"))) full_line_spec = parts[0] used_configs = parts[1:] - invalid_configs = [c for c in used_configs if c not in configs] - if invalid_configs: - if logger: - logger.update( - f"Card ignored due to unknown render spec configurations: {full_line_spec.split()[0]}" - ) - for c in invalid_configs: - logger.update(f"\nUnknown render spec configuration: {c}") - continue for c in used_configs: - spec_info = configs[c]["info"] - full_line_spec += f" {spec_info}" + if c in configs: + spec_info = configs[c]["info"] + full_line_spec += f" {spec_info}" + else: + full_line_spec += f" {c}" def append_card(card_spec): # Pretend this is a file and parse that From c9bb973058b8bd1af3b074d6ca632a243504e643 Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 13 Jan 2024 16:36:35 +0100 Subject: [PATCH 093/190] Add support for globs in render spec --- src/render_spec.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 73579220..67f00282 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -4,6 +4,7 @@ """ # Standard Library Imports +import glob import re as regex from pathlib import Path from typing import Dict, TypedDict, Optional, Any @@ -47,6 +48,7 @@ def parse_render_spec(file_path: Path) -> RenderSpec: # Extract just the spec name file_name = file_path.stem + parent_dir = str(file_path.parent) # Load all the content and get rid of empty lines and comments spec_lines = open(file_path, "r").read().splitlines() @@ -78,27 +80,34 @@ def parse_render_spec(file_path: Path) -> RenderSpec: continue parts = list(map(str.strip, l.split("|"))) - full_line_spec = parts[0] + spec_base = parts[0] + + if "*" in spec_base: + specs = glob.glob(spec_base, root_dir=parent_dir, recursive=True) + specs = [s for s in specs if not s.endswith(".txt")] + else: + specs = [spec_base] + used_configs = parts[1:] for c in used_configs: if c in configs: - spec_info = configs[c]["info"] - full_line_spec += f" {spec_info}" + config_spec = configs[c]["info"] else: - full_line_spec += f" {c}" + config_spec = c + specs = [s + f" {config_spec}" for s in specs] def append_card(card_spec): # Pretend this is a file and parse that - full_card_path = file_path.with_stem(card_spec) + full_card_path = file_path.parent.joinpath(card_spec) cards.append(parse_card_info(full_card_path)) # If part of a group we need to just accumulate if groups: if l.startswith("}"): # The group ended, so we assign this configuration to all the cards in it - full_line_spec = full_line_spec[1:].strip() + group_spec = specs[0][1:].strip() ended_group = groups.pop() - ended_group = [c + f" {full_line_spec}" for c in ended_group] + ended_group = [c + f" {group_spec}" for c in ended_group] if groups: # Replace special variables @@ -129,9 +138,10 @@ def append_card(card_spec): append_card(card) else: # Append the card to the group - groups[-1].append(full_line_spec) + groups[-1].extend(specs) else: - append_card(full_line_spec) + for card_spec in specs: + append_card(card_spec) # Return dictionary return { From 542bcf7ffc6053c1b935e7d3428f76d5439e5c14 Mon Sep 17 00:00:00 2001 From: Mal Date: Mon, 15 Jan 2024 09:13:08 +0100 Subject: [PATCH 094/190] Fix for globbed files loosing their ability to load artwork when applying specs --- src/render_spec.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 67f00282..e9789617 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -4,6 +4,7 @@ """ # Standard Library Imports +import os import glob import re as regex from pathlib import Path @@ -82,11 +83,25 @@ def parse_render_spec(file_path: Path) -> RenderSpec: parts = list(map(str.strip, l.split("|"))) spec_base = parts[0] + def append_config(card, config): + return (card[0] + f" {config}", card[1]) + + def append_card(card_spec): + spec, path = card_spec + # Pretend this is a file and parse that + full_card_path = file_path.parent.joinpath(spec) + card_info = parse_card_info(full_card_path) + if path is not None: + card_info["additional_cfg"]["art"] = path + cards.append(card_info) + if "*" in spec_base: specs = glob.glob(spec_base, root_dir=parent_dir, recursive=True) - specs = [s for s in specs if not s.endswith(".txt")] + specs = [(s.split(".")[0], s) for s in specs if not s.endswith(".txt")] + elif os.path.exists(spec_base): + specs = [(spec_base.split(".")[0], spec_base)] else: - specs = [spec_base] + specs = [(spec_base, None)] used_configs = parts[1:] for c in used_configs: @@ -94,20 +109,15 @@ def parse_render_spec(file_path: Path) -> RenderSpec: config_spec = configs[c]["info"] else: config_spec = c - specs = [s + f" {config_spec}" for s in specs] - - def append_card(card_spec): - # Pretend this is a file and parse that - full_card_path = file_path.parent.joinpath(card_spec) - cards.append(parse_card_info(full_card_path)) + specs = [append_config(s, config_spec) for s in specs] # If part of a group we need to just accumulate if groups: if l.startswith("}"): # The group ended, so we assign this configuration to all the cards in it - group_spec = specs[0][1:].strip() + group_spec = specs[0][0][1:].strip() ended_group = groups.pop() - ended_group = [c + f" {group_spec}" for c in ended_group] + ended_group = [append_config(c, group_spec) for c in ended_group] if groups: # Replace special variables From 11355ed73c8747631fd201767152d65c9b81fb66 Mon Sep 17 00:00:00 2001 From: Mal Date: Mon, 15 Jan 2024 11:32:05 +0100 Subject: [PATCH 095/190] Fix issues withb `art=` tag and relative paths --- src/render_spec.py | 4 ++-- src/templates/_core.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index e9789617..0fc5749d 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -88,8 +88,8 @@ def append_config(card, config): def append_card(card_spec): spec, path = card_spec - # Pretend this is a file and parse that - full_card_path = file_path.parent.joinpath(spec) + # Pretend this is a file right next to the spec and parse that + full_card_path = file_path.parent / Path(spec).name card_info = parse_card_info(full_card_path) if path is not None: card_info["additional_cfg"]["art"] = path diff --git a/src/templates/_core.py b/src/templates/_core.py index 6a01b0b6..222a6d50 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -751,7 +751,11 @@ def art_file(self) -> Path: """Path to the art file to load.""" art_file = self.layout.file.get('kwargs', {}).get('art', None) if art_file is not None: - return self.layout.art_file.with_name(art_file) + art_file = Path(art_file) + if art_file.is_absolute(): + return art_file + else: + return self.layout.art_file.parent / art_file else: return self.layout.art_file From ed728465536ef0743a10baede1066e6e50e298af Mon Sep 17 00:00:00 2001 From: Mal Date: Fri, 19 Jan 2024 12:46:38 +0100 Subject: [PATCH 096/190] Fix some issues with globs --- src/render_spec.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 0fc5749d..9a3eeea9 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -5,10 +5,11 @@ # Standard Library Imports import os +import re import glob import re as regex from pathlib import Path -from typing import Dict, TypedDict, Optional, Any +from typing import Dict, TypedDict # Local Imports from src.cards import CardDetails, parse_card_info @@ -88,6 +89,11 @@ def append_config(card, config): def append_card(card_spec): spec, path = card_spec + # Make sure the extension doesn't contain a ']' as that implies + # we have something without extension using a config that contains + # something with extension, e.g. [art=file.png] + if not re.match(r"\.[^\]]+$", spec): + spec += ".png" # Pretend this is a file right next to the spec and parse that full_card_path = file_path.parent / Path(spec).name card_info = parse_card_info(full_card_path) @@ -119,14 +125,17 @@ def append_card(card_spec): ended_group = groups.pop() ended_group = [append_config(c, group_spec) for c in ended_group] + def expand_variable(card, variable, value): + return (card[0].replace(variable, str(value)), card[1]) + if groups: # Replace special variables ended_group = [ - c.replace("${GROUP_INDEX}", str(i)) + expand_variable(c, "${GROUP_INDEX}", i) for (i, c) in enumerate(ended_group) ] ended_group = [ - c.replace("${INNER_GROUP_INDEX}", str(i)) + expand_variable(c, "${INNER_GROUP_INDEX}", i) for (i, c) in enumerate(ended_group) ] @@ -135,11 +144,11 @@ def append_card(card_spec): else: # Replace special variables ended_group = [ - c.replace("${GROUP_INDEX}", str(i)) + expand_variable(c, "${GROUP_INDEX}", i) for (i, c) in enumerate(ended_group) ] ended_group = [ - c.replace("${OUTER_GROUP_INDEX}", str(i)) + expand_variable(c, "${OUTER_GROUP_INDEX}", i) for (i, c) in enumerate(ended_group) ] From ce98ed0d40d0fe56be21d74cab592136cca6a605 Mon Sep 17 00:00:00 2001 From: Mal Date: Mon, 15 Jan 2024 10:15:36 +0100 Subject: [PATCH 097/190] Add option to maintain folder structure in out folder --- src/_config.py | 3 +++ src/data/config/app.toml | 6 ++++++ src/templates/_core.py | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/src/_config.py b/src/_config.py index 330e15aa..fa2d6516 100644 --- a/src/_config.py +++ b/src/_config.py @@ -108,6 +108,9 @@ def update_definitions(self): option="Output.File.Name", fallback="#name (#frame<, #suffix>) [#set] {#num}", ) + self.maintain_folder_structure = self.file.getboolean( + section='APP.FILES', option='Maintain.Folder.Structure', + fallback=True) self.png_compression_level = self.file.getint( "APP.FILES", "PNG.Compression.Level", fallback=3 ) diff --git a/src/data/config/app.toml b/src/data/config/app.toml index 91ab7d0e..3f68ef8a 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -22,6 +22,12 @@ desc = """Choose how the rendered card images are named. See the README for adva type = "string" default = "#name (#frame<, #suffix>) [#set] {#num}" +[FILES."Maintain.Folder.Structure"] +title = "Maintain Folder Structure" +desc = """Choose whether to flatten all images into the `out` folder or maintain the folder structure of the `art` folder.""" +type = "bool" +default = 1 + [FILES."Overwrite.Duplicate"] title = "Overwrite Duplicates" desc = """Overwrite rendered files with identical file names.""" diff --git a/src/templates/_core.py b/src/templates/_core.py index 222a6d50..69953642 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -2,6 +2,7 @@ * CORE PROXYSHOP TEMPLATES """ +import os from collections.abc import Callable, Sequence from contextlib import suppress from functools import cached_property @@ -286,6 +287,10 @@ def output_file_name(self) -> Path: f".{self.config.output_file_type}" ) + if self.config.maintain_folder_structure and self.art_file.is_relative_to(PATH.ART): + relative_path = self.art_file.parent.relative_to(PATH.ART) + path = path.parent / relative_path / path.name + # Are we overwriting duplicate names? if not self.config.overwrite_duplicate: path = get_unique_filename(path) @@ -1651,6 +1656,12 @@ async def execute(self) -> bool: if self.config.exit_early: await self.pause_async("Rendering paused for manual editing.") + # Make sure output folder exists + if self.config.maintain_folder_structure: + output_folder = self.output_file_name.parent + if not os.path.exists(output_folder): + os.makedirs(output_folder) + # Save the document if not self.run_tasks( funcs=[lambda: self.save_mode(self.output_file_name, self.docref)], From e68b3edbb7f22198427c955b025e73fe990aec55 Mon Sep 17 00:00:00 2001 From: Mal Date: Thu, 2 Apr 2026 10:31:15 +0200 Subject: [PATCH 098/190] Add ability to override set symbol from additional config --- src/layouts.py | 3 +++ src/templates/_core.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index df75cfc4..2e9b6d97 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -499,6 +499,9 @@ def color_indicator(self) -> str: @cached_property def symbol_code(self) -> str: """Code used to match a symbol to this card's set. Provided by hexproof.io.""" + forced_symbol = self.file['kwargs'].get('sym', None) + if forced_symbol: + return forced_symbol.upper() if self.config.symbol_force_default: return self.config.symbol_default.upper() if self.set_data: diff --git a/src/templates/_core.py b/src/templates/_core.py index 69953642..22b0a9c1 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -754,7 +754,7 @@ def process_layout_data(self) -> None: @cached_property def art_file(self) -> Path: """Path to the art file to load.""" - art_file = self.layout.file.get('kwargs', {}).get('art', None) + art_file = self.layout.file['kwargs'].get('art', None) if art_file is not None: art_file = Path(art_file) if art_file.is_absolute(): From 78d42fc501e91b7a2a8f82a75a8167e579c9833c Mon Sep 17 00:00:00 2001 From: Mal Date: Thu, 2 Apr 2026 10:31:27 +0200 Subject: [PATCH 099/190] Cleanup render_spec.py --- src/render_spec.py | 51 +++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 9a3eeea9..788f6eae 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -10,6 +10,7 @@ import re as regex from pathlib import Path from typing import Dict, TypedDict +from dataclasses import dataclass, astuple # Local Imports from src.cards import CardDetails, parse_card_info @@ -19,9 +20,16 @@ """ -class RenderConfiguration(TypedDict): +@dataclass +class RenderConfiguration: name: str - info: str + spec: str + + +@dataclass +class CardSpec: + spec: str + actual_path: str | None class RenderSpec(TypedDict): @@ -64,11 +72,11 @@ def parse_render_spec(file_path: Path) -> RenderSpec: # Find all the configurations first configs = {} for l in config_lines: - [config_name, config_info] = map(str.strip, l.split(":")) - configs[config_name] = { - "name": config_name, - "info": config_info, - } + [config_name, config_spec] = map(str.strip, l.split(":")) + configs[config_name] = RenderConfiguration( + name=config_name, + spec=config_spec, + ) # Now find all the cards and parse them by using the configs cards = [] @@ -84,11 +92,15 @@ def parse_render_spec(file_path: Path) -> RenderSpec: parts = list(map(str.strip, l.split("|"))) spec_base = parts[0] - def append_config(card, config): - return (card[0] + f" {config}", card[1]) + def append_config( + card: CardSpec, config: RenderConfiguration | str + ) -> CardSpec: + if isinstance(config, RenderConfiguration): + config = config.spec + return CardSpec(card.spec + f" {config}", card.actual_path) - def append_card(card_spec): - spec, path = card_spec + def append_card(card_spec: CardSpec): + spec, path = astuple(card_spec) # Make sure the extension doesn't contain a ']' as that implies # we have something without extension using a config that contains # something with extension, e.g. [art=file.png] @@ -97,31 +109,32 @@ def append_card(card_spec): # Pretend this is a file right next to the spec and parse that full_card_path = file_path.parent / Path(spec).name card_info = parse_card_info(full_card_path) - if path is not None: + if path is not None and "art" not in card_info["additional_cfg"]: card_info["additional_cfg"]["art"] = path cards.append(card_info) if "*" in spec_base: specs = glob.glob(spec_base, root_dir=parent_dir, recursive=True) - specs = [(s.split(".")[0], s) for s in specs if not s.endswith(".txt")] + specs = [ + CardSpec(s.split(".")[0], s) for s in specs if not s.endswith(".txt") + ] elif os.path.exists(spec_base): - specs = [(spec_base.split(".")[0], spec_base)] + specs = [CardSpec(spec_base.split(".")[0], spec_base)] else: - specs = [(spec_base, None)] + specs = [CardSpec(spec_base, None)] used_configs = parts[1:] for c in used_configs: if c in configs: - config_spec = configs[c]["info"] + specs = [append_config(s, configs[c]) for s in specs] else: - config_spec = c - specs = [append_config(s, config_spec) for s in specs] + specs = [append_config(s, c) for s in specs] # If part of a group we need to just accumulate if groups: if l.startswith("}"): # The group ended, so we assign this configuration to all the cards in it - group_spec = specs[0][0][1:].strip() + group_spec = specs[0].spec[1:].strip() ended_group = groups.pop() ended_group = [append_config(c, group_spec) for c in ended_group] From 3d019e64ac6d9b6196c4a2ef509c02fb065b1079 Mon Sep 17 00:00:00 2001 From: Mal Date: Thu, 2 Apr 2026 13:54:39 +0200 Subject: [PATCH 100/190] Fix render spec with groups --- src/render_spec.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 788f6eae..a4064db1 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -97,7 +97,10 @@ def append_config( ) -> CardSpec: if isinstance(config, RenderConfiguration): config = config.spec - return CardSpec(card.spec + f" {config}", card.actual_path) + return CardSpec( + card.spec + f" {config}", + card.actual_path, + ) def append_card(card_spec: CardSpec): spec, path = astuple(card_spec) @@ -138,8 +141,11 @@ def append_card(card_spec: CardSpec): ended_group = groups.pop() ended_group = [append_config(c, group_spec) for c in ended_group] - def expand_variable(card, variable, value): - return (card[0].replace(variable, str(value)), card[1]) + def expand_variable(card: CardSpec, variable: str, value: str | int): + return CardSpec( + card.spec.replace(variable, str(value)), + card.actual_path, + ) if groups: # Replace special variables From 7c5bb17641d04683299ca4f4779a673c7eb6a876 Mon Sep 17 00:00:00 2001 From: Hanno Date: Fri, 3 Apr 2026 23:09:33 +0200 Subject: [PATCH 101/190] Make sure you can have an full filename/path in a render spec --- src/render_spec.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index a4064db1..be945499 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -121,10 +121,12 @@ def append_card(card_spec: CardSpec): specs = [ CardSpec(s.split(".")[0], s) for s in specs if not s.endswith(".txt") ] - elif os.path.exists(spec_base): - specs = [CardSpec(spec_base.split(".")[0], spec_base)] else: - specs = [CardSpec(spec_base, None)] + abs_spec_base = file_path.parent / Path(spec_base).name + if os.path.exists(abs_spec_base): + specs = [CardSpec(spec_base.split(".")[0], str(abs_spec_base))] + else: + specs = [CardSpec(spec_base, None)] used_configs = parts[1:] for c in used_configs: From 4ecae7cf586bafe5c24344a297a1e003a3b5e33f Mon Sep 17 00:00:00 2001 From: Hanno Date: Sat, 4 Apr 2026 23:13:07 +0200 Subject: [PATCH 102/190] Fix Pylance errors --- src/render_spec.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index be945499..97f68c23 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -70,7 +70,7 @@ def parse_render_spec(file_path: Path) -> RenderSpec: card_lines = [l for l in spec_lines if l not in config_lines] # Find all the configurations first - configs = {} + configs: dict[str, RenderConfiguration] = {} for l in config_lines: [config_name, config_spec] = map(str.strip, l.split(":")) configs[config_name] = RenderConfiguration( @@ -79,8 +79,8 @@ def parse_render_spec(file_path: Path) -> RenderSpec: ) # Now find all the cards and parse them by using the configs - cards = [] - groups = [] + cards: list[CardDetails] = [] + groups: list[list[CardSpec]] = [] for l in card_lines: # Entering a group if l.startswith("{"): @@ -112,8 +112,8 @@ def append_card(card_spec: CardSpec): # Pretend this is a file right next to the spec and parse that full_card_path = file_path.parent / Path(spec).name card_info = parse_card_info(full_card_path) - if path is not None and "art" not in card_info["additional_cfg"]: - card_info["additional_cfg"]["art"] = path + if path is not None and "art" not in card_info["kwargs"]: + card_info["kwargs"]["art"] = path cards.append(card_info) if "*" in spec_base: From dda31cd049d23c422e64508808f4d3a8049f5fd4 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 5 Apr 2026 12:51:51 +0300 Subject: [PATCH 103/190] refactor: Code cleanup and auto formatting --- main.py | 3 +- plugins/Investigamer/py/__init__.py | 1 + .../Investigamer/py/actions/pencilsketch.py | 7 +- plugins/Investigamer/py/actions/sketch.py | 5 +- plugins/SilvanMTG/py/__init__.py | 1 + plugins/SilvanMTG/py/templates.py | 7 +- src/cards.py | 171 ++++---- src/commands/__init__.py | 48 +-- src/commands/build.py | 40 +- src/commands/docs.py | 15 +- src/commands/files.py | 50 +-- src/commands/render.py | 30 +- src/commands/test/__init__.py | 36 +- src/commands/test/compression.py | 37 +- src/commands/test/frame_logic.py | 89 ++-- src/commands/test/scryfall.py | 15 +- src/commands/test/text_logic.py | 22 +- src/commands/test/utility.py | 53 ++- src/enums/layers.py | 392 +++++++++--------- src/enums/mtg.py | 280 +++++++------ src/frame_logic.py | 351 ++++++++-------- src/gui/qml/models/image_transform_model.py | 10 +- src/gui/qml/models/pydantic_q_list_model.py | 2 +- src/gui/qml/models/render_operations_model.py | 16 +- src/gui/qml/models/settings_tree_model.py | 8 +- src/gui/qml/models/template_list_model.py | 6 +- src/gui/qml/models/test_renders_model.py | 2 +- src/helpers/__init__.py | 1 + src/helpers/actions.py | 12 +- src/helpers/adjustments.py | 29 +- src/helpers/bounds.py | 7 - src/helpers/colors.py | 28 +- src/helpers/descriptors.py | 8 +- src/helpers/document.py | 29 +- src/helpers/effects.py | 20 +- src/helpers/layers.py | 46 +- src/helpers/masks.py | 51 ++- src/helpers/position.py | 8 +- src/helpers/text.py | 6 +- src/templates/__init__.py | 21 +- src/templates/_vector.py | 9 +- src/templates/adventure.py | 14 +- src/templates/battle.py | 10 +- src/templates/case.py | 6 +- src/templates/leveler.py | 9 +- src/templates/mdfc.py | 31 +- src/templates/mutate.py | 14 +- src/templates/normal.py | 64 ++- src/templates/planar.py | 1 + src/templates/prototype.py | 1 + src/templates/saga.py | 18 +- src/templates/split.py | 9 +- src/templates/station.py | 2 +- src/templates/transform.py | 1 + src/utils/adobe.py | 113 ++--- src/utils/build.py | 150 ++++--- src/utils/download.py | 23 +- src/utils/fonts.py | 74 ++-- src/utils/manual_actions.py | 5 +- src/utils/windows.py | 6 +- 60 files changed, 1358 insertions(+), 1165 deletions(-) diff --git a/main.py b/main.py index e781c371..2643a2e3 100644 --- a/main.py +++ b/main.py @@ -27,7 +27,6 @@ def launch_cli(): if "cli" in sys.argv: sys.argv.remove("cli") - # Local Imports from src.commands import ProxyshopCLI # Run the CLI application @@ -119,7 +118,7 @@ def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]) "github_url", f"https://github.com/{ENV.APP_UPDATES_REPO}" ) - # This points to the extracted bundle directory in a PyInstaller build + # This points to the extracted bundle directory in a distributable build engine.addImportPath(Path(__file__).parent / "src" / "gui") QQuickStyle.setStyle("Fusion") QQuickStyle.setFallbackStyle("Basic") diff --git a/plugins/Investigamer/py/__init__.py b/plugins/Investigamer/py/__init__.py index 9aeefdd6..7568aa99 100644 --- a/plugins/Investigamer/py/__init__.py +++ b/plugins/Investigamer/py/__init__.py @@ -4,4 +4,5 @@ Notes: Import * from each py file in this directory. """ + from .templates import * diff --git a/plugins/Investigamer/py/actions/pencilsketch.py b/plugins/Investigamer/py/actions/pencilsketch.py index 810c9b36..e28aa783 100644 --- a/plugins/Investigamer/py/actions/pencilsketch.py +++ b/plugins/Investigamer/py/actions/pencilsketch.py @@ -5,11 +5,12 @@ from collections.abc import Iterable import photoshop.api as ps +from photoshop.api.enumerations import DialogModes from src import APP from src.render.setup import RenderOperation -dialog_mode = ps.DialogModes.DisplayNoDialogs +dialog_mode = DialogModes.DisplayNoDialogs """ * Action Funcs @@ -1388,7 +1389,5 @@ def step343(): select_bg() # Flatten if manual_editing: - render_operation.pause_sync( - "Sketch Action complete." - ) + render_operation.pause_sync("Sketch Action complete.") APP.instance.executeAction(APP.instance.cID("FltI"), None, dialog_mode) diff --git a/plugins/Investigamer/py/actions/sketch.py b/plugins/Investigamer/py/actions/sketch.py index 9616e06d..ca758175 100644 --- a/plugins/Investigamer/py/actions/sketch.py +++ b/plugins/Investigamer/py/actions/sketch.py @@ -2,10 +2,9 @@ * Sketchify Action Module """ -# Third Party Imports -from photoshop.api import ActionDescriptor, ActionList, ActionReference, DialogModes +from photoshop.api import ActionDescriptor, ActionList, ActionReference +from photoshop.api.enumerations import DialogModes -# Local Imports from src import APP diff --git a/plugins/SilvanMTG/py/__init__.py b/plugins/SilvanMTG/py/__init__.py index 58225800..53baca7d 100644 --- a/plugins/SilvanMTG/py/__init__.py +++ b/plugins/SilvanMTG/py/__init__.py @@ -1,4 +1,5 @@ """ * Import all Template Classes """ + from .templates import * diff --git a/plugins/SilvanMTG/py/templates.py b/plugins/SilvanMTG/py/templates.py index ddd1ab0c..cb8acc8c 100644 --- a/plugins/SilvanMTG/py/templates.py +++ b/plugins/SilvanMTG/py/templates.py @@ -2,17 +2,14 @@ * SilvanMTG Templates """ -# Standard Library Imports from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.templates import MDFCMod, ExtendedMod, M15Template +from src.enums.layers import LAYERS +from src.templates import ExtendedMod, M15Template, MDFCMod """ * Template Classes diff --git a/src/cards.py b/src/cards.py index 92fb50fb..d8f7537a 100644 --- a/src/cards.py +++ b/src/cards.py @@ -38,6 +38,7 @@ class CardDetails(TypedDict): """Card details obtained from processing a card's art file name.""" + name: str set: str number: str @@ -49,6 +50,7 @@ class CardDetails(TypedDict): class FrameDetails(TypedDict): """Frame details obtained from processing frame logic.""" + background: str pinlines: str twins: str @@ -57,7 +59,7 @@ class FrameDetails(TypedDict): is_hybrid: bool -_filename_character_replacements: dict[str,str] = { +_filename_character_replacements: dict[str, str] = { "<": "<", ">": ">", ":": ":", @@ -66,7 +68,7 @@ class FrameDetails(TypedDict): "\": "\\", "│": "|", "?": "?", - "*": "*" + "*": "*", } """ Windows filenames disallow many characters which might be needed in some @@ -76,7 +78,7 @@ class FrameDetails(TypedDict): names into Proxyshop. """ -_reverse_filename_character_replacements: dict[str,str] = { +_reverse_filename_character_replacements: dict[str, str] = { value: key for key, value in _filename_character_replacements.items() } @@ -101,16 +103,20 @@ def get_card_data( """ # Format our query data - name, code = card.get('name', ''), card.get('set', '') - number = card.get('number', '').lstrip('0 ') if card.get('number') != '0' else '0' + name, code = card.get("name", ""), card.get("set", "") + number = card.get("number", "").lstrip("0 ") if card.get("number") != "0" else "0" # Establish kwarg search terms - kwargs = { - 'unique': cfg.scry_unique, - 'order': cfg.scry_sorting, - 'dir': 'asc' if cfg.scry_ascending else 'desc', - 'include_extras': str(cfg.scry_extras), - } if not number else {} + kwargs = ( + { + "unique": cfg.scry_unique, + "order": cfg.scry_sorting, + "dir": "asc" if cfg.scry_ascending else "desc", + "include_extras": str(cfg.scry_extras), + } + if not number + else {} + ) # Establish Scryfall fetch action action = get_card_unique if number else get_card_search @@ -118,14 +124,13 @@ def get_card_data( # Is this an alternate language request? if cfg.lang != "en": - # Pull the alternate language card try: return action(*params, lang=cfg.lang, **kwargs) except ScryfallException as exc: _logger.warning( f"Couldn't find language {cfg.lang} for {name}. Reverting to English.", - exc_info=exc + exc_info=exc, ) # Query the card in English, retry with extras if failed @@ -135,11 +140,13 @@ def get_card_data( if not number and not cfg.scry_extras: # Retry with extras included, case: Planar cards try: - kwargs['include_extras'] = 'True' + kwargs["include_extras"] = "True" data = action(*params, **kwargs) return data except ScryfallException: - _logger.exception("Couldn't retrieve card from Scryfall even with include_extras enabled.") + _logger.exception( + "Couldn't retrieve card from Scryfall even with include_extras enabled." + ) else: _logger.exception("Couldn't retrieve card from Scryfall.") @@ -169,17 +176,19 @@ def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDe artist = CardTextPatterns.PATH_ARTIST.search(file_name) number = CardTextPatterns.PATH_NUM.search(file_name) code = CardTextPatterns.PATH_SET.search(file_name) - kwargs_match: list[tuple[str,str]] = CardTextPatterns.PATH_KWARGS.findall(file_name) + kwargs_match: list[tuple[str, str]] = CardTextPatterns.PATH_KWARGS.findall( + file_name + ) # Return dictionary return { - 'file': file_path, - 'name': name_split[0].strip(), - 'set': code.group(1) if code else '', - 'artist': artist.group(1) if artist else '', - 'number': number.group(1) if number and code else '', - 'creator': name_split[-1] if '$' in file_name else '', - 'kwargs': {key: value for key, value in kwargs_match} + "file": file_path, + "name": name_split[0].strip(), + "set": code.group(1) if code else "", + "artist": artist.group(1) if artist else "", + "number": number.group(1) if number and code else "", + "creator": name_split[-1] if "$" in file_name else "", + "kwargs": {key: value for key, value in kwargs_match}, } @@ -199,26 +208,30 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: Processed scryfall data. """ # Define a normalized name - name_normalized = normalize_str(card['name'], no_space=True) + name_normalized = normalize_str(card["name"], no_space=True) # Modify meld card data to fit transform layout - if data.layout == 'meld': + if data.layout == "meld": # Ignore tokens and other objects front: list[ScryfallRelatedCard] = [] - back: ScryfallRelatedCard | None = None + back: ScryfallRelatedCard | None = None for part in data.all_parts if data.all_parts else []: - if part.component == 'meld_part': + if part.component == "meld_part": front.append(part) - if part.component == 'meld_result': + if part.component == "meld_result": back = part # Figure out if card is a front or a back is_back = back and name_normalized == normalize_str(back.name, no_space=True) - faces: list[ScryfallRelatedCard | None] = [front[0], back] if ( - is_back or - name_normalized == normalize_str(front[0].name, no_space=True) - ) else [front[1], back] + faces: list[ScryfallRelatedCard | None] = ( + [front[0], back] + if ( + is_back + or name_normalized == normalize_str(front[0].name, no_space=True) + ) + else [front[1], back] + ) try: if is_back and back: @@ -233,13 +246,17 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: face_data_dict = face_data.model_dump() face_data_dict["object"] = "card_face" data.card_faces.append(ScryfallCardFace(**face_data_dict)) - + # Add meld transform icon if none provided - if not data.frame_effects or not any([bool(n in TransformIcons) for n in data.frame_effects]): + if not data.frame_effects or not any( + [bool(n in TransformIcons) for n in data.frame_effects] + ): data.frame_effects = ["meld"] data.layout = "transform" except ScryfallException: - _logger.exception("Couldn't retrieve additional card details for a meld card.") + _logger.exception( + "Couldn't retrieve additional card details for a meld card." + ) raise # Check for alternate MDFC / Transform layouts @@ -258,14 +275,18 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: data.front = i == 0 # Transform / MDFC Planeswalker layout if card_face.type_line: - if 'Planeswalker' in card_face.type_line: - data.layout = 'planeswalker_tf' if data.layout == 'transform' else 'planeswalker_mdfc' + if "Planeswalker" in card_face.type_line: + data.layout = ( + "planeswalker_tf" + if data.layout == "transform" + else "planeswalker_mdfc" + ) # Transform Saga layout - elif 'Saga' in card_face.type_line: - data.layout = 'saga' + elif "Saga" in card_face.type_line: + data.layout = "saga" # Battle layout - elif 'Battle' in card_face.type_line: - data.layout = 'battle' + elif "Battle" in card_face.type_line: + data.layout = "battle" # Fix Adventure Land mana costs locally, as Scryfall seems unwilling to # fix the issue on their end even after reporting it and waiting for months. @@ -275,8 +296,8 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: main_face = card_faces[0] adventure_face = card_faces[1] if ( - main_face.type_line and - main_face.type_line.startswith("Land ") + main_face.type_line + and main_face.type_line.startswith("Land ") and main_face.mana_cost and not adventure_face.mana_cost ): @@ -289,25 +310,25 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: return data # Add Mutate layout - if 'Mutate' in data.keywords: - data.layout = 'mutate' + if "Mutate" in data.keywords: + data.layout = "mutate" return data - + type_line = data.type_line # Add Planeswalker layout - if 'Planeswalker' in type_line: - data.layout = 'planeswalker' + if "Planeswalker" in type_line: + data.layout = "planeswalker" return data - + # Check for Saga Creature layout - if 'Saga' in type_line and 'Creature' in type_line: - data.layout = 'saga' + if "Saga" in type_line and "Creature" in type_line: + data.layout = "saga" return data # Check for Station layout if data.keywords and "Station" in data.keywords: - data.layout = 'station' + data.layout = "station" return data # Return updated data @@ -328,7 +349,7 @@ def sanitize_card_filename(name: str) -> str: def locate_symbols( text: str, symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Logger | None = None + logger: Logger | None = None, ) -> tuple[str, list[CardSymbolString]]: """Locate symbols in the input string, replace them with the proper characters from the mana font, and determine the colors those characters need to be. @@ -343,27 +364,27 @@ def locate_symbols( of each symbol to format. """ # Is there a symbol in this text? - if '{' not in text: + if "{" not in text: return text, [] # Starting values symbol_indices: list[CardSymbolString] = [] - start, end = text.find('{'), text.find('}') + start, end = text.find("{"), text.find("}") # Look for symbols in the text while 0 <= start <= end: - symbol = text[start:end + 1] + symbol = text[start : end + 1] try: # Replace the symbol, add its location and color symbol_string, symbol_color = symbol_map[symbol] text = text.replace(symbol, symbol_string, 1) symbol_indices.append((start, symbol_color)) - except (KeyError, IndexError): + except KeyError, IndexError: if logger: - logger.warning(f'Symbol not recognized: {symbol}') - text = text.replace(symbol, symbol.strip('{}')) + logger.warning(f"Symbol not recognized: {symbol}") + text = text.replace(symbol, symbol.strip("{}")) # Move to the next symbols - start, end = text.find('{'), text.find('}') + start, end = text.find("{"), text.find("}") return text, symbol_indices @@ -371,7 +392,7 @@ def locate_italics( st: str, italics_strings: list[str], symbol_map: dict[str, tuple[str, list[ColorObject]]], - logger: Logger | None = None + logger: Logger | None = None, ) -> list[CardItalicString]: """Locate all instances of italic strings in the input string and record their start and end indices. @@ -384,24 +405,23 @@ def locate_italics( Returns: List of italic string indices (start and end). """ - indexes: list[tuple[int,int]] = [] + indexes: list[tuple[int, int]] = [] for italic in italics_strings: - # Look for symbols present in italicized text - if '{' in italic: - start = italic.find('{') - end = italic.find('}') + if "{" in italic: + start = italic.find("{") + end = italic.find("}") while 0 <= start < end: # Replace the symbol - symbol = italic[start:end + 1] + symbol = italic[start : end + 1] try: italic = italic.replace(symbol, symbol_map[symbol][0]) - except (KeyError, IndexError): + except KeyError, IndexError: if logger: - logger.warning(f'Symbol not recognized: {symbol}') - st = st.replace(symbol, symbol.strip('{}')) + logger.warning(f"Symbol not recognized: {symbol}") + st = st.replace(symbol, symbol.strip("{}")) # Move to the next symbol - start, end = italic.find('{'), italic.find('}') + start, end = italic.find("{"), italic.find("}") # Locate Italicized text end_index = 0 @@ -430,7 +450,6 @@ def generate_italics(card_text: str) -> list[str]: # Find each reminder text block end_index = 0 while True: - # Find parenthesis enclosed string, otherwise break start_index = card_text.find("(", end_index) if start_index < 0: @@ -441,13 +460,13 @@ def generate_italics(card_text: str) -> list[str]: italic_text.append(card_text[start_index:end_index]) # Determine whether to look for ability words - if ' — ' not in card_text: + if " — " not in card_text: return italic_text # Find and add ability words for match in CardTextPatterns.TEXT_ABILITY.findall(card_text): # Cover "villainous choice" case - if 'villainous' in match: + if "villainous" in match: continue # Cover "Mirrodin Besieged" case if f"• {match}" in card_text and "choose one" not in card_text.lower(): @@ -472,11 +491,11 @@ def strip_reminder_text(text: str) -> str: Oracle text with no reminder text. """ # Skip if there's no reminder text present - if '(' not in text: + if "(" not in text: return text # Remove reminder text text_stripped = CardTextPatterns.TEXT_REMINDER.sub("", text) # Remove any extra whitespace - return CardTextPatterns.EXTRA_SPACE.sub('', text_stripped).strip() + return CardTextPatterns.EXTRA_SPACE.sub("", text_stripped).strip() diff --git a/src/commands/__init__.py b/src/commands/__init__.py index a9d87be7..2ba5a0f3 100644 --- a/src/commands/__init__.py +++ b/src/commands/__init__.py @@ -1,16 +1,14 @@ """ * Headless CLI Application """ -# Standard Library Imports + import os import sys from pathlib import Path -# Third Party Imports import click -# Local Imports -from src import PATH +from src._state import PATH from src.commands.build import build_cli from src.commands.docs import docs_cli from src.commands.files import compress_cli @@ -22,27 +20,22 @@ """ -@click.command( - name='run_headless', - help='Launch the Proxyshop CLI application.') +@click.command(name="run_headless", help="Launch the Proxyshop CLI application.") def run_cli(): """Launch the Proxyshop CLI application.""" response = input("What would you like to do?\n") - if response == '1': - return print('You gave me 1.') - return print('You did not give me 1.') + if response == "1": + return print("You gave me 1.") + return print("You did not give me 1.") -@click.command( - name='run_gui', - help='Launch the Proxyshop GUI application.' -) +@click.command(name="run_gui", help="Launch the Proxyshop GUI application.") def run_gui(): """Launch the Proxyshop GUI application.""" exe_path = Path(sys.argv[0]) - if exe_path.suffix not in ['.py', '.exe']: - exe_path = PATH.CWD / 'main.py' - os.execv(sys.executable, ['python', exe_path]) + if exe_path.suffix not in [".py", ".exe"]: + exe_path = PATH.CWD / "main.py" + os.execv(sys.executable, ["python", exe_path]) """ @@ -52,18 +45,17 @@ def run_gui(): @click.group( commands={ - 'build': build_cli, - 'compress': compress_cli, - 'docs': docs_cli, - 'gui': run_gui, - 'render': render_cli, - 'test': test_cli, - }, - context_settings={ - 'ignore_unknown_options': True + "build": build_cli, + "compress": compress_cli, + "docs": docs_cli, + "gui": run_gui, + "render": render_cli, + "test": test_cli, }, + context_settings={"ignore_unknown_options": True}, invoke_without_command=True, - help='Invoke the CLI without a command to launch an ongoing headless Proxyshop application.') + help="Invoke the CLI without a command to launch an ongoing headless Proxyshop application.", +) @click.pass_context def ProxyshopCLI(ctx: click.Context): if ctx.invoked_subcommand is None: @@ -72,4 +64,4 @@ def ProxyshopCLI(ctx: click.Context): # Export CLI -__all__ = ['ProxyshopCLI'] +__all__ = ["ProxyshopCLI"] diff --git a/src/commands/build.py b/src/commands/build.py index 2ea8dc37..c63c2136 100644 --- a/src/commands/build.py +++ b/src/commands/build.py @@ -2,24 +2,40 @@ * CLI Commands: Build """ -# Third party imports import click -# Local Imports from src.utils.build import build_release - """ * Commands """ -@click.command(help='Build executable app release and distributable zip.') -@click.argument('version', required=False) -@click.option('-B', '--beta', is_flag=True, default=False, help="Build app as a Beta release.") -@click.option('-C', '--console', is_flag=True, default=False, help="Build app with console enabled.") -@click.option('-R', '--raw', is_flag=True, default=False, help="Build app without creating zip release archive.") -def build_app(version: str | None = None, beta: bool = False, console: bool = False, raw: bool = False) -> None: +@click.command(help="Build executable app release and distributable zip.") +@click.argument("version", required=False) +@click.option( + "-B", "--beta", is_flag=True, default=False, help="Build app as a Beta release." +) +@click.option( + "-C", + "--console", + is_flag=True, + default=False, + help="Build app with console enabled.", +) +@click.option( + "-R", + "--raw", + is_flag=True, + default=False, + help="Build app without creating zip release archive.", +) +def build_app( + version: str | None = None, + beta: bool = False, + console: bool = False, + raw: bool = False, +) -> None: """Build Proxyshop as an executable release. Args: @@ -37,8 +53,8 @@ def build_app(version: str | None = None, beta: bool = False, console: bool = Fa @click.group( - name='build', - help='Command utilities for building and managing release files.', + name="build", + help="Command utilities for building and managing release files.", commands={"app": build_app}, ) def build_cli() -> None: @@ -47,4 +63,4 @@ def build_cli() -> None: # Export CLI -__all__ = ['build_cli'] +__all__ = ["build_cli"] diff --git a/src/commands/docs.py b/src/commands/docs.py index 608454c1..4e114f1d 100644 --- a/src/commands/docs.py +++ b/src/commands/docs.py @@ -1,10 +1,9 @@ """ * CLI Commands: Build """ -# Third party imports + import click -# Local Imports from src.utils.build import generate_mkdocs, generate_nav, update_mkdocs_yml """ @@ -13,8 +12,7 @@ @click.group( - name="docs", - help="Command utilities for managing the app's documentation." + name="docs", help="Command utilities for managing the app's documentation." ) def docs_cli() -> None: """App docs tools CLI.""" @@ -27,17 +25,16 @@ def docs_cli() -> None: @docs_cli.command( - name="update", - help="Updates MKDocs files for the current app version." + name="update", help="Updates MKDocs files for the current app version." ) def generate_docs() -> None: """Build the docs.""" - headers = ['Template Classes', 'Photoshop Helpers', 'App Utilities'] - paths = ['templates', 'helpers', 'utils'] + headers = ["Template Classes", "Photoshop Helpers", "App Utilities"] + paths = ["templates", "helpers", "utils"] [generate_mkdocs(p) for p in paths] nav = generate_nav(headers, paths) update_mkdocs_yml(nav) # Export CLI -__all__ = ['docs_cli'] +__all__ = ["docs_cli"] diff --git a/src/commands/files.py b/src/commands/files.py index b897c7e2..57b0aea9 100644 --- a/src/commands/files.py +++ b/src/commands/files.py @@ -1,36 +1,30 @@ """ * CLI Commands: Files """ -# Standard Library + from pathlib import Path -# Third Party Imports import click from omnitils.files.archive import compress_7z, compress_7z_all -# Local Imports -from src import PATH +from src._state import PATH """ * Commands: Compression """ -@click.group( - name='compress', - help='Command utilities for compressing files.' -) +@click.group(name="compress", help="Command utilities for compressing files.") def compress_cli(): """File utilities CLI.""" pass @compress_cli.command( - name='template', - help='Compress a Photoshop template file (PSD/PSB).' + name="template", help="Compress a Photoshop template file (PSD/PSB)." ) -@click.argument('template') -@click.argument('plugin', required=False) +@click.argument("template") +@click.argument("plugin", required=False) def compress_template(template: str, plugin: str | None = None) -> None: """Compress a template by name and optionally plugin name. @@ -38,26 +32,28 @@ def compress_template(template: str, plugin: str | None = None) -> None: template: Filename of the template, e.g. `normal.psd` plugin: Name of the plugin containing the template if required, e.g. MrTeferi """ - path = Path(PATH.PLUGINS, plugin, 'templates') if plugin else PATH.TEMPLATES + path = Path(PATH.PLUGINS, plugin, "templates") if plugin else PATH.TEMPLATES path = path / template if not path.is_file(): - print(f"I couldn't find a template named '{template}' at this path:\n{str(path)}") + print( + f"I couldn't find a template named '{template}' at this path:\n{str(path)}" + ) return compress_7z(path) @compress_cli.command( - name='plugin', - help='Compress all Photoshop template files (PSD/PSB) in a given plugin.' + name="plugin", + help="Compress all Photoshop template files (PSD/PSB) in a given plugin.", ) -@click.argument('plugin') +@click.argument("plugin") def compress_plugin(plugin: str) -> None: """Compress all templates in a specific plugin. Args: plugin: Name of the plugin, e.g. MrTeferi """ - path = PATH.PLUGINS / plugin / 'templates' + path = PATH.PLUGINS / plugin / "templates" if not path.is_dir(): print(f"I couldn't find a plugin named '{plugin}'") return @@ -65,10 +61,16 @@ def compress_plugin(plugin: str) -> None: @compress_cli.command( - name='all', - help='Compress all Photoshop template files (PSD/PSB) in the entire app, plugins optional.' + name="all", + help="Compress all Photoshop template files (PSD/PSB) in the entire app, plugins optional.", +) +@click.option( + "-P", + "--plugins", + is_flag=True, + default=False, + help="Compress built-in plugins as well.", ) -@click.option('-P', '--plugins', is_flag=True, default=False, help="Compress built-in plugins as well.") def compress_all(plugins: bool = False) -> None: """Compress all templates. @@ -81,10 +83,10 @@ def compress_all(plugins: bool = False) -> None: # Compress plugins if requested if plugins: plugins = [ - Path(PATH.PLUGINS, p, 'templates') - for p in ['Investigamer', 'SilvanMTG']] + Path(PATH.PLUGINS, p, "templates") for p in ["Investigamer", "SilvanMTG"] + ] [compress_7z_all(p) for p in plugins] # Export CLI -__all__ = ['compress_cli'] +__all__ = ["compress_cli"] diff --git a/src/commands/render.py b/src/commands/render.py index 81c5b6df..f7a02005 100644 --- a/src/commands/render.py +++ b/src/commands/render.py @@ -1,14 +1,12 @@ """ * CLI Commands: Rendering """ -# Standard Library Imports + from pathlib import Path -# Third Party Imports import click from omnitils.files import load_data_file -# Local Imports from src import CON, TEMPLATE_DEFAULTS from src._loader import TemplateDetails from src.cards import CardDetails, parse_card_info @@ -21,27 +19,21 @@ """ -@click.group( - name='render', - help='Commands for rendering card images.' -) +@click.group(name="render", help="Commands for rendering card images.") def render_cli(): """App render CLI.""" pass -@render_cli.command( - name='target', - help='Render one or more target cards.' -) -@click.argument('data_file', required=True) +@render_cli.command(name="target", help="Render one or more target cards.") +@click.argument("data_file", required=True) def render_target(data_file: str = None): """Render a single card using JSON data.""" # Find your art image file # Todo: Use target selection in Photoshop, support optional filepath argument - art_path = Path(CON.cwd, 'art', data_file) - for suf in ['.jpg', '.png', '.webp']: + art_path = Path(CON.cwd, "art", data_file) + for suf in [".jpg", ".png", ".webp"]: art_file = art_path.with_suffix(suf) if art_file.is_file(): break @@ -52,24 +44,24 @@ def render_target(data_file: str = None): # Load card data from a json file and create fake card "details" dict # Todo: Make custom card data an optional filepath argument - data_file = Path(CON.cwd, 'customs', data_file).with_suffix('.json') + data_file = Path(CON.cwd, "customs", data_file).with_suffix(".json") card = load_data_file(data_file) file_details: CardDetails = parse_card_info(art_path) # Get appropriate layout class and initialize it # Todo: Use the appropriate layout class provided - layout = layout_map.get(card.get('layout', 'normal')) + layout = layout_map.get(card.get("layout", "normal")) layout(card, file_details) # Get appropriate template for this layout # Todo: Use default, support optional template name argument or custom defined template: TemplateDetails = TEMPLATE_DEFAULTS.get(layout.type) - template_class = template['object'].get_template_class(template['class_name']) - layout.template_file = template['object'].path_psd + template_class = template["object"].get_template_class(template["class_name"]) + layout.template_file = template["object"].path_psd # Load the template class, create an instance, execute it template_class(layout).execute() # Export CLI -__all__ = ['render_cli'] +__all__ = ["render_cli"] diff --git a/src/commands/test/__init__.py b/src/commands/test/__init__.py index 7743c5c1..1fd8a60e 100644 --- a/src/commands/test/__init__.py +++ b/src/commands/test/__init__.py @@ -17,10 +17,13 @@ @click.command( - short_help='Test Scryfall data frame logic analysis across a variety of card cases.', - help='Test Scryfall data frame logic analysis across a variety of card cases. Frame analysis ' - 'is used to determine what colors and textures should be used on a given card.') -@click.option('-T', '--target', is_flag=True, default=False, help="Evaluate a specific test case.") + short_help="Test Scryfall data frame logic analysis across a variety of card cases.", + help="Test Scryfall data frame logic analysis across a variety of card cases. Frame analysis " + "is used to determine what colors and textures should be used on a given card.", +) +@click.option( + "-T", "--target", is_flag=True, default=False, help="Evaluate a specific test case." +) def test_frame_logic(target: bool = False): """Run Frame Logic test on all cases.""" _logger.info(f"Test Utility: Frame Logic ({PATH.CWD})") @@ -33,8 +36,10 @@ def test_frame_logic(target: bool = False): # Choose test case options = {str(i): title for i, title in enumerate(cases.keys())} while True: - choice = input("Enter the number for a test case to evaluate:\n" + - "\n".join([f"[{i}] {n}" for i, n in options.items()])) + choice = input( + "Enter the number for a test case to evaluate:\n" + + "\n".join([f"[{i}] {n}" for i, n in options.items()]) + ) if choice not in options: print("Choice provided was invalid.") break @@ -46,9 +51,10 @@ def test_frame_logic(target: bool = False): @click.command( - short_help='Test Scryfall data text logic analysis across a variety of card cases.', - help='Test Scryfall data text logic analysis across a variety of card cases. Text analysis is used to ' - 'decide what text should be italicized when rendering the card.') + short_help="Test Scryfall data text logic analysis across a variety of card cases.", + help="Test Scryfall data text logic analysis across a variety of card cases. Text analysis is used to " + "decide what text should be italicized when rendering the card.", +) def test_text_logic(): """Run Text Logic test on all cases.""" text_logic.test_all_cases() @@ -60,12 +66,10 @@ def test_text_logic(): @click.group( - name='test', chain=True, - help='Commands that test app functionality.', - commands={ - 'logic.frame': test_frame_logic, - 'logic.text': test_text_logic - } + name="test", + chain=True, + help="Commands that test app functionality.", + commands={"logic.frame": test_frame_logic, "logic.text": test_text_logic}, ) def test_cli(): """Cli interface for test funcs.""" @@ -73,4 +77,4 @@ def test_cli(): # Export CLI -__all__ = ['test_cli'] +__all__ = ["test_cli"] diff --git a/src/commands/test/compression.py b/src/commands/test/compression.py index 621702ea..fddbed7c 100644 --- a/src/commands/test/compression.py +++ b/src/commands/test/compression.py @@ -36,7 +36,7 @@ def test_7z_compression(path: Path) -> dict: WordSize.WS48, WordSize.WS64, WordSize.WS96, - WordSize.WS128 + WordSize.WS128, ] dict_sizes = [ DictionarySize.DS32, @@ -62,14 +62,14 @@ def test_7z_compression(path: Path) -> dict: s = perf_counter() path_out = compress_7z(path, word_size=ws, dict_size=ds) size = path_out.stat().st_size - time = round(perf_counter()-s, 3) + time = round(perf_counter() - s, 3) x.append(time) y.append(size) z.append(f"{ws}/{ds}") print(f"Word: {ws}, Dict: {ds}, T({time})—MB({size}) [{current}/{total}]") # Format a 600 DPI plot at 12" x 8" - plt.rcParams['lines.markersize'] = 5 + plt.rcParams["lines.markersize"] = 5 fig, ax = plt.subplots() fig.set_dpi(600) fig.set_size_inches(12, 8) @@ -89,15 +89,15 @@ def test_7z_compression(path: Path) -> dict: # Show the plot and return raw data plt.show() - return {name: {'time': x[i], 'size': y[i]} for i, name in enumerate(z)} + return {name: {"time": x[i], "size": y[i]} for i, name in enumerate(z)} def test_jpeg_compression( - path: Path, - test_dpi: bool =True, - test_resample: bool =True, - test_optimize: bool =True, - test_quality: list[int] | None = None + path: Path, + test_dpi: bool = True, + test_resample: bool = True, + test_optimize: bool = True, + test_quality: list[int] | None = None, ) -> None: """ Test a battery of JPEG compression settings. @@ -109,7 +109,11 @@ def test_jpeg_compression( """ # Settings to test - RESAMPLE = [Resampling.LANCZOS, Resampling.BICUBIC] if test_resample else [Resampling.LANCZOS] + RESAMPLE = ( + [Resampling.LANCZOS, Resampling.BICUBIC] + if test_resample + else [Resampling.LANCZOS] + ) OPTIMIZE = [True, False] if test_optimize else [True] WIDTH = [3264, 2176] if test_dpi else [3264] QUALITY, i = test_quality or [95, 90, 85, 80], 0 @@ -120,21 +124,22 @@ def test_jpeg_compression( for _R in RESAMPLE: for _Q in QUALITY: for _O in OPTIMIZE: - # Downscale the image s = perf_counter() - CURRENT = (f"{i}. {'800' if _W < 3264 else '1200'} " - f"{'lanczos' if _R == Resampling.LANCZOS else 'bicubic'} " - f"{_Q} {'optimize_YES' if _O else 'optimize_NO'}") + CURRENT = ( + f"{i}. {'800' if _W < 3264 else '1200'} " + f"{'lanczos' if _R == Resampling.LANCZOS else 'bicubic'} " + f"{_Q} {'optimize_YES' if _O else 'optimize_NO'}" + ) _logger.info(f"TESTING: {CURRENT}") - SAVE_TO = path.parent / 'compressed' / f'{CURRENT}.jpg' + SAVE_TO = path.parent / "compressed" / f"{CURRENT}.jpg" downscale_image_by_width( path_img=path, path_save=SAVE_TO, max_width=_W, optimize=_O, quality=_Q, - resample=_R + resample=_R, ) # Print the time of execution diff --git a/src/commands/test/frame_logic.py b/src/commands/test/frame_logic.py index 69a9c589..b4fe037d 100644 --- a/src/commands/test/frame_logic.py +++ b/src/commands/test/frame_logic.py @@ -2,6 +2,7 @@ * Tests: Frame Logic * Credit to Chilli: https://tinyurl.com/chilli-frame-logic-tests """ + from concurrent.futures import Future, as_completed from concurrent.futures import ThreadPoolExecutor as Pool from logging import getLogger @@ -34,7 +35,7 @@ def get_frame_logic_cases() -> dict[str, dict[str, FrameData]]: """Return frame logic test cases from TOML data file.""" - return load_data_file(Path(PATH.SRC_DATA_TESTS, 'frame_data.toml')) + return load_data_file(Path(PATH.SRC_DATA_TESTS, "frame_data.toml")) def format_result(layout: CardLayout) -> FrameData: @@ -52,7 +53,7 @@ def format_result(layout: CardLayout) -> FrameData: str(layout.pinlines), str(layout.twins), str(layout.is_nyx), - str(layout.is_colorless) + str(layout.is_colorless), ] @@ -61,7 +62,9 @@ def format_result(layout: CardLayout) -> FrameData: """ -def test_case(card_name: str, card_data: FrameData) -> tuple[str, FrameData, FrameData] | None: +def test_case( + card_name: str, card_data: FrameData +) -> tuple[str, FrameData, FrameData] | None: """Test frame logic for a target test case. Args: @@ -74,46 +77,56 @@ def test_case(card_name: str, card_data: FrameData) -> tuple[str, FrameData, Fra try: # Check if a set code was provided set_code = None - if all([n in card_name for n in ['[', ']']]): - if set_match := CardTextPatterns.PATH_SET.search(card_name): + if all([n in card_name for n in ["[", "]"]]): + if set_match := CardTextPatterns.PATH_SET.search(card_name): set_code = set_match.group(1) - card_name = card_name.replace(f'[{set_code}]', '').strip() + card_name = card_name.replace(f"[{set_code}]", "").strip() # Create a fake card details object details: CardDetails = { - 'name': card_name, - 'set': set_code or "", - 'number': '', - 'creator': '', - 'file': Path(), - 'artist': '' + "name": card_name, + "set": set_code or "", + "number": "", + "creator": "", + "file": Path(), + "artist": "", + "kwargs": {}, } # Pull Scryfall data - scryfall = get_card_data( - card=details, - cfg=CFG) + scryfall = get_card_data(card=details, cfg=CFG) if not scryfall: - raise OSError('Did not return valid data from Scryfall.') + raise OSError("Did not return valid data from Scryfall.") # Process the Scryfall data scryfall = process_card_data(scryfall, details) except Exception as e: # Exception occurred during Scryfall lookup - return _logger.error(f"Scryfall error occurred at card: '{card_name}'", exc_info=e) + return _logger.error( + f"Scryfall error occurred at card: '{card_name}'", exc_info=e + ) # Pull layout data for the card try: result_data: FrameData = format_result( - layout_map[scryfall['layout']]( + layout_map[scryfall.layout]( scryfall=scryfall, file={ - 'name': card_name, - 'artist': scryfall['artist'], - 'set_code': scryfall['set'], - 'creator': '', 'filename': ''})) + "name": card_name, + "artist": scryfall.artist or "", + "set": scryfall.set, + "number": scryfall.collector_number, + "creator": "", + "file": Path(), + "kwargs": {}, + }, + config=CFG, + ) + ) except Exception as e: # Exception occurred during layout generation - return _logger.error(f"Layout error occurred at card: '{card_name}'", exc_info=e) + return _logger.error( + f"Layout error occurred at card: '{card_name}'", exc_info=e + ) # Compare the results if not result_data == card_data: @@ -137,15 +150,13 @@ def test_target_case(cards: dict[str, FrameData]) -> None: # Submit tasks to executor for card_name, data in cards.items(): - tests_submitted.append( - executor.submit(test_case, card_name, data)) + tests_submitted.append(executor.submit(test_case, card_name, data)) # Create a progress bar pbar = tqdm( total=len(tests_submitted), - bar_format=f'{LogColors.BLUE}' - '{l_bar}{bar}{r_bar}' - f'{LogColors.RESET}') + bar_format=f"{LogColors.BLUE}{{l_bar}}{{bar}}{{r_bar}}{LogColors.RESET}", + ) # Iterate over completed tasks, update progress bar, add failed tasks for task in as_completed(tests_submitted): @@ -155,13 +166,13 @@ def test_target_case(cards: dict[str, FrameData]) -> None: # Set the progress bar result if tests_failed: - pbar.set_postfix({ - "Status": ( - Fore.RED + "FAILED" + Style.RESET_ALL - ) if tests_failed else ( - Fore.GREEN + "SUCCESS" + Style.RESET_ALL - ) - }) + pbar.set_postfix( + { + "Status": (Fore.RED + "FAILED" + Style.RESET_ALL) + if tests_failed + else (Fore.GREEN + "SUCCESS" + Style.RESET_ALL) + } + ) # Close progress bar and return failures pbar.close() @@ -170,9 +181,11 @@ def test_target_case(cards: dict[str, FrameData]) -> None: if tests_failed: _logger.error("=" * 40) for name, actual, correct in tests_failed: - _logger.warning(f'NAME: {name}') - _logger.warning(f'RESULT [Actual / Expected]:\n' - f'{LogColors.RESET}{LogColors.WHITE}{actual}\n{correct}') + _logger.warning(f"NAME: {name}") + _logger.warning( + f"RESULT [Actual / Expected]:\n" + f"{LogColors.RESET}{LogColors.WHITE}{actual}\n{correct}" + ) _logger.error("=" * 40) _logger.error("SOME TESTS FAILED!") return diff --git a/src/commands/test/scryfall.py b/src/commands/test/scryfall.py index 8b9cace3..e179cfb1 100644 --- a/src/commands/test/scryfall.py +++ b/src/commands/test/scryfall.py @@ -1,9 +1,8 @@ """ * Tests: Scryfall """ -# Local Imports -from src.api.scryfall import get_card_unique, get_card_search, get_set +from src.utils.scryfall import get_card_search, get_card_unique, get_set """ * Test Funcs @@ -13,8 +12,8 @@ def test_scryfall_unique(): """Test the request function for Scryfall's '/cards/set/num' endpoint.""" try: - obj = get_card_unique(card_set='TSR', card_number='50', lang='en') - print("SUCCESS:", obj['name'], obj['set'], obj['collector_number']) + obj = get_card_unique(card_set="TSR", card_number="50", lang="en") + print("SUCCESS:", obj["name"], obj["set"], obj["collector_number"]) return obj except Exception as e: print(e) @@ -24,8 +23,8 @@ def test_scryfall_unique(): def test_scryfall_search(): """Test the request function for Scryfall's '/cards/search' endpoint.""" try: - obj = get_card_search(card_name='Damnation', card_set='TSR', lang='en') - print("SUCCESS:", obj['name'], obj['set'], obj['collector_number']) + obj = get_card_search(card_name="Damnation", card_set="TSR", lang="en") + print("SUCCESS:", obj["name"], obj["set"], obj["collector_number"]) return obj except Exception as e: print(e) @@ -35,8 +34,8 @@ def test_scryfall_search(): def test_scryfall_set(): """Test the request function for Scryfall's '/sets/code' endpoint.""" try: - obj = get_set(card_set='TSR') - print("SUCCESS:", obj['name'], obj['code']) + obj = get_set(card_set="TSR") + print("SUCCESS:", obj["name"], obj["code"]) return obj except Exception as e: print(e) diff --git a/src/commands/test/text_logic.py b/src/commands/test/text_logic.py index 84320a51..f3fbff49 100644 --- a/src/commands/test/text_logic.py +++ b/src/commands/test/text_logic.py @@ -1,6 +1,7 @@ """ * Tests: Card Text Logic """ + from logging import getLogger from pathlib import Path from typing import TypedDict @@ -17,8 +18,9 @@ """ -class TestCaseTextItalic (TypedDict): +class TestCaseTextItalic(TypedDict): """Test cases for validating the generate italics function.""" + result: list[str] scenario: str text: str @@ -34,19 +36,25 @@ def test_all_cases() -> bool: # Load our test cases success = True - test_file: Path = Path(PATH.SRC_DATA_TESTS, 'text_italic.toml') + test_file: Path = Path(PATH.SRC_DATA_TESTS, "text_italic.toml") test_cases: dict[str, TestCaseTextItalic] = load_data_file(test_file) _logger.info(f"Testing > Card Text Logic ({test_file.name})") # Check each test case for success for name, case in test_cases.items(): - # Compare actual test results VS expected test results - result_actual, result_expected = generate_italics(case.get('text', '')), case.get('result', []) + result_actual, result_expected = ( + generate_italics(case.get("text", "")), + case.get("result", []), + ) if not sorted(result_actual) == sorted(result_expected): success = False - msg_actual = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_actual, start=1)) - msg_expected = ''.join(f'\n {i}. {n}' for i, n in enumerate(result_expected, start=1)) + msg_actual = "".join( + f"\n {i}. {n}" for i, n in enumerate(result_actual, start=1) + ) + msg_expected = "".join( + f"\n {i}. {n}" for i, n in enumerate(result_expected, start=1) + ) _logger.error(f"Case: {name} ({case.get('scenario', '')})") # Log what we expect @@ -63,5 +71,5 @@ def test_all_cases() -> bool: # Did any tests fail? if success: - _logger.info('All tests successful!') + _logger.info("All tests successful!") return success diff --git a/src/commands/test/utility.py b/src/commands/test/utility.py index 574a7ff9..e348d112 100644 --- a/src/commands/test/utility.py +++ b/src/commands/test/utility.py @@ -3,39 +3,28 @@ * For contributors and plugin development. """ -# Standard Library Imports -from contextlib import suppress +import csv +import json +import logging +import warnings +import xml.etree.ElementTree as ET from _ctypes import COMError +from contextlib import suppress from xml.dom import minidom -import warnings -import logging -import json -import csv -# Third Party Imports +from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -from photoshop.api import ( - ActionDescriptor, - ActionReference, - ElementPlacement, - DialogModes, - LayerKind, -) -from psd_tools.constants import Resource +from photoshop.api.enumerations import DialogModes, ElementPlacement, LayerKind from psd_tools import PSDImage +from psd_tools.constants import Resource from psd_tools.psd.image_resources import ImageResource -import xml.etree.ElementTree as ET -# Local Imports -from src import APP, TEMPLATES import src.helpers as psd +from src import APP, TEMPLATES from src.schema.colors import ColorObject from src.utils.adobe import LayerContainer -# Photoshop infrastructure -NO_DIALOG = DialogModes.DisplayNoDialogs - # Reference Box colors ORANGE = [255, 172, 64] TANG = [255, 97, 11] @@ -207,7 +196,7 @@ def try_all_getters(desc: ActionDescriptor, type_id) -> dict: # Getter may have returned an ActionList, grab the first object result = get_action_items(result.getObjectValue(0)) values[k] = result - except (COMError, NameError, KeyError): + except COMError, NameError, KeyError: # Skip this getter pass return values @@ -313,7 +302,9 @@ def apply_single_line_composer(layer: ArtLayer) -> None: desc2.putInteger(APP.instance.sID("textOverrideFeatureName"), 808464691) desc2.putBoolean(APP.instance.sID("textEveryLineComposer"), False) desc1.putObject(APP.instance.sID("to"), APP.instance.sID("paragraphStyle"), desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str | None = " "): @@ -447,7 +438,9 @@ def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: desc1.putUnitDouble( APP.instance.sID("tolerance"), APP.instance.sID("pixelsUnit"), 2.000000 ) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) ref1 = ActionReference() desc1 = ActionDescriptor() @@ -464,7 +457,9 @@ def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: APP.instance.sID("type"), APP.instance.sID("solidColorLayer"), desc3 ) desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) APP.instance.activeDocument.activeLayer.name = layer_name # Check dims @@ -732,7 +727,9 @@ def import_data_set(path: str) -> None: ) desc.putBoolean(APP.instance.sID("eraseAll"), True) desc.putBoolean(APP.instance.sID("useFirstColumn"), True) - APP.instance.executeAction(APP.instance.sID("importDataSets"), desc, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("importDataSets"), desc, DialogModes.DisplayNoDialogs + ) def apply_data_set(data_set_name: str) -> None: @@ -745,7 +742,9 @@ def apply_data_set(data_set_name: str) -> None: setRef = ActionReference() setRef.putName(APP.instance.sID("dataSetClass"), data_set_name) desc.putReference(APP.instance.sID("null"), setRef) - APP.instance.executeAction(APP.instance.sID("apply"), desc, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("apply"), desc, DialogModes.DisplayNoDialogs + ) """ diff --git a/src/enums/layers.py b/src/enums/layers.py index 0e2b572f..21a53b3c 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -9,252 +9,252 @@ """ -class LAYERS (StrEnum): +class LAYERS(StrEnum): """Layer name definitions.""" # Default art layer - DEFAULT = 'Layer 1' + DEFAULT = "Layer 1" # Colors - WHITE = 'W' - BLUE = 'U' - BLACK = 'B' - RED = 'R' - GREEN = 'G' - WU = 'WU' - UB = 'UB' - BR = 'BR' - RG = 'RG' - GW = 'GW' - WB = 'WB' - BG = 'BG' - GU = 'GU' - UR = 'UR' - RW = 'RW' - GWU = 'GWU' - WUB = 'WUB' - UBR = 'UBR' - BRG = 'BRG' - RGW = 'RGW' - WBG = 'WBG' - URW = 'URW' - BGU = 'BGU' - RWB = 'RWB' - GUR = 'GUR' - WUBR = 'WUBR' - UBRG = 'UBRG' - BRGW = 'BRGW' - RGWU = 'RGWU' - GWUB = 'GWUB' - WUBRG = 'WUBRG' - ARTIFACT = 'Artifact' - COLORLESS = 'Colorless' - NONLAND = 'Nonland' - LAND = 'Land' - GOLD = 'Gold' - VEHICLE = 'Vehicle' - HYBRID = 'Hybrid' + WHITE = "W" + BLUE = "U" + BLACK = "B" + RED = "R" + GREEN = "G" + WU = "WU" + UB = "UB" + BR = "BR" + RG = "RG" + GW = "GW" + WB = "WB" + BG = "BG" + GU = "GU" + UR = "UR" + RW = "RW" + GWU = "GWU" + WUB = "WUB" + UBR = "UBR" + BRG = "BRG" + RGW = "RGW" + WBG = "WBG" + URW = "URW" + BGU = "BGU" + RWB = "RWB" + GUR = "GUR" + WUBR = "WUBR" + UBRG = "UBRG" + BRGW = "BRGW" + RGWU = "RGWU" + GWUB = "GWUB" + WUBRG = "WUBRG" + ARTIFACT = "Artifact" + COLORLESS = "Colorless" + NONLAND = "Nonland" + LAND = "Land" + GOLD = "Gold" + VEHICLE = "Vehicle" + HYBRID = "Hybrid" # Frame layer group names - PT_BOX = 'PT Box' - PT_AND_LEVEL_BOXES = 'PT and Level Boxes' - TWINS = 'Name & Title Boxes' - LEGENDARY_CROWN = 'Legendary Crown' - PINLINES_TEXTBOX = 'Pinlines & Textbox' - PINLINES = 'Pinlines' - LAND_PINLINES_TEXTBOX = 'Land Pinlines & Textbox' - COMPANION = 'Companion' - BACKGROUND = 'Background' - NYX = 'Nyx' - FRAME = 'Frame' - LEGENDARY = 'Legendary' - NON_LEGENDARY = 'Non-Legendary' - CREATURE = 'Creature' - NON_CREATURE = 'Non-Creature' - NONE = 'None' - ONE_LINE = 'One Line' - FULL = 'Full' - NORMAL = 'Normal' - SNOW = 'Snow' - LEVEL = 'Level' + PT_BOX = "PT Box" + PT_AND_LEVEL_BOXES = "PT and Level Boxes" + TWINS = "Name & Title Boxes" + LEGENDARY_CROWN = "Legendary Crown" + PINLINES_TEXTBOX = "Pinlines & Textbox" + PINLINES = "Pinlines" + LAND_PINLINES_TEXTBOX = "Land Pinlines & Textbox" + COMPANION = "Companion" + BACKGROUND = "Background" + NYX = "Nyx" + FRAME = "Frame" + LEGENDARY = "Legendary" + NON_LEGENDARY = "Non-Legendary" + CREATURE = "Creature" + NON_CREATURE = "Non-Creature" + NONE = "None" + ONE_LINE = "One Line" + FULL = "Full" + NORMAL = "Normal" + SNOW = "Snow" + LEVEL = "Level" # Borders - BORDER = 'Border' - OUTLINE = 'Outline' - NORMAL_BORDER = 'Normal Border' - LEGENDARY_BORDER = 'Legendary Border' + BORDER = "Border" + OUTLINE = "Outline" + NORMAL_BORDER = "Normal Border" + LEGENDARY_BORDER = "Legendary Border" # Shadows - SHADOWS = 'Shadows' - HOLLOW_CROWN_SHADOW = 'Hollow Crown Shadow' + SHADOWS = "Shadows" + HOLLOW_CROWN_SHADOW = "Hollow Crown Shadow" # Vectors - SHAPE = 'Shape' - TEXTLESS = 'Textless' + SHAPE = "Shape" + TEXTLESS = "Textless" # Effects - EFFECTS = 'Effects' - LIGHTEN = 'Lighten' - DARKEN = 'Darken' - CONTRAST = 'Contrast' + EFFECTS = "Effects" + LIGHTEN = "Lighten" + DARKEN = "Darken" + CONTRAST = "Contrast" # Masks - MASKS = 'Masks' - HALF = '1/2' - THIRD = '1/3' - TWO_THIRDS = '2/3' - QUARTER = '1/4' - THREE_QUARTERS = '3/4' - FIFTH = '1/5' - TWO_FIFTHS = '2/5' - THREE_FIFTHS = '3/5' - FOUR_FIFTHS = '4/5' + MASKS = "Masks" + HALF = "1/2" + THIRD = "1/3" + TWO_THIRDS = "2/3" + QUARTER = "1/4" + THREE_QUARTERS = "3/4" + FIFTH = "1/5" + TWO_FIFTHS = "2/5" + THREE_FIFTHS = "3/5" + FOUR_FIFTHS = "4/5" # Legal / Collector Info - LEGAL = 'Legal' - ARTIST = 'Artist' - SET = 'Set' - COLLECTOR = 'Collector' - NFS = 'NFS' - NFS_CREATURE = 'NFS - Creature' + LEGAL = "Legal" + ARTIST = "Artist" + SET = "Set" + COLLECTOR = "Collector" + NFS = "NFS" + NFS_CREATURE = "NFS - Creature" # Text and Icons - TEXT_AND_ICONS = 'Text and Icons' - NAME = 'Card Name' - NAME_SHIFT = 'Card Name Shift' - NICKNAME = 'Nickname' - TYPE_LINE = 'Typeline' - TYPE_LINE_SHIFT = 'Typeline Shift' - MANA_COST = 'Mana Cost' - EXPANSION_SYMBOL = 'Expansion Symbol' - COLOR_INDICATOR = 'Color Indicator' - POWER_TOUGHNESS = 'Power / Toughness' - FLIPSIDE_POWER_TOUGHNESS = 'Flipside Power / Toughness' - RULES_TEXT = 'Rules Text' - RULES_TEXT_FLIP = 'Rules Text - Flip' - RULES_TEXT_NONCREATURE = 'Rules Text - Noncreature' - RULES_TEXT_NONCREATURE_FLIP = 'Rules Text - Noncreature Flip' - RULES_TEXT_CREATURE = 'Rules Text - Creature' - RULES_TEXT_CREATURE_FLIP = 'Rules Text - Creature Flip' - REMINDER_TEXT = 'Reminder Text' - MUTATE = 'Mutate' - DIVIDER = 'Divider' - CREATOR = 'Creator' + TEXT_AND_ICONS = "Text and Icons" + NAME = "Card Name" + NAME_SHIFT = "Card Name Shift" + NICKNAME = "Nickname" + TYPE_LINE = "Typeline" + TYPE_LINE_SHIFT = "Typeline Shift" + MANA_COST = "Mana Cost" + EXPANSION_SYMBOL = "Expansion Symbol" + COLOR_INDICATOR = "Color Indicator" + POWER_TOUGHNESS = "Power / Toughness" + FLIPSIDE_POWER_TOUGHNESS = "Flipside Power / Toughness" + RULES_TEXT = "Rules Text" + RULES_TEXT_FLIP = "Rules Text - Flip" + RULES_TEXT_NONCREATURE = "Rules Text - Noncreature" + RULES_TEXT_NONCREATURE_FLIP = "Rules Text - Noncreature Flip" + RULES_TEXT_CREATURE = "Rules Text - Creature" + RULES_TEXT_CREATURE_FLIP = "Rules Text - Creature Flip" + REMINDER_TEXT = "Reminder Text" + MUTATE = "Mutate" + DIVIDER = "Divider" + CREATOR = "Creator" # Prototype - PROTO_TEXTBOX = 'Prototype Textbox' - PROTO_MANABOX = 'Prototype Manabox' - PROTO_PTBOX = 'Prototype PT Box' - PROTO_MANA_COST = 'Prototype Mana Cost' - PROTO_RULES = 'Prototype Rules Text' - PROTO_PT = 'Prototype Power / Toughness' + PROTO_TEXTBOX = "Prototype Textbox" + PROTO_MANABOX = "Prototype Manabox" + PROTO_PTBOX = "Prototype PT Box" + PROTO_MANA_COST = "Prototype Mana Cost" + PROTO_RULES = "Prototype Rules Text" + PROTO_PT = "Prototype Power / Toughness" # Planar text and icons - STATIC_ABILITY = 'Static Ability' - CHAOS_ABILITY = 'Chaos Ability' - CHAOS_SYMBOL = 'Chaos Symbol' - PHENOMENON = 'Phenomenon' - TEXTBOX = 'Textbox' + STATIC_ABILITY = "Static Ability" + CHAOS_ABILITY = "Chaos Ability" + CHAOS_SYMBOL = "Chaos Symbol" + PHENOMENON = "Phenomenon" + TEXTBOX = "Textbox" # Text References - TEXTBOX_REFERENCE = 'Textbox Reference' - TEXTBOX_REFERENCE_LAND = 'Textbox Reference Land' - MUTATE_REFERENCE = 'Mutate Reference' - PT_REFERENCE = 'PT Reference' - EXPANSION_REFERENCE = 'Expansion Reference' - NAME_REFERENCE_NON_LEGENDARY = 'Name Reference Non-Legendary' - NAME_REFERENCE_LEGENDARY = 'Name Reference Legendary' - NAME_REFERENCE = 'Name Reference' - TYPE_LINE_REFERENCE = 'Typeline Reference' - COLLECTOR_REFERENCE = 'Collector Reference' + TEXTBOX_REFERENCE = "Textbox Reference" + TEXTBOX_REFERENCE_LAND = "Textbox Reference Land" + MUTATE_REFERENCE = "Mutate Reference" + PT_REFERENCE = "PT Reference" + EXPANSION_REFERENCE = "Expansion Reference" + NAME_REFERENCE_NON_LEGENDARY = "Name Reference Non-Legendary" + NAME_REFERENCE_LEGENDARY = "Name Reference Legendary" + NAME_REFERENCE = "Name Reference" + TYPE_LINE_REFERENCE = "Typeline Reference" + COLLECTOR_REFERENCE = "Collector Reference" # Saga - SAGA = 'Saga' - PINLINES_AND_SAGA_STRIPE = 'Pinlines & Saga Stripe' - BANNER = 'Banner' - STRIPE = 'Stripe' + SAGA = "Saga" + PINLINES_AND_SAGA_STRIPE = "Pinlines & Saga Stripe" + BANNER = "Banner" + STRIPE = "Stripe" # Case - CASE = 'Case' + CASE = "Case" # Class - CLASS = 'Class' - STAGE = 'Stage' + CLASS = "Class" + STAGE = "Stage" # Token - TOKEN = 'Token' + TOKEN = "Token" # Planeswalker - PLANESWALKER = 'Planeswalker' - FIRST_ABILITY = 'First Ability' - SECOND_ABILITY = 'Second Ability' - THIRD_ABILITY = 'Third Ability' - FOURTH_ABILITY = 'Fourth Ability' - STARTING_LOYALTY = 'Starting Loyalty' - LOYALTY_GRAPHICS = 'Loyalty Graphics' - LOYALTY_REFERENCE = 'Loyalty Reference' - STATIC_TEXT = 'Static Text' - ABILITY_TEXT = 'Ability Text' - COLON = 'Colon' - TEXT = 'Text' - COST = 'Cost' + PLANESWALKER = "Planeswalker" + FIRST_ABILITY = "First Ability" + SECOND_ABILITY = "Second Ability" + THIRD_ABILITY = "Third Ability" + FOURTH_ABILITY = "Fourth Ability" + STARTING_LOYALTY = "Starting Loyalty" + LOYALTY_GRAPHICS = "Loyalty Graphics" + LOYALTY_REFERENCE = "Loyalty Reference" + STATIC_TEXT = "Static Text" + ABILITY_TEXT = "Ability Text" + COLON = "Colon" + TEXT = "Text" + COST = "Cost" # Art Frames - ART_FRAME = 'Art Frame' - FULL_ART_FRAME = 'Full Art Frame' - BORDERLESS_FRAME = 'Borderless Frame' - BASIC_ART_FRAME = 'Basic Art Frame' - PLANESWALKER_ART_FRAME = 'Planeswalker Art Frame' - SCRYFALL_SCAN_FRAME = 'Scryfall Scan Frame' + ART_FRAME = "Art Frame" + FULL_ART_FRAME = "Full Art Frame" + BORDERLESS_FRAME = "Borderless Frame" + BASIC_ART_FRAME = "Basic Art Frame" + PLANESWALKER_ART_FRAME = "Planeswalker Art Frame" + SCRYFALL_SCAN_FRAME = "Scryfall Scan Frame" # Double Faced - MDFC = 'MDFC' - TRANSFORM = 'Transform' - TRANSFORM_FRONT = 'TF Front' - TRANSFORM_BACK = 'TF Back' - MDFC_FRONT = 'mdfc-front' - MDFC_BACK = 'mdfc-back' - MODAL_FRONT = 'MDFC Front' - MODAL_BACK = 'MDFC Back' + MDFC = "MDFC" + TRANSFORM = "Transform" + TRANSFORM_FRONT = "TF Front" + TRANSFORM_BACK = "TF Back" + MDFC_FRONT = "mdfc-front" + MDFC_BACK = "mdfc-back" + MODAL_FRONT = "MDFC Front" + MODAL_BACK = "MDFC Back" # Orientation - TOP = 'Top' - BOTTOM = 'Bottom' - LEFT = 'Left' - RIGHT = 'Right' - BACK = 'Back' - FRONT = 'Front' + TOP = "Top" + BOTTOM = "Bottom" + LEFT = "Left" + RIGHT = "Right" + BACK = "Back" + FRONT = "Front" # Sizes - TALL = 'Tall' - MEDIUM = 'Medium' - SHORT = 'Short' - MINI = 'Mini' + TALL = "Tall" + MEDIUM = "Medium" + SHORT = "Short" + MINI = "Mini" # Frame types - BORDERLESS = 'Borderless' - EXTENDED = 'Extended' - CLASSIC = 'Classic' - FULLART = 'Fullart' + BORDERLESS = "Borderless" + EXTENDED = "Extended" + CLASSIC = "Classic" + FULLART = "Fullart" # Adventure - ADVENTURE = 'Adventure' - STORYBOOK = 'Storybook' - ADVENTURE_NAME = 'Adventure Name' - ADVENTURE_TYPELINE = 'Adventure Typeline' - ADVENTURE_TYPELINE_ACCENT = 'Adventure Typeline Accent' - NAME_ADVENTURE = 'Card Name - Adventure' - TYPE_LINE_ADVENTURE = 'Typeline - Adventure' - MANA_COST_ADVENTURE = 'Mana Cost - Adventure' - RULES_TEXT_ADVENTURE = 'Rules Text - Adventure' - TEXTBOX_REFERENCE_ADVENTURE = 'Textbox Reference - Adventure' - DIVIDER_ADVENTURE = 'Divider - Adventure' - WINGS = 'Wings' + ADVENTURE = "Adventure" + STORYBOOK = "Storybook" + ADVENTURE_NAME = "Adventure Name" + ADVENTURE_TYPELINE = "Adventure Typeline" + ADVENTURE_TYPELINE_ACCENT = "Adventure Typeline Accent" + NAME_ADVENTURE = "Card Name - Adventure" + TYPE_LINE_ADVENTURE = "Typeline - Adventure" + MANA_COST_ADVENTURE = "Mana Cost - Adventure" + RULES_TEXT_ADVENTURE = "Rules Text - Adventure" + TEXTBOX_REFERENCE_ADVENTURE = "Textbox Reference - Adventure" + DIVIDER_ADVENTURE = "Divider - Adventure" + WINGS = "Wings" # Battles - DEFENSE = 'Defense' - DEFENSE_REFERENCE = 'Defense Reference' + DEFENSE = "Defense" + DEFENSE_REFERENCE = "Defense Reference" # Station - STATION = 'Station' - REQUIREMENT = 'Requirement' + STATION = "Station" + REQUIREMENT = "Requirement" diff --git a/src/enums/mtg.py b/src/enums/mtg.py index 874a7d59..c6b862bf 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -13,87 +13,92 @@ class LayoutCategory(StrEnum): """Card layout category, broad naming used for displaying on GUI elements.""" - Adventure = 'Adventure' - Battle = 'Battle' - Case = 'Case' - Class = 'Class' - Leveler = 'Leveler' - MDFC = 'MDFC' - Mutate = 'Mutate' - Normal = 'Normal' - Planar = 'Planar' - Planeswalker = 'Planeswalker' - PlaneswalkerMDFC = 'PW MDFC' - PlaneswalkerTransform = 'PW Transform' - Prototype = 'Prototype' - Saga = 'Saga' - Split = 'Split' - Station = 'Station' - Token = 'Token' - Transform = 'Transform' + + Adventure = "Adventure" + Battle = "Battle" + Case = "Case" + Class = "Class" + Leveler = "Leveler" + MDFC = "MDFC" + Mutate = "Mutate" + Normal = "Normal" + Planar = "Planar" + Planeswalker = "Planeswalker" + PlaneswalkerMDFC = "PW MDFC" + PlaneswalkerTransform = "PW Transform" + Prototype = "Prototype" + Saga = "Saga" + Split = "Split" + Station = "Station" + Token = "Token" + Transform = "Transform" @classmethod - def matches_layout_type(cls, category: LayoutCategory, layout_type: LayoutType) -> bool: + def matches_layout_type( + cls, category: LayoutCategory, layout_type: LayoutType + ) -> bool: return layout_type in layout_map_category[category] class LayoutType(StrEnum): """Card layout type, fine-grained naming separated by front/back where applicable.""" - Adventure = 'adventure' - Battle = 'battle' - Case = 'case' - Class = 'class' - Leveler = 'leveler' - MDFCBack = 'mdfc_back' - MDFCFront = 'mdfc_front' - Mutate = 'mutate' - Normal = 'normal' - Planar = 'planar' - Planeswalker = 'planeswalker' - PlaneswalkerMDFCBack = 'pw_mdfc_back' - PlaneswalkerMDFCFront = 'pw_mdfc_front' - PlaneswalkerTransformBack = 'pw_tf_back' - PlaneswalkerTransformFront = 'pw_tf_front' - Prototype = 'prototype' - Saga = 'saga' - Split = 'split' - Station = 'station' - TransformBack = 'transform_back' - TransformFront = 'transform_front' + + Adventure = "adventure" + Battle = "battle" + Case = "case" + Class = "class" + Leveler = "leveler" + MDFCBack = "mdfc_back" + MDFCFront = "mdfc_front" + Mutate = "mutate" + Normal = "normal" + Planar = "planar" + Planeswalker = "planeswalker" + PlaneswalkerMDFCBack = "pw_mdfc_back" + PlaneswalkerMDFCFront = "pw_mdfc_front" + PlaneswalkerTransformBack = "pw_tf_back" + PlaneswalkerTransformFront = "pw_tf_front" + Prototype = "prototype" + Saga = "saga" + Split = "split" + Station = "station" + TransformBack = "transform_back" + TransformFront = "transform_front" class LayoutScryfall(StrEnum): """Card layout type, according to Scryfall data.""" - Normal = 'normal' - Split = 'split' - Flip = 'flip' - Transform = 'transform' - MDFC = 'modal_dfc' - Meld = 'meld' - Leveler = 'leveler' - Case = 'case' - Class = 'class' - Saga = 'saga' - Adventure = 'adventure' - Mutate = 'mutate' - Prototype = 'prototype' - Battle = 'battle' - Planar = 'planar' - Scheme = 'scheme' - Vanguard = 'vanguard' - Token = 'token' - DoubleFacedToken = 'double_faced_token' - Emblem = 'emblem' - Augment = 'augment' - Host = 'host' - ArtSeries = 'art_series' - ReversibleCard = 'reversible_card' + + Normal = "normal" + Split = "split" + Flip = "flip" + Transform = "transform" + MDFC = "modal_dfc" + Meld = "meld" + Leveler = "leveler" + Case = "case" + Class = "class" + Saga = "saga" + Adventure = "adventure" + Mutate = "mutate" + Prototype = "prototype" + Battle = "battle" + Planar = "planar" + Scheme = "scheme" + Vanguard = "vanguard" + Token = "token" + DoubleFacedToken = "double_faced_token" + Emblem = "emblem" + Augment = "augment" + Host = "host" + ArtSeries = "art_series" + ReversibleCard = "reversible_card" # Definitions added to Scryfall built-ins - Planeswalker = 'planeswalker' - PlaneswalkerMDFC = 'planeswalker_mdfc' - PlaneswalkerTransform = 'planeswalker_tf' - Station = 'station' + Planeswalker = "planeswalker" + PlaneswalkerMDFC = "planeswalker_mdfc" + PlaneswalkerTransform = "planeswalker_tf" + Station = "station" """Maps Layout categories to tuples of equivalent Layout types.""" @@ -102,8 +107,14 @@ class LayoutScryfall(StrEnum): LayoutCategory.MDFC: (LayoutType.MDFCFront, LayoutType.MDFCBack), LayoutCategory.Transform: (LayoutType.TransformFront, LayoutType.TransformBack), LayoutCategory.Planeswalker: (LayoutType.Planeswalker,), - LayoutCategory.PlaneswalkerMDFC: (LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack), - LayoutCategory.PlaneswalkerTransform: (LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack), + LayoutCategory.PlaneswalkerMDFC: ( + LayoutType.PlaneswalkerMDFCFront, + LayoutType.PlaneswalkerMDFCBack, + ), + LayoutCategory.PlaneswalkerTransform: ( + LayoutType.PlaneswalkerTransformFront, + LayoutType.PlaneswalkerTransformBack, + ), LayoutCategory.Saga: (LayoutType.Saga,), LayoutCategory.Case: (LayoutType.Case,), LayoutCategory.Class: (LayoutType.Class,), @@ -119,7 +130,8 @@ class LayoutScryfall(StrEnum): """Maps Layout types to their equivalent Layout category.""" layout_map_types: dict[LayoutType, LayoutCategory] = { - raw: named for named, raw in ( + raw: named + for named, raw in ( (k, n) for k, names in layout_map_category.items() for n in names ) } @@ -127,40 +139,38 @@ class LayoutScryfall(StrEnum): """Maps Layout types to a display formatted Layout category (with Back or Front).""" layout_map_types_display: dict[LayoutType, str] = { raw: ( - f'{named} Front' if 'front' in raw else ( - f'{named} Back' if 'back' in raw else named) - ) for raw, named in layout_map_types.items() + f"{named} Front" + if "front" in raw + else (f"{named} Back" if "back" in raw else named) + ) + for raw, named in layout_map_types.items() } """Maps multimodal display formatted layout types to a singular layout type.""" -layout_map_display_condition: dict[str,LayoutType] = { - f'{LayoutCategory.Transform} Front': LayoutType.TransformFront, - f'{LayoutCategory.Transform} Back': LayoutType.TransformBack, - f'{LayoutCategory.MDFC} Front': LayoutType.MDFCFront, - f'{LayoutCategory.MDFC} Back': LayoutType.MDFCBack, - f'{LayoutCategory.PlaneswalkerTransform} Front': LayoutType.PlaneswalkerTransformFront, - f'{LayoutCategory.PlaneswalkerTransform} Back': LayoutType.PlaneswalkerTransformBack, - f'{LayoutCategory.PlaneswalkerMDFC} Front': LayoutType.PlaneswalkerMDFCFront, - f'{LayoutCategory.PlaneswalkerMDFC} Back': LayoutType.PlaneswalkerMDFCBack +layout_map_display_condition: dict[str, LayoutType] = { + f"{LayoutCategory.Transform} Front": LayoutType.TransformFront, + f"{LayoutCategory.Transform} Back": LayoutType.TransformBack, + f"{LayoutCategory.MDFC} Front": LayoutType.MDFCFront, + f"{LayoutCategory.MDFC} Back": LayoutType.MDFCBack, + f"{LayoutCategory.PlaneswalkerTransform} Front": LayoutType.PlaneswalkerTransformFront, + f"{LayoutCategory.PlaneswalkerTransform} Back": LayoutType.PlaneswalkerTransformBack, + f"{LayoutCategory.PlaneswalkerMDFC} Front": LayoutType.PlaneswalkerMDFCFront, + f"{LayoutCategory.PlaneswalkerMDFC} Back": LayoutType.PlaneswalkerMDFCBack, } """Maps display formatted layout types to a combined group of two layout types.""" -layout_map_display_condition_dual: dict[LayoutCategory, tuple[LayoutType, LayoutType]] = { - LayoutCategory.Transform: ( - LayoutType.TransformFront, - LayoutType.TransformBack - ), - LayoutCategory.MDFC: ( - LayoutType.MDFCFront, - LayoutType.MDFCBack - ), +layout_map_display_condition_dual: dict[ + LayoutCategory, tuple[LayoutType, LayoutType] +] = { + LayoutCategory.Transform: (LayoutType.TransformFront, LayoutType.TransformBack), + LayoutCategory.MDFC: (LayoutType.MDFCFront, LayoutType.MDFCBack), LayoutCategory.PlaneswalkerTransform: ( LayoutType.PlaneswalkerTransformFront, - LayoutType.PlaneswalkerTransformBack + LayoutType.PlaneswalkerTransformBack, ), LayoutCategory.PlaneswalkerMDFC: ( LayoutType.PlaneswalkerMDFCFront, - LayoutType.PlaneswalkerMDFCBack + LayoutType.PlaneswalkerMDFCBack, ), } @@ -172,31 +182,33 @@ class LayoutScryfall(StrEnum): class CardTypes(StrEnum): """Represents main card types.""" - Artifact = 'Artifact' - Battle = 'Battle' - Conspiracy = 'Conspiracy' - Creature = 'Creature' - Enchantment = 'Enchantment' - Instant = 'Instant' - Land = 'Land' - Phenomenon = 'Phenomenon' - Plane = 'Plane' - Planeswalker = 'Planeswalker' - Scheme = 'Scheme' - Sorcery = 'Sorcery' - Tribal = 'Tribal' - Vanguard = 'Vanguard' + + Artifact = "Artifact" + Battle = "Battle" + Conspiracy = "Conspiracy" + Creature = "Creature" + Enchantment = "Enchantment" + Instant = "Instant" + Land = "Land" + Phenomenon = "Phenomenon" + Plane = "Plane" + Planeswalker = "Planeswalker" + Scheme = "Scheme" + Sorcery = "Sorcery" + Tribal = "Tribal" + Vanguard = "Vanguard" class CardTypesSuper(StrEnum): """Represents card supertypes.""" - Basic = 'Basic' - Elite = 'Elite' - Host = 'Host' - Legendary = 'Legendary' - Ongoing = 'Ongoing' - Snow = 'Snow' - World = 'World' + + Basic = "Basic" + Elite = "Elite" + Host = "Host" + Legendary = "Legendary" + Ongoing = "Ongoing" + Snow = "Snow" + World = "World" """ @@ -282,6 +294,7 @@ class CardTypesSuper(StrEnum): class Rarity(StrEnum): """Card rarities.""" + C = "common" U = "uncommon" R = "rare" @@ -293,6 +306,7 @@ class Rarity(StrEnum): class TransformIcons(StrEnum): """Transform icon names.""" + MOONELDRAZI = "mooneldrazidfc" COMPASSLAND = "compasslanddfc" UPSIDEDOWN = "upsidedowndfc" @@ -309,18 +323,17 @@ class TransformIcons(StrEnum): # Abilities that aren't italicize, despite fitting the pattern non_italics_abilities = [ - "Boast", # Kaldheim - "Forecast", # Dissension - "Gotcha", # Unhinged - "Visit", # Unfinity - "Whack", "Doodle", "Buzz" # Unstable + "Boast", # Kaldheim + "Forecast", # Dissension + "Gotcha", # Unhinged + "Visit", # Unfinity + "Whack", + "Doodle", + "Buzz", # Unstable ] # Edge case Planeswalkers with tall ability box -planeswalkers_tall = [ - "Gideon Blackblade", - "Comet, Stellar Pup" -] +planeswalkers_tall = ["Gideon Blackblade", "Comet, Stellar Pup"] """ * Fonts & Characters @@ -329,6 +342,7 @@ class TransformIcons(StrEnum): class CardFonts(StrEnum): """Fonts used for card text.""" + RULES = "PlantinMTPro-Regular" RULES_BOLD = "PlantinMTPro-Bold" RULES_ITALIC = "PlantinMTPro-Italic" @@ -361,8 +375,12 @@ class CardTextPatterns: """Defined card data regex patterns.""" # Rules Text - Special Card Types - LEVELER: re.Pattern[str] = re.compile(r"(.*?)\nLEVEL (\d*-\d*)\n(\d*/\d*)\n(.*?)\nLEVEL (\d*\+)\n(\d*/\d*)\n(.*?)$") - PROTOTYPE: re.Pattern[str] = re.compile(r"Prototype (.+) [—\-] ([0-9]{0,2}/[0-9]{0,2}) \((.+)\)") + LEVELER: re.Pattern[str] = re.compile( + r"(.*?)\nLEVEL (\d*-\d*)\n(\d*/\d*)\n(.*?)\nLEVEL (\d*\+)\n(\d*/\d*)\n(.*?)$" + ) + PROTOTYPE: re.Pattern[str] = re.compile( + r"Prototype (.+) [—\-] ([0-9]{0,2}/[0-9]{0,2}) \((.+)\)" + ) PLANESWALKER: re.Pattern[str] = re.compile(r"(^[^:]*$|^.*:.*$)", re.MULTILINE) CLASS: re.Pattern[str] = re.compile(r"(.+?): ([^\d]+ ?)(\d)\n(.+)") CLASS_NON_ENGLISH: re.Pattern[str] = re.compile(r"//Level_\d//") @@ -373,7 +391,7 @@ class CardTextPatterns: PATH_KWARGS: re.Pattern[str] = re.compile(r"\[([^\]=]+)=([^\]]*)]") PATH_SET: re.Pattern[str] = re.compile(r"\[([^\]=]+)]") PATH_NUM: re.Pattern[str] = re.compile(r"\{(.*)}") - PATH_CONDITION: re.Pattern[str] = re.compile(r'<([^>]*)>') + PATH_CONDITION: re.Pattern[str] = re.compile(r"<([^>]*)>") # Mana - Symbols SYMBOL: re.Pattern[str] = re.compile(r"(\{.*?})") @@ -387,7 +405,11 @@ class CardTextPatterns: # Text - Reminder TEXT_REMINDER: re.Pattern[str] = re.compile(r"\([^()]*\)") - TEXT_REMINDER_ENDING: re.Pattern[str] = re.compile(r"[\s\S]*(\([^()]*\))$", ) + TEXT_REMINDER_ENDING: re.Pattern[str] = re.compile( + r"[\s\S]*(\([^()]*\))$", + ) # Text - Italicised Ability - TEXT_ABILITY: re.Pattern[str] = re.compile(r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE) + TEXT_ABILITY: re.Pattern[str] = re.compile( + r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE + ) diff --git a/src/frame_logic.py b/src/frame_logic.py index 6a5c6226..59937cd5 100644 --- a/src/frame_logic.py +++ b/src/frame_logic.py @@ -1,6 +1,7 @@ """ * Frame Logic Module """ + from collections.abc import Iterable, Iterator from functools import cache, cached_property @@ -16,6 +17,7 @@ class RulesTextLine: """Data structure representing one line of the rules text in a Magic the Gathering card.""" + def __init__(self, line: str): self._line = line @@ -32,7 +34,7 @@ class RulesText: def __init__(self, text: str): self._text = text - self._lines = [RulesTextLine(n) for n in text.split('\n')] + self._lines = [RulesTextLine(n) for n in text.split("\n")] def __iter__(self) -> Iterator[RulesTextLine]: yield from self._lines @@ -44,65 +46,62 @@ def __iter__(self) -> Iterator[RulesTextLine]: # Single Letter Colors -colors = [ - LAYERS.WHITE, - LAYERS.BLUE, - LAYERS.BLACK, - LAYERS.RED, - LAYERS.GREEN -] +colors = [LAYERS.WHITE, LAYERS.BLUE, LAYERS.BLACK, LAYERS.RED, LAYERS.GREEN] # Color Lookup Table color_lookup = { # Two Colors - 2: {''.join(sorted(color)): color for color in [ - LAYERS.WU, - LAYERS.UB, - LAYERS.BR, - LAYERS.RG, - LAYERS.GW, - LAYERS.WB, - LAYERS.BG, - LAYERS.GU, - LAYERS.UR, - LAYERS.RW - ]}, + 2: { + "".join(sorted(color)): color + for color in [ + LAYERS.WU, + LAYERS.UB, + LAYERS.BR, + LAYERS.RG, + LAYERS.GW, + LAYERS.WB, + LAYERS.BG, + LAYERS.GU, + LAYERS.UR, + LAYERS.RW, + ] + }, # Three Colors - 3: {''.join(sorted(color)): color for color in [ - LAYERS.GWU, - LAYERS.WUB, - LAYERS.UBR, - LAYERS.BRG, - LAYERS.RGW, - LAYERS.WBG, - LAYERS.URW, - LAYERS.BGU, - LAYERS.RWB, - LAYERS.GUR - ]}, + 3: { + "".join(sorted(color)): color + for color in [ + LAYERS.GWU, + LAYERS.WUB, + LAYERS.UBR, + LAYERS.BRG, + LAYERS.RGW, + LAYERS.WBG, + LAYERS.URW, + LAYERS.BGU, + LAYERS.RWB, + LAYERS.GUR, + ] + }, # Four Colors - 4: {''.join(sorted(color)): color for color in [ - LAYERS.WUBR, - LAYERS.UBRG, - LAYERS.BRGW, - LAYERS.RGWU, - LAYERS.GWUB - ]}, - 5: {'BGRUW': LAYERS.WUBRG} + 4: { + "".join(sorted(color)): color + for color in [LAYERS.WUBR, LAYERS.UBRG, LAYERS.BRGW, LAYERS.RGWU, LAYERS.GWUB] + }, + 5: {"BGRUW": LAYERS.WUBRG}, } # Basic Land Types land_types = { - 'Plains': LAYERS.WHITE, - 'Island': LAYERS.BLUE, - 'Swamp': LAYERS.BLACK, - 'Mountain': LAYERS.RED, - 'Forest': LAYERS.GREEN + "Plains": LAYERS.WHITE, + "Island": LAYERS.BLUE, + "Swamp": LAYERS.BLACK, + "Mountain": LAYERS.RED, + "Forest": LAYERS.GREEN, } # Mana Symbol Matching -mono_symbols = ['{W}', '{U}', '{B}', '{R}', '{G}'] -hybrid_symbols = ['W/U', 'U/B', 'B/R', 'R/G', 'G/W', 'W/B', 'B/G', 'G/U', 'U/R', 'R/W'] +mono_symbols = ["{W}", "{U}", "{B}", "{R}", "{G}"] +hybrid_symbols = ["W/U", "U/B", "B/R", "R/G", "G/W", "W/B", "B/G", "G/U", "U/R", "R/W"] """ @@ -123,7 +122,7 @@ def is_multicolor_string(text: str) -> bool: if not text: return False if 1 < len(text) < 6: - return bool(''.join(sorted(text)) in color_lookup.get(len(text), [])) + return bool("".join(sorted(text)) in color_lookup.get(len(text), [])) return False @@ -142,7 +141,7 @@ def contains_frame_colors(text: str) -> bool: if text in colors or text in LAYERS.WUBRG: return True if 1 > len(text) < 5: - return bool(''.join(sorted(text)) in color_lookup.get(len(text), [])) + return bool("".join(sorted(text)) in color_lookup.get(len(text), [])) return False @@ -158,9 +157,9 @@ def get_ordered_colors(text: str | Iterable[str]) -> str: """ # Validate the input if not text: - return '' + return "" if not isinstance(text, str): - text = ''.join(text) + text = "".join(text) # Match an ordered color if len(text) == 1: @@ -168,7 +167,7 @@ def get_ordered_colors(text: str | Iterable[str]) -> str: return text if 1 < len(text) < 5: # Use a lookup table - return color_lookup[len(text)].get(''.join(sorted(text)), '') + return color_lookup[len(text)].get("".join(sorted(text)), "") # All 5 colors return LAYERS.WUBRG @@ -184,9 +183,9 @@ def get_mana_cost_colors(mana_cost: str) -> str: """ # No valid mana cost if not mana_cost: - return '' + return "" color_list = [color for color in colors if color in mana_cost] - return ''.join(color_list) + return "".join(color_list) def get_color_identity_nonland( @@ -194,7 +193,7 @@ def get_color_identity_nonland( type_line: str, oracle_text: str, color_indicator: list[MagicColor], - color_list: list[MagicColor] + color_list: list[MagicColor], ) -> str: """Get the assumed color identity of Non-Land card based on a priority list of factors. @@ -208,24 +207,26 @@ def get_color_identity_nonland( Returns: Our best guess for this card's color identity. """ - if ' is all colors.' in oracle_text: + if " is all colors." in oracle_text: # Transguild Courier case return LAYERS.WUBRG - if mana_cost == '' or (mana_cost == '{0}' and LAYERS.ARTIFACT not in type_line): + if mana_cost == "" or (mana_cost == "{0}" and LAYERS.ARTIFACT not in type_line): # Card with no mana cost if color_indicator: # Use Color Indicator if provided - return get_ordered_colors(''.join(color_indicator)) + return get_ordered_colors("".join(color_indicator)) elif color_list: # Use Color Identity/Colors if provided - return get_ordered_colors(''.join(color_list)) + return get_ordered_colors("".join(color_list)) # No Color Identity return "" # Use colors from Mana Cost as assumed color identity return get_ordered_colors(get_mana_cost_colors(mana_cost)) -def check_hybrid_color_card(color_identity: str | list[str], mana_cost: str, is_dfc: bool) -> bool: +def check_hybrid_color_card( + color_identity: str | list[str], mana_cost: str, is_dfc: bool +) -> bool: """Check a number of inputs to see if this card is: - A card with only hybrid Mana symbols - Only 2 colors represented @@ -242,9 +243,11 @@ def check_hybrid_color_card(color_identity: str | list[str], mana_cost: str, is_ True if hybrid, otherwise False. """ # Identify if the card is a two-color hybrid card with only hybrid mana - if len(color_identity) == 2 and not any([symbol in mana_cost for symbol in mono_symbols]): + if len(color_identity) == 2 and not any( + [symbol in mana_cost for symbol in mono_symbols] + ): # Hybrid empty mana case - Asmoranomardi[...] - if mana_cost == '' and not is_dfc: + if mana_cost == "" and not is_dfc: return True for hybrid_symbol in hybrid_symbols: if hybrid_symbol in mana_cost: @@ -264,7 +267,9 @@ def check_hybrid_mana_cost(color_identity: str | list[str], mana_cost: str) -> b True if hybrid mana cost, otherwise False. """ # Identify if the card is a two-color hybrid card with only hybrid mana - if len(color_identity) == 2 and not any([symbol in mana_cost for symbol in mono_symbols]): + if len(color_identity) == 2 and not any( + [symbol in mana_cost for symbol in mono_symbols] + ): if any([bool(sym in mana_cost) for sym in hybrid_symbols]): return True return False @@ -285,7 +290,7 @@ def get_frame_details(card: ScryfallCard | ScryfallCardFace) -> FrameDetails: Returns: Dict containing FrameDetails representing the card's frame makeup. """ - if card.type_line and 'Land' in card.type_line: + if card.type_line and "Land" in card.type_line: return get_frame_details_land(card) return get_frame_details_nonland(card) @@ -301,14 +306,14 @@ def get_frame_details_land(card: ScryfallCard | ScryfallCardFace) -> FrameDetail """ # Grab the attributes we need type_line, oracle_text = card.type_line or "", card.oracle_text or "" - twins = colors_tapped = basic_identity = '' + twins = colors_tapped = basic_identity = "" result: FrameDetails = { "background": LAYERS.LAND, "pinlines": LAYERS.LAND, "twins": LAYERS.LAND, - "identity": '', + "identity": "", "is_colorless": False, - "is_hybrid": False + "is_hybrid": False, } # Check if it has a basic land type @@ -323,18 +328,15 @@ def get_frame_details_land(card: ScryfallCard | ScryfallCardFace) -> FrameDetail elif len(basic_identity) == 2: # Dual land type identity identity = get_ordered_colors(basic_identity) - result.update({ - "pinlines": identity, - "identity": identity - }) + result.update({"pinlines": identity, "identity": identity}) return result # Iterate over rules text lines - basic_identity = '' - for line in oracle_text.split('\n'): + basic_identity = "" + for line in oracle_text.split("\n"): # Identify if the card is a fetch land - if 'search your library' in line.lower(): - if 'cycling' not in line.lower(): + if "search your library" in line.lower(): + if "cycling" not in line.lower(): # Fetch land of some kind, find basic land types for key, basic in land_types.items(): if key in line: @@ -344,81 +346,93 @@ def get_frame_details_land(card: ScryfallCard | ScryfallCardFace) -> FrameDetail # Set the name box & pinlines based on how many basics the ability mentions if len(basic_identity) == 1: # One basic mentioned - Single color identity - result.update({ - 'pinlines': basic_identity, - 'twins': basic_identity, - 'identity': basic_identity, - }) + result.update( + { + "pinlines": basic_identity, + "twins": basic_identity, + "identity": basic_identity, + } + ) return result elif len(basic_identity) == 2: # Two basics mentioned - Dual color identity identity = get_ordered_colors(basic_identity) - result.update({ - 'pinlines': identity, - 'identity': identity - }) + result.update({"pinlines": identity, "identity": identity}) return result elif len(basic_identity) == 3: # Three basics mentioned - Panorama case return result elif LAYERS.LAND.lower() in line: # Land probably fetches any basic, exclude "Ash Barrens" case - if (('tapped' not in line or 'untap' in line) and - # "Ash Barrens" case - 'into your hand' not in line and - # "Demolition Field" case - 'Destroy' not in line): + if ( + ("tapped" not in line or "untap" in line) + and + # "Ash Barrens" case + "into your hand" not in line + and + # "Demolition Field" case + "Destroy" not in line + ): # Gold fetch land - result.update({ - 'pinlines': LAYERS.GOLD, - 'twins': LAYERS.GOLD, - 'identity': LAYERS.WUBRG - }) + result.update( + { + "pinlines": LAYERS.GOLD, + "twins": LAYERS.GOLD, + "identity": LAYERS.WUBRG, + } + ) return result # Colorless fetch land return result # Check if the line adds one mana of any color - if ('add' in line.lower() and 'mana' in line) and any( - [t in line for t in ['color ', 'colors ', 'color.', 'colors.', 'any type']] + if ("add" in line.lower() and "mana" in line) and any( + [t in line for t in ["color ", "colors ", "color.", "colors.", "any type"]] ): # Probably Gold Land if it excludes the following cases - cases = ['enters the battlefield', 'Remove a charge counter', 'Sacrifice', 'luck counter'] + cases = [ + "enters the battlefield", + "Remove a charge counter", + "Sacrifice", + "luck counter", + ] if all(case not in line for case in cases): # Gold Identity Land - result.update({ - 'pinlines': LAYERS.GOLD, - 'twins': LAYERS.GOLD, - 'identity': LAYERS.WUBRG - }) + result.update( + { + "pinlines": LAYERS.GOLD, + "twins": LAYERS.GOLD, + "identity": LAYERS.WUBRG, + } + ) return result # Check if the line chooses a basic land type, e.g. Thran Portal - if 'choose a basic land type' in line: + if "choose a basic land type" in line: # Gold Identity Land - result.update({ - 'pinlines': LAYERS.GOLD, - 'twins': LAYERS.GOLD, - 'identity': LAYERS.WUBRG - }) + result.update( + { + "pinlines": LAYERS.GOLD, + "twins": LAYERS.GOLD, + "identity": LAYERS.WUBRG, + } + ) return result # Check if the line makes all lands X type, ex: Urborg, Tomb of Yawgmoth - if 'Each land is a ' in line: + if "Each land is a " in line: for k, v in land_types.items(): - if f'Each land is a {k}' in line: - result.update({ - 'pinlines': v, - 'twins': v, - 'identity': v - }) + if f"Each land is a {k}" in line: + result.update({"pinlines": v, "twins": v, "identity": v}) return result # Count how many colors of mana the card can tap to add - if line.find('{T}') < line.find(':') and 'add ' in line.lower(): + if line.find("{T}") < line.find(":") and "add " in line.lower(): # This line taps to add one or more colors, add those colors - for color in [c for c in colors if f"{{{c}}}" in line and c not in colors_tapped]: + for color in [ + c for c in colors if f"{{{c}}}" in line and c not in colors_tapped + ]: # Add this color to colors_tapped colors_tapped += color @@ -426,25 +440,27 @@ def get_frame_details_land(card: ScryfallCard | ScryfallCardFace) -> FrameDetail identity = get_ordered_colors(colors_tapped) if len(identity) == 1: # Mono Color - result.update({ - 'pinlines': identity, - 'identity': identity, - 'twins': twins or colors_tapped - }) + result.update( + { + "pinlines": identity, + "identity": identity, + "twins": twins or colors_tapped, + } + ) elif len(identity) == 2: # Dual Color - result.update({ - 'pinlines': identity, - 'identity': identity, - 'twins': twins or LAYERS.LAND - }) + result.update( + {"pinlines": identity, "identity": identity, "twins": twins or LAYERS.LAND} + ) elif len(colors_tapped) > 2: # Three to Five Colors - result.update({ - 'pinlines': LAYERS.GOLD, - 'identity': identity, - 'twins': twins or LAYERS.GOLD - }) + result.update( + { + "pinlines": LAYERS.GOLD, + "identity": identity, + "twins": twins or LAYERS.GOLD, + } + ) return result @@ -466,7 +482,9 @@ def get_frame_details_nonland(card: ScryfallCard | ScryfallCardFace) -> FrameDet type_line=type_line, oracle_text=oracle_text, color_indicator=card.color_indicator or [], - color_list=card.color_identity if isinstance(card, ScryfallCard) else card.colors or [] + color_list=card.color_identity + if isinstance(card, ScryfallCard) + else card.colors or [], ) # Default results @@ -476,78 +494,81 @@ def get_frame_details_nonland(card: ScryfallCard | ScryfallCardFace) -> FrameDet "twins": color_identity, "identity": color_identity, "is_colorless": False, - "is_hybrid": False + "is_hybrid": False, } # Handle full art colorless cards and devoid frame cards if ( - # Devoid card check - devoid := bool('Devoid' in oracle_text and len(color_identity) > 0) - ) or ( - # Zero color non-artifact card check - len(color_identity) <= 0 and LAYERS.ARTIFACT not in type_line - ) or ( - # Zero mana cost eldrazi card check - not mana_cost and 'Eldrazi' in type_line + ( + # Devoid card check + devoid := bool("Devoid" in oracle_text and len(color_identity) > 0) + ) + or ( + # Zero color non-artifact card check + len(color_identity) <= 0 and LAYERS.ARTIFACT not in type_line + ) + or ( + # Zero mana cost eldrazi card check + not mana_cost and "Eldrazi" in type_line + ) ): # Devoid dual color frame or Colorless frame? if devoid and len(color_identity) > 1: # Use gold name plates and devoid-style background - result.update({ - 'twins': LAYERS.GOLD, - 'background': LAYERS.GOLD - }) + result.update({"twins": LAYERS.GOLD, "background": LAYERS.GOLD}) elif not devoid: # Completely Colorless card - result.update({ - 'twins': LAYERS.COLORLESS, - 'background': LAYERS.COLORLESS, - 'pinlines': LAYERS.COLORLESS - }) + result.update( + { + "twins": LAYERS.COLORLESS, + "background": LAYERS.COLORLESS, + "pinlines": LAYERS.COLORLESS, + } + ) # Return formatted Colorless card - result['is_colorless'] = True + result["is_colorless"] = True return result # Identify Hybrid frame cards hybrid = check_hybrid_color_card( color_identity=color_identity, mana_cost=mana_cost, - is_dfc=card.object == 'card_face' + is_dfc=card.object == "card_face", ) # Is this card hybrid? if hybrid: - result['is_hybrid'] = True + result["is_hybrid"] = True # Switch Background if LAYERS.VEHICLE in type_line: # Vehicle card - result['background'] = LAYERS.VEHICLE + result["background"] = LAYERS.VEHICLE elif LAYERS.ARTIFACT in type_line: # Artifact card - result['background'] = LAYERS.ARTIFACT + result["background"] = LAYERS.ARTIFACT elif len(color_identity) >= 2 and not hybrid: # 2+ color card not Hybrid - result['background'] = LAYERS.GOLD + result["background"] = LAYERS.GOLD # Switch Pinlines if len(color_identity) == 0: # No colors - result['pinlines'] = LAYERS.ARTIFACT + result["pinlines"] = LAYERS.ARTIFACT elif len(color_identity) > 2: # 1-2 colors - result['pinlines'] = LAYERS.GOLD + result["pinlines"] = LAYERS.GOLD # Switch Name Plates if len(color_identity) == 0: # No colors - result['twins'] = LAYERS.ARTIFACT + result["twins"] = LAYERS.ARTIFACT elif hybrid: # Hybrid card - result['twins'] = LAYERS.LAND + result["twins"] = LAYERS.LAND elif len(color_identity) >= 2: # 2+ colors - result['twins'] = LAYERS.GOLD + result["twins"] = LAYERS.GOLD # Return the processed details return result @@ -570,13 +591,13 @@ def get_special_rarity(rarity: str, card: ScryfallCard) -> str: """ if rarity == Rarity.S: # Timeshifted cards - if card.frame == '1997': + if card.frame == "1997": return Rarity.T # Championship cards - if 'Champion' in card.set_name: + if "Champion" in card.set_name: return Rarity.M # Masterpiece - if card.set_type == 'masterpiece': + if card.set_type == "masterpiece": return Rarity.M # Case like Prismatic Piper return Rarity.C diff --git a/src/gui/qml/models/image_transform_model.py b/src/gui/qml/models/image_transform_model.py index 17f086af..9c86bc0e 100644 --- a/src/gui/qml/models/image_transform_model.py +++ b/src/gui/qml/models/image_transform_model.py @@ -43,7 +43,7 @@ def _thread_pool(self) -> ThreadPoolExecutor: _image_file_formats_changed = Signal() @Property(list, notify=_image_file_formats_changed) - def image_file_formats(self) -> list[str]: # pyright: ignore[reportRedeclaration] + def image_file_formats(self) -> list[str]: return self._image_file_formats _downscale_changed = Signal() @@ -97,7 +97,9 @@ def encoding_quality(self, value: int) -> None: @Slot() def transform_images(self) -> None: async def action(): - images = await self._file_dialog_model.select_images(dialog_id="image_transform") + images = await self._file_dialog_model.select_images( + dialog_id="image_transform" + ) await gather(*[self.transform_image(image) for image in images]) ensure_future(action()) @@ -119,6 +121,8 @@ async def transform_image(self, image: Path) -> None: self._downscale_width if self._downscale else None, self._encoding_quality, ) - _logger.info(f"Saved transformed version of {image} to {out_path}") + _logger.info( + f"Saved transformed version of {image} to {out_path}" + ) except Exception: _logger.exception(f"Failed to transform {image}") diff --git a/src/gui/qml/models/pydantic_q_list_model.py b/src/gui/qml/models/pydantic_q_list_model.py index 9d684507..fa9eb4ac 100644 --- a/src/gui/qml/models/pydantic_q_list_model.py +++ b/src/gui/qml/models/pydantic_q_list_model.py @@ -156,7 +156,7 @@ def _set_selected_model_index(self, value: QModelIndex) -> None: if not value.isValid() or not item: self._selected_model_index = value self.selected_model_index_changed.emit(value) - self.selected_title = "" # pyright: ignore[reportAttributeAccessIssue] + self.selected_title = "" return None if value != self._selected_model_index: diff --git a/src/gui/qml/models/render_operations_model.py b/src/gui/qml/models/render_operations_model.py index ab95aa31..5161416e 100644 --- a/src/gui/qml/models/render_operations_model.py +++ b/src/gui/qml/models/render_operations_model.py @@ -122,49 +122,49 @@ def active_operation(self, value: RenderOperationDetails | None) -> None: self._active_operation_changed.emit() @Property(str, notify=_active_operation_changed) - def rendering_image_name(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_image_name(self) -> str: if self._active_operation: return self._active_operation.image_name return "" @Property(str, notify=_active_operation_changed) - def rendering_image_path(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_image_path(self) -> str: if self._active_operation: return self._active_operation.image_path return "" @Property(str, notify=_active_operation_changed) - def rendering_card_name(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_card_name(self) -> str: if self._active_operation: return self._active_operation.card_name return "" @Property(str, notify=_active_operation_changed) - def rendering_card_artist(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_card_artist(self) -> str: if self._active_operation: return self._active_operation.card_artist return "" @Property(str, notify=_active_operation_changed) - def rendering_card_set(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_card_set(self) -> str: if self._active_operation: return self._active_operation.card_set return "" @Property(str, notify=_active_operation_changed) - def rendering_card_collector_number(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_card_collector_number(self) -> str: if self._active_operation: return self._active_operation.card_collector_number return "" @Property(str, notify=_active_operation_changed) - def rendering_layout_name(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_layout_name(self) -> str: if self._active_operation: return self._active_operation.layout_name return "" @Property(str, notify=_active_operation_changed) - def rendering_class_name(self) -> str: # pyright: ignore[reportRedeclaration] + def rendering_class_name(self) -> str: if self._active_operation: return self._active_operation.class_name return "" diff --git a/src/gui/qml/models/settings_tree_model.py b/src/gui/qml/models/settings_tree_model.py index 09e3bdc4..0ebcad0d 100644 --- a/src/gui/qml/models/settings_tree_model.py +++ b/src/gui/qml/models/settings_tree_model.py @@ -3,13 +3,7 @@ from typing import override from pydantic import BaseModel -from PySide6.QtCore import ( - Property, - QModelIndex, - QObject, - Signal, - Slot, -) +from PySide6.QtCore import Property, QModelIndex, QObject, Signal, Slot from src._config import AppConfig from src._loader import AssembledTemplate, ConfigHandler, TemplateLibrary diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 51a928f2..8814d089 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -5,11 +5,7 @@ from urllib.request import url2pathname from pydantic import BaseModel -from PySide6.QtCore import ( - QObject, - QUrl, - Slot, -) +from PySide6.QtCore import QObject, QUrl, Slot from src._loader import ( AssembledTemplate, diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index 7c9d3a40..f710523e 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -44,7 +44,7 @@ def template_render_test_cases(self) -> dict[LayoutType, dict[str, str]]: _layout_categories_changed = Signal() @Property(list, notify=_layout_categories_changed) - def layout_categories(self) -> list[LayoutCategory]: # pyright: ignore[reportRedeclaration] + def layout_categories(self) -> list[LayoutCategory]: return self._layout_categories async def test_render( diff --git a/src/helpers/__init__.py b/src/helpers/__init__.py index 70c27fda..51fc1105 100644 --- a/src/helpers/__init__.py +++ b/src/helpers/__init__.py @@ -1,6 +1,7 @@ """ * Photoshop Helper Modules """ + from src.helpers.actions import * from src.helpers.adjustments import * from src.helpers.bounds import * diff --git a/src/helpers/actions.py b/src/helpers/actions.py index 127f4adc..888124b2 100644 --- a/src/helpers/actions.py +++ b/src/helpers/actions.py @@ -2,15 +2,11 @@ * Helpers: Actions """ -# Third Party Imports -from photoshop.api import DialogModes, ActionDescriptor, ActionReference +from photoshop.api import ActionDescriptor, ActionReference +from photoshop.api.enumerations import DialogModes -# Local Imports from src import APP -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Working With Actions """ @@ -30,4 +26,6 @@ def run_action(action_set: str, action: str) -> None: ref7.putName(APP.instance.sID("action"), action) ref7.putName(APP.instance.sID("actionSet"), action_set) desc310.putReference(APP.instance.sID("target"), ref7) - APP.instance.executeAction(APP.instance.sID("play"), desc310, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("play"), desc310, DialogModes.DisplayNoDialogs + ) diff --git a/src/helpers/adjustments.py b/src/helpers/adjustments.py index 2993435f..a2a1e6f5 100644 --- a/src/helpers/adjustments.py +++ b/src/helpers/adjustments.py @@ -2,29 +2,18 @@ * Helpers: Adjustment Layers """ -# Standard Library from typing import NotRequired, TypedDict, Unpack -# Third Party -from photoshop.api import ( - BlendMode, - DialogModes, - ActionList, - ActionDescriptor, - ActionReference, -) +from photoshop.api import ActionDescriptor, ActionList, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import BlendMode, DialogModes -# Local Imports from src import APP -from src.helpers.colors import get_color, apply_color, add_color_to_gradient, rgb_black +from src.helpers.colors import add_color_to_gradient, apply_color, get_color, rgb_black from src.schema.colors import ColorObject, GradientConfig -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Creating Adjustment Layers """ @@ -41,7 +30,9 @@ def create_vibrant_saturation(vibrancy: int, saturation: int) -> None: desc232 = ActionDescriptor() desc232.putInteger(APP.instance.sID("vibrance"), vibrancy) desc232.putInteger(APP.instance.sID("saturation"), saturation) - APP.instance.executeAction(APP.instance.sID("vibrance"), desc232, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("vibrance"), desc232, DialogModes.DisplayNoDialogs + ) class CreateColorLayerKwargs(TypedDict): @@ -87,7 +78,9 @@ def create_color_layer( APP.instance.sID("type"), APP.instance.sID("solidColorLayer"), desc3 ) desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) layer = docref.activeLayer if not isinstance(layer, ArtLayer): raise ValueError( @@ -197,7 +190,9 @@ def create_gradient_layer( ) desc2.putObject(APP.instance.sID("type"), APP.instance.sID("gradientLayer"), desc3) desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) layer = docref.activeLayer if not isinstance(layer, ArtLayer): raise ValueError("Failed to create a gradient color layer") diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index 5beb97be..24be1804 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -2,25 +2,18 @@ * Helpers: Bounds and Dimensions """ -# Standard Library Imports from contextlib import suppress from typing import TypedDict -# Third Party Imports -from photoshop.api import DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports from src import APP from src.helpers.descriptors import get_layer_action_descriptor from src.helpers.document import undo_action from src.helpers.layers import duplicate_group from src.utils.adobe import PS_EXCEPTIONS, LayerBounds -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Types """ diff --git a/src/helpers/colors.py b/src/helpers/colors.py index bbde3c1e..9471a539 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -2,28 +2,15 @@ * Helpers: Colors """ -# Standard Library Imports from collections.abc import Mapping -# Third Party Imports -from photoshop.api import ( - SolidColor, - DialogModes, - ActionList, - ActionDescriptor, - ColorModel, - LayerKind, -) +from photoshop.api import ActionDescriptor, ActionList, SolidColor from photoshop.api._artlayer import ArtLayer +from photoshop.api.enumerations import ColorModel, DialogModes, LayerKind -# Local Imports from src import APP, CON from src.enums.layers import LAYERS -from src.schema.colors import pinlines_color_map, ColorObject -from src.schema.colors import GradientConfig - -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs +from src.schema.colors import ColorObject, GradientConfig, pinlines_color_map """ * Color Conversions @@ -162,9 +149,8 @@ def get_color(color: ColorObject) -> SolidColor: return get_color(CON.colors[color]) # Hexadecimal return get_rgb_from_hex(color) - except (ValueError, TypeError): - raise ValueError(f"Invalid color notation given: {color}") - raise ValueError(f"Unrecognized color notation given: {color}") + except (ValueError, TypeError) as exc: + raise ValueError(f"Invalid color notation given: {color}") from exc def get_text_layer_color(layer: ArtLayer) -> SolidColor: @@ -367,4 +353,6 @@ def fill_layer_primary(): APP.instance.sID("fillContents"), APP.instance.sID("foregroundColor"), ) - APP.instance.executeAction(APP.instance.sID("fill"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("fill"), desc1, DialogModes.DisplayNoDialogs + ) diff --git a/src/helpers/descriptors.py b/src/helpers/descriptors.py index 4744e16d..b8a61cbe 100644 --- a/src/helpers/descriptors.py +++ b/src/helpers/descriptors.py @@ -1,19 +1,13 @@ """ * Helpers: PS Object Descriptors """ -# Standard Library Imports -# Third Party Imports -from photoshop.api import ActionDescriptor, DialogModes, ActionReference +from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports from src import APP -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Layer Action Descriptors """ diff --git a/src/helpers/document.py b/src/helpers/document.py index bab1c12a..018757b9 100644 --- a/src/helpers/document.py +++ b/src/helpers/document.py @@ -1,6 +1,7 @@ """ * Helpers: Documents """ + from collections.abc import Callable from pathlib import Path from typing import Any @@ -220,7 +221,9 @@ def jump_to_history_state(position: int): ref1 = ActionReference() ref1.putOffset(APP.instance.sID("historyState"), position) desc1.putReference(APP.instance.sID("target"), ref1) - APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs + ) def toggle_history_state(direction: str = "previous") -> None: @@ -237,7 +240,9 @@ def toggle_history_state(direction: str = "previous") -> None: APP.instance.sID(direction), ) desc1.putReference(APP.instance.sID("target"), ref1) - APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs + ) def undo_action() -> None: @@ -260,7 +265,9 @@ def reset_document(docref: Document | None = None) -> None: d1, r1 = ActionDescriptor(), ActionReference() r1.putName(APP.instance.sID("snapshotClass"), docref.name) d1.putReference(APP.instance.sID("target"), r1) - APP.instance.executeAction(APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs + ) """ @@ -349,7 +356,9 @@ def trim_transparent_pixels() -> None: desc258.putBoolean(APP.instance.sID("bottom"), True) desc258.putBoolean(APP.instance.sID("left"), True) desc258.putBoolean(APP.instance.sID("right"), True) - APP.instance.executeAction(APP.instance.sID("trim"), desc258, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("trim"), desc258, DialogModes.DisplayNoDialogs + ) """ @@ -436,7 +445,9 @@ def save_document_psb(path: Path) -> None: d1.putObject(APP.instance.sID("as"), APP.instance.sID("largeDocumentFormat"), d2) d1.putPath(APP.instance.sID("in"), str(path.with_suffix(".psb"))) d1.putBoolean(APP.instance.sID("lowerCase"), True) - APP.instance.executeAction(APP.instance.sID("save"), d1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("save"), d1, DialogModes.DisplayNoDialogs + ) def close_document( @@ -476,7 +487,9 @@ def rotate_document(angle: int) -> None: ) desc1.putReference(APP.instance.sID("target"), ref1) desc1.putUnitDouble(APP.instance.sID("angle"), APP.instance.sID("angleUnit"), angle) - APP.instance.executeAction(APP.instance.sID("rotateEventEnum"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("rotateEventEnum"), desc1, DialogModes.DisplayNoDialogs + ) def rotate_counter_clockwise() -> None: @@ -514,4 +527,6 @@ def paste_to_document(layer: ArtLayer | LayerSet | None = None): APP.instance.sID("antiAliasNone"), ) desc1.putClass(APP.instance.sID("as"), APP.instance.sID("pixel")) - APP.instance.executeAction(APP.instance.sID("paste"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("paste"), desc1, DialogModes.DisplayNoDialogs + ) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index 19f24511..265fa706 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -87,7 +87,9 @@ def set_layer_fx_visibility( action_list.putReference(ref) desc.putList(APP.instance.sID("target"), action_list) APP.instance.executeAction( - APP.instance.sID("show" if visible else "hide"), desc, DialogModes.DisplayNoDialogs + APP.instance.sID("show" if visible else "hide"), + desc, + DialogModes.DisplayNoDialogs, ) @@ -127,7 +129,9 @@ def clear_layer_fx(layer: ArtLayer | LayerSet | None) -> None: ) desc1600.putReference(APP.instance.sID("target"), ref126) APP.instance.executeAction( - APP.instance.sID("disableLayerStyle"), desc1600, DialogModes.DisplayNoDialogs + APP.instance.sID("disableLayerStyle"), + desc1600, + DialogModes.DisplayNoDialogs, ) except COMError: _logger.exception( @@ -150,7 +154,9 @@ def rasterize_layer_fx(layer: ArtLayer) -> None: APP.instance.sID("rasterizeItem"), APP.instance.sID("layerStyle"), ) - APP.instance.executeAction(APP.instance.sID("rasterizeLayer"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("rasterizeLayer"), desc1, DialogModes.DisplayNoDialogs + ) def copy_layer_fx( @@ -186,7 +192,9 @@ def copy_layer_fx( APP.instance.sID("layerEffects"), result_desc.getObjectValue(APP.instance.sID("layerEffects")), ) - APP.instance.executeAction(APP.instance.sID("set"), desc_set, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("set"), desc_set, DialogModes.DisplayNoDialogs + ) """ @@ -231,7 +239,9 @@ def apply_fx(layer: ArtLayer | LayerSet, effects: list[LayerEffects]) -> None: main_action.putObject( APP.instance.sID("to"), APP.instance.sID("layerEffects"), fx_action ) - APP.instance.executeAction(APP.instance.sID("set"), main_action, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("set"), main_action, DialogModes.DisplayNoDialogs + ) def apply_fx_bevel(action: ActionDescriptor, fx: EffectBevel) -> None: diff --git a/src/helpers/layers.py b/src/helpers/layers.py index 96740700..d346805b 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -76,7 +76,7 @@ def getLayer( if group and isinstance(group, str) else "" }', - exc_info=exc + exc_info=exc, ) @@ -130,7 +130,7 @@ def getLayerSet( if group and isinstance(group, str) else "" }', - exc_info=exc + exc_info=exc, ) @@ -167,7 +167,7 @@ def get_reference_layer( if group and isinstance(group, str) else "" }', - exc_info=exc + exc_info=exc, ) @@ -221,7 +221,9 @@ def merge_layers( select_layers(layers) # Merge layers and return result - APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs + ) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): @@ -272,7 +274,9 @@ def group_layers( desc1.putInteger(APP.instance.sID("layerSectionStart"), 0) desc1.putInteger(APP.instance.sID("layerSectionEnd"), 1) desc1.putString(APP.instance.cID("Nm "), name) - APP.instance.executeAction(APP.instance.cID("Mk "), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.cID("Mk "), desc1, DialogModes.DisplayNoDialogs + ) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): @@ -304,7 +308,9 @@ def duplicate_group(group: LayerSet, name: str) -> LayerSet: desc241.putReference(APP.instance.sID("target"), ref4) desc241.putString(APP.instance.sID("name"), name) desc241.putInteger(APP.instance.sID("version"), 5) - APP.instance.executeAction(APP.instance.sID("duplicate"), desc241, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("duplicate"), desc241, DialogModes.DisplayNoDialogs + ) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, LayerSet): @@ -323,7 +329,9 @@ def merge_group(group: LayerSet | None = None) -> None: """ if group: APP.instance.activeDocument.activeLayer = group - APP.instance.executeAction(APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("mergeLayersNew"), None, DialogModes.DisplayNoDialogs + ) """ @@ -343,7 +351,9 @@ def smart_layer( docref = docref or APP.instance.activeDocument if layer: docref.activeLayer = layer - APP.instance.executeAction(APP.instance.sID("newPlacedLayer"), None, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("newPlacedLayer"), None, DialogModes.DisplayNoDialogs + ) active_layer = APP.instance.activeDocument.activeLayer if not isinstance(active_layer, ArtLayer): @@ -384,7 +394,9 @@ def unpack_smart_layer( docref = docref or APP.instance.activeDocument docref.activeLayer = layer APP.instance.executeAction( - APP.instance.sID("placedLayerConvertToLayers"), None, DialogModes.DisplayNoDialogs + APP.instance.sID("placedLayerConvertToLayers"), + None, + DialogModes.DisplayNoDialogs, ) @@ -408,7 +420,9 @@ def lock_layer(layer: ArtLayer | LayerSet, protection: str = "protectAll") -> No d2.putBoolean(APP.instance.sID(protection), True) idlayerLocking = APP.instance.sID("layerLocking") d1.putObject(idlayerLocking, idlayerLocking, d2) - APP.instance.executeAction(APP.instance.sID("applyLocking"), d1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("applyLocking"), d1, DialogModes.DisplayNoDialogs + ) def unlock_layer(layer: ArtLayer | LayerSet) -> None: @@ -437,7 +451,9 @@ def select_layer(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None r1.putIdentifier(APP.instance.sID("layer"), layer.id) d1.putReference(APP.instance.sID("target"), r1) d1.putBoolean(APP.instance.sID("makeVisible"), make_visible) - APP.instance.executeAction(APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs + ) def select_layer_add(layer: ArtLayer | LayerSet, make_visible: bool = False) -> None: @@ -457,7 +473,9 @@ def select_layer_add(layer: ArtLayer | LayerSet, make_visible: bool = False) -> APP.instance.sID("addToSelection"), ) desc1.putBoolean(APP.instance.sID("makeVisible"), make_visible) - APP.instance.executeAction(APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("select"), desc1, DialogModes.DisplayNoDialogs + ) def select_layers(layers: Sequence[ArtLayer | LayerSet]) -> None: @@ -505,4 +523,6 @@ def select_no_layers() -> None: APP.instance.sID("targetEnum"), ) d1.putReference(APP.instance.sID("target"), r1) - APP.instance.executeAction(APP.instance.sID("selectNoLayers"), d1, DialogModes.DisplayNoDialogs) + APP.instance.executeAction( + APP.instance.sID("selectNoLayers"), d1, DialogModes.DisplayNoDialogs + ) diff --git a/src/helpers/masks.py b/src/helpers/masks.py index 3a7c1572..9ebc88ff 100644 --- a/src/helpers/masks.py +++ b/src/helpers/masks.py @@ -2,21 +2,16 @@ * Helpers: Masks """ -# Standard Library Imports from _ctypes import COMError -# Third Party Imports -from photoshop.api import ActionDescriptor, ActionReference, DialogModes +from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DialogModes -# Local Imports from src import APP from src.helpers.selection import select_canvas -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - """ * Copying Masks """ @@ -49,7 +44,9 @@ def copy_layer_mask( ) ref18.putIdentifier(APP.instance.sID("layer"), layer_from.id) desc1.putReference(APP.instance.sID("using"), ref18) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) def copy_vector_mask( @@ -81,7 +78,9 @@ def copy_vector_mask( ) ref3.putIdentifier(APP.instance.sID("layer"), layer_from.id) desc1.putReference(APP.instance.sID("using"), ref3) - APP.instance.executeAction(APP.instance.sID("make"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) """ @@ -90,7 +89,7 @@ def copy_vector_mask( def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: - """Sets the layer mask to apply only to layer effects in blending options. + """Sets the layer mask to apply to layer effects in blending options. Args: layer: ArtLayer or LayerSet object. @@ -105,7 +104,9 @@ def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: desc = ActionDescriptor() desc.putReference(APP.instance.sID("target"), ref) desc.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), layer_fx) - APP.instance.executeAction(APP.instance.sID("set"), desc, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc, DialogModes.DisplayNoDialogs + ) def set_layer_mask( @@ -126,7 +127,9 @@ def set_layer_mask( desc1.putReference(APP.instance.sID("target"), ref1) desc2.putBoolean(APP.instance.cID("UsrM"), visible) desc1.putObject(APP.instance.cID("T "), APP.instance.cID("Lyr "), desc2) - APP.instance.executeAction(APP.instance.cID("setd"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.cID("setd"), desc1, DialogModes.DisplayNoDialogs + ) def enable_mask(layer: ArtLayer | LayerSet | None = None) -> None: @@ -164,7 +167,9 @@ def apply_mask(layer: ArtLayer | LayerSet | None = None) -> None: ) desc1.putReference(APP.instance.sID("target"), ref1) desc1.putBoolean(APP.instance.sID("apply"), True) - APP.instance.executeAction(APP.instance.sID("delete"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("delete"), desc1, DialogModes.DisplayNoDialogs + ) def set_layer_vector_mask( @@ -185,7 +190,9 @@ def set_layer_vector_mask( desc1.putReference(APP.instance.sID("target"), ref1) desc2.putBoolean(APP.instance.sID("vectorMaskEnabled"), visible) desc1.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), desc2) - APP.instance.executeAction(APP.instance.sID("set"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("set"), desc1, DialogModes.DisplayNoDialogs + ) def enable_vector_mask(layer: ArtLayer | LayerSet | None = None) -> None: @@ -228,7 +235,9 @@ def enter_mask_channel(layer: ArtLayer | LayerSet | None = None): ) d1.putReference(APP.instance.sID("target"), r1) d1.putBoolean(APP.instance.sID("makeVisible"), True) - APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs + ) def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): @@ -248,7 +257,9 @@ def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): ) d1.putReference(APP.instance.sID("target"), r1) d1.putBoolean(APP.instance.sID("makeVisible"), True) - APP.instance.executeAction(APP.instance.sID("select"), d1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("select"), d1, DialogModes.DisplayNoDialogs + ) def create_mask(layer: ArtLayer | LayerSet | None = None): @@ -273,7 +284,9 @@ def create_mask(layer: ArtLayer | LayerSet | None = None): APP.instance.sID("userMaskEnabled"), APP.instance.sID("revealAll"), ) - APP.instance.executeAction(APP.instance.sID("make"), d1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("make"), d1, DialogModes.DisplayNoDialogs + ) def copy_to_mask( @@ -330,4 +343,6 @@ def delete_mask(layer: ArtLayer | LayerSet | None = None) -> None: APP.instance.sID("targetEnum"), ) desc1.putReference(APP.instance.sID("target"), ref1) - APP.instance.executeAction(APP.instance.sID("delete"), desc1, NO_DIALOG) + APP.instance.executeAction( + APP.instance.sID("delete"), desc1, DialogModes.DisplayNoDialogs + ) diff --git a/src/helpers/position.py b/src/helpers/position.py index 4ecffbeb..69d9c541 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -2,19 +2,16 @@ * Helpers: Positioning """ -# Standard Library Imports import math from collections.abc import Sequence from typing import Literal -# Third Party Imports -from photoshop.api import AnchorPosition, DialogModes from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api._selection import Selection +from photoshop.api.enumerations import AnchorPosition -# Local Imports from src import APP from src.enums.adobe import Dimensions from src.helpers.bounds import ( @@ -32,9 +29,6 @@ from src.helpers.text import get_font_size, set_text_size_and_leading from src.utils.adobe import ReferenceLayer -# QOL Definitions -NO_DIALOG = DialogModes.DisplayNoDialogs - # Positioning positions_horizontal = [Dimensions.Left, Dimensions.Right, Dimensions.CenterX] positions_vertical = [Dimensions.Top, Dimensions.Bottom, Dimensions.CenterY] diff --git a/src/helpers/text.py b/src/helpers/text.py index e17c3018..5bad7a6e 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -6,11 +6,7 @@ from logging import getLogger from typing import Literal, overload -from photoshop.api import ( - ActionDescriptor, - ActionList, - ActionReference, -) +from photoshop.api import ActionDescriptor, ActionList, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet diff --git a/src/templates/__init__.py b/src/templates/__init__.py index a04054b5..25118b2e 100644 --- a/src/templates/__init__.py +++ b/src/templates/__init__.py @@ -1,22 +1,23 @@ """ * Templates Modules """ + from src.templates._core import * from src.templates._cosmetic import * from src.templates._vector import * -from src.templates.normal import * -from src.templates.planeswalker import * -from src.templates.transform import * -from src.templates.mdfc import * from src.templates.adventure import * -from src.templates.leveler import * -from src.templates.saga import * -from src.templates.token import * -from src.templates.mutate import * +from src.templates.battle import * from src.templates.case import * from src.templates.classes import * -from src.templates.battle import * -from src.templates.prototype import * +from src.templates.leveler import * +from src.templates.mdfc import * +from src.templates.mutate import * +from src.templates.normal import * from src.templates.planar import * +from src.templates.planeswalker import * +from src.templates.prototype import * +from src.templates.saga import * from src.templates.split import * from src.templates.station import * +from src.templates.token import * +from src.templates.transform import * diff --git a/src/templates/_vector.py b/src/templates/_vector.py index 83999d8e..aca43a12 100644 --- a/src/templates/_vector.py +++ b/src/templates/_vector.py @@ -8,21 +8,18 @@ * Vector templates can be challenging for beginners, but have huge benefits. """ -# Standard Library Imports +from collections.abc import Callable, Sequence from functools import cached_property from typing import NotRequired, TypedDict -from collections.abc import Callable, Sequence -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import GradientConfig +from src.enums.layers import LAYERS from src.schema.colors import ( ColorObject, + GradientConfig, crown_color_map, indicator_color_map, pinlines_color_map, diff --git a/src/templates/adventure.py b/src/templates/adventure.py index 2fdb35c7..4fcfd97e 100644 --- a/src/templates/adventure.py +++ b/src/templates/adventure.py @@ -2,23 +2,19 @@ * Adventure Templates """ -# Standard Library -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import GradientConfig +import src.text_layers as text_classes +from src.enums.layers import LAYERS from src.layouts import AdventureLayout, NormalLayout -from src.schema.colors import ColorObject -from src.templates._vector import VectorTemplate +from src.schema.colors import ColorObject, GradientConfig from src.templates._core import NormalTemplate -import src.text_layers as text_classes +from src.templates._vector import VectorTemplate """ * Modifier Classes diff --git a/src/templates/battle.py b/src/templates/battle.py index bd785387..9fb4c4b1 100644 --- a/src/templates/battle.py +++ b/src/templates/battle.py @@ -2,21 +2,17 @@ * BATTLE TEMPLATES """ -# Standard Library -from functools import cached_property from collections.abc import Callable, Sequence +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd -from src.schema.colors import GradientConfig +from src.enums.layers import LAYERS from src.helpers.layers import get_reference_layer from src.layouts import BattleLayout, NormalLayout -from src.schema.colors import ColorObject, pinlines_color_map +from src.schema.colors import ColorObject, GradientConfig, pinlines_color_map from src.templates._core import BaseTemplate from src.templates._vector import VectorTemplate from src.text_layers import FormattedTextArea, TextField diff --git a/src/templates/case.py b/src/templates/case.py index bff38da5..17a4dd67 100644 --- a/src/templates/case.py +++ b/src/templates/case.py @@ -1,5 +1,5 @@ -from functools import cached_property from collections.abc import Callable +from functools import cached_property from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet @@ -156,7 +156,9 @@ def layer_positioning_case(self) -> None: ) # Get the exact gap between each layer left over - layer_heights = sum([get_layer_height(lyr) for lyr in self.case_line_layers]) + layer_heights = sum( + [get_layer_height(lyr) for lyr in self.case_line_layers] + ) gap = (ref_height - layer_heights) * (spacing / spacing_total) inside_gap = (ref_height - layer_heights) * ( (spacing + divider_height) / spacing_total diff --git a/src/templates/leveler.py b/src/templates/leveler.py index 0db7d2cb..e09b38f1 100644 --- a/src/templates/leveler.py +++ b/src/templates/leveler.py @@ -2,20 +2,17 @@ * LEVELER TEMPLATES """ -# Standard Library -from functools import cached_property from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd +import src.text_layers as text_classes +from src.enums.layers import LAYERS from src.layouts import LevelerLayout from src.templates._core import NormalTemplate -import src.text_layers as text_classes from src.utils.adobe import ReferenceLayer """ diff --git a/src/templates/mdfc.py b/src/templates/mdfc.py index 052470de..80cc9a5e 100644 --- a/src/templates/mdfc.py +++ b/src/templates/mdfc.py @@ -1,19 +1,17 @@ """ * MDFC TEMPLATES """ -# Standard Library -from functools import cached_property + from collections.abc import Callable +from functools import cached_property -# Third Party Imports from photoshop.api._artlayer import ArtLayer -# Local Imports -from src.enums.layers import LAYERS import src.helpers as psd +from src.enums.layers import LAYERS from src.templates._core import BaseTemplate, NormalTemplate from src.templates._vector import VectorTemplate -from src.text_layers import ScaledTextField, FormattedTextField +from src.text_layers import FormattedTextField, ScaledTextField """ * Modifier Classes @@ -35,13 +33,15 @@ class MDFCMod(BaseTemplate): """ @cached_property - def frame_layer_methods(self) -> list[Callable[[],None]]: + def frame_layer_methods(self) -> list[Callable[[], None]]: """Add MDFC frame layers step.""" parent_funcs = super().frame_layer_methods - return [*parent_funcs, self.enable_mdfc_layers] if self.is_mdfc else parent_funcs + return ( + [*parent_funcs, self.enable_mdfc_layers] if self.is_mdfc else parent_funcs + ) @cached_property - def text_layer_methods(self) -> list[Callable[[],None]]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add MDFC text layers step.""" parent_funcs = super().text_layer_methods return [*parent_funcs, self.text_layers_mdfc] if self.is_mdfc else parent_funcs @@ -113,15 +113,20 @@ def text_layers_mdfc(self) -> None: # Add mdfc text layers if self.text_layer_mdfc_right: - self.text.append(FormattedTextField( - layer=self.text_layer_mdfc_right, - contents=self.layout.other_face_right)) + self.text.append( + FormattedTextField( + layer=self.text_layer_mdfc_right, + contents=self.layout.other_face_right, + ) + ) if self.text_layer_mdfc_left: self.text.append( ScaledTextField( layer=self.text_layer_mdfc_left, contents=self.layout.other_face_left, - reference=self.text_layer_mdfc_right)) + reference=self.text_layer_mdfc_right, + ) + ) # Front and back side layers if self.is_front: diff --git a/src/templates/mutate.py b/src/templates/mutate.py index a95e1c20..113faa5f 100644 --- a/src/templates/mutate.py +++ b/src/templates/mutate.py @@ -1,6 +1,7 @@ """ * Mutate Templates """ + from collections.abc import Callable from functools import cached_property @@ -32,9 +33,11 @@ class MutateMod(NormalTemplate): """ @cached_property - def text_layer_methods(self) -> list[Callable[[],None]]: + def text_layer_methods(self) -> list[Callable[[], None]]: """Add Mutate text layers.""" - funcs = [self.text_layers_mutate] if isinstance(self.layout, MutateLayout) else [] + funcs = ( + [self.text_layers_mutate] if isinstance(self.layout, MutateLayout) else [] + ) return [*super().text_layer_methods, *funcs] """ @@ -62,8 +65,7 @@ def mutate_reference(self) -> ReferenceLayer | None: def process_layout_data(self) -> None: """Remove reminder text for mutate text if required.""" if self.config.remove_reminder and isinstance(self.layout, MutateLayout): - self.layout.mutate_text = strip_reminder_text( - self.layout.mutate_text) + self.layout.mutate_text = strip_reminder_text(self.layout.mutate_text) super().process_layout_data() """ @@ -79,7 +81,9 @@ def text_layers_mutate(self): text_classes.FormattedTextArea( layer=self.text_layer_mutate, contents=self.layout.mutate_text, - reference=self.mutate_reference)) + reference=self.mutate_reference, + ) + ) """ diff --git a/src/templates/normal.py b/src/templates/normal.py index 5a4a6fb3..2b3007cf 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1,6 +1,7 @@ """ * Normal Templates """ + from collections.abc import Callable, Iterable, Sequence from functools import cached_property @@ -114,7 +115,11 @@ def rules_text_and_pt_layers(self) -> None: self.text_layer_rules.textItem.color = psd.rgb_white() # Make the divider white - if self.layout.flavor_text and self.layout.oracle_text and self.config.flavor_divider: + if ( + self.layout.flavor_text + and self.layout.oracle_text + and self.config.flavor_divider + ): psd.enable_layer_fx(self.divider_layer) @@ -387,12 +392,16 @@ def template_suffix(self) -> str: @cached_property def is_promo_star(self) -> bool: """bool: Whether to enable the Promo Star overlay.""" - return self.config.get_bool_setting(section="FRAME", key="Promo.Star", default=False) + return self.config.get_bool_setting( + section="FRAME", key="Promo.Star", default=False + ) @cached_property def is_extended(self) -> bool: """bool: Whether to render using Extended Art framing.""" - return self.config.get_bool_setting(section="FRAME", key="Extended.Art", default=False) + return self.config.get_bool_setting( + section="FRAME", key="Extended.Art", default=False + ) @cached_property def is_align_collector_left(self) -> bool: @@ -1292,7 +1301,11 @@ def enable_crown(self) -> None: class BorderlessVectorTemplate( - NicknameVectorMod, VectorBorderlessMod, VectorMDFCMod, VectorTransformMod, VectorTemplate + NicknameVectorMod, + VectorBorderlessMod, + VectorMDFCMod, + VectorTransformMod, + VectorTemplate, ): """Borderless template first used in the Womens Day Secret Lair, redone with vector shapes.""" @@ -1415,20 +1428,29 @@ def size(self) -> str: @cached_property def color_limit(self) -> int: # Built in setting, dual and triple color split - return int(self.config.get_setting(section="COLORS", key="Max.Colors", default="2")) + 1 + return ( + int( + self.config.get_setting(section="COLORS", key="Max.Colors", default="2") + ) + + 1 + ) @cached_property def drop_shadow_enabled(self) -> bool: """Returns True if Drop Shadow text setting is enabled.""" return bool( - self.config.get_bool_setting(section="TEXT", key="Drop.Shadow", default=True) + self.config.get_bool_setting( + section="TEXT", key="Drop.Shadow", default=True + ) ) @cached_property def crown_texture_enabled(self) -> bool: """Returns True if Legendary crown clipping texture should be enabled.""" return bool( - self.config.get_bool_setting(section="FRAME", key="Crown.Texture", default=True) + self.config.get_bool_setting( + section="FRAME", key="Crown.Texture", default=True + ) ) @cached_property @@ -1462,14 +1484,18 @@ def multicolor_twins(self) -> bool: def multicolor_pt(self) -> bool: """Returns True if PT Box for multicolored cards should use the last color.""" return bool( - self.config.get_bool_setting(section="COLORS", key="Multicolor.PT", default=False) + self.config.get_bool_setting( + section="COLORS", key="Multicolor.PT", default=False + ) ) @cached_property def hybrid_colored(self) -> bool: """Returns True if Twins and PT should be colored on Hybrid cards.""" return bool( - self.config.get_bool_setting(section="COLORS", key="Hybrid.Colored", default=True) + self.config.get_bool_setting( + section="COLORS", key="Hybrid.Colored", default=True + ) ) @cached_property @@ -1485,7 +1511,9 @@ def front_face_colors(self) -> bool: def land_colorshift(self) -> bool: """Returns True if Land cards should use the darker brown color.""" return bool( - self.config.get_bool_setting(section="COLORS", key="Land.Colorshift", default=False) + self.config.get_bool_setting( + section="COLORS", key="Land.Colorshift", default=False + ) ) @cached_property @@ -1529,7 +1557,9 @@ def art_frame(self) -> str: @cached_property def is_basic_land(self) -> bool: """Disable basic land watermark if Textless is enabled.""" - if bool(self.config.get_bool_setting(section="FRAME", key="Textless", default=False)): + if bool( + self.config.get_bool_setting(section="FRAME", key="Textless", default=False) + ): return False return super().is_basic_land @@ -1540,12 +1570,16 @@ def is_textless(self) -> bool: [self.layout.oracle_text, self.layout.flavor_text, self.is_basic_land] ): return True - return self.config.get_bool_setting(section="FRAME", key="Textless", default=False) + return self.config.get_bool_setting( + section="FRAME", key="Textless", default=False + ) @cached_property def is_colored_nickname(self) -> bool: """Return True if nickname plate should be colored.""" - return self.config.get_bool_setting(section="COLORS", key="Nickname", default=False) + return self.config.get_bool_setting( + section="COLORS", key="Nickname", default=False + ) @cached_property def is_multicolor(self) -> bool: @@ -1575,7 +1609,9 @@ def is_centered(self) -> bool: @cached_property def is_pt_enabled(self) -> bool: """Return True if a separate Power/Toughness text layer is used for this render.""" - return self.is_creature and (not self.is_textless or not self.config.symbol_enabled) + return self.is_creature and ( + not self.is_textless or not self.config.symbol_enabled + ) @cached_property def is_drop_shadow(self) -> bool: diff --git a/src/templates/planar.py b/src/templates/planar.py index 900571aa..fc44338b 100644 --- a/src/templates/planar.py +++ b/src/templates/planar.py @@ -1,6 +1,7 @@ """ * PLANAR TEMPLATES """ + from functools import cached_property from photoshop.api._artlayer import ArtLayer diff --git a/src/templates/prototype.py b/src/templates/prototype.py index b7d83ecf..f9f5df56 100644 --- a/src/templates/prototype.py +++ b/src/templates/prototype.py @@ -1,6 +1,7 @@ """ * Templates: Prototype """ + from collections.abc import Callable from functools import cached_property diff --git a/src/templates/saga.py b/src/templates/saga.py index 485860c3..eb1d213a 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -255,7 +255,9 @@ def layer_positioning_saga(self) -> None: for _ in range(len(self.saga_ability_layers) - 1) ] psd.position_dividers( - dividers=dividers, layers=self.saga_ability_layers, docref=self.docref + dividers=dividers, + layers=self.saga_ability_layers, + docref=self.docref, ) @@ -560,7 +562,13 @@ def enabled_shapes(self) -> list[ArtLayer | LayerSet | None]: @cached_property def pinlines_mask( self, - ) -> MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None: + ) -> ( + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None + ): """Mask hiding pinlines effects inside textbox and art frame.""" if self.pinlines_group and ( layer := psd.getLayer( @@ -576,7 +584,11 @@ def pinlines_mask( def enabled_masks( self, ) -> list[ - MaskAction | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] | ArtLayer | LayerSet | None + MaskAction + | tuple[ArtLayer | LayerSet, ArtLayer | LayerSet] + | ArtLayer + | LayerSet + | None ]: """Support a pinlines mask.""" return [self.pinlines_mask] diff --git a/src/templates/split.py b/src/templates/split.py index d936680e..fd5cbb89 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -66,7 +66,10 @@ def pinline_gradient_locations(self) -> list[dict[int, list[int | float]]]: @cached_property def color_limit(self) -> int: """One more than the max number of colors this card can split by.""" - return self.config.get_int_setting(section="COLORS", key="Max.Colors", default=2) + 1 + return ( + self.config.get_int_setting(section="COLORS", key="Max.Colors", default=2) + + 1 + ) # endregion Settings @@ -481,7 +484,9 @@ def create_watermark(self) -> None: ) # Apply opacity, blending, and effects - wm.opacity = wm_details.get("opacity", self.config.watermark_opacity) + wm.opacity = wm_details.get( + "opacity", self.config.watermark_opacity + ) wm.blendMode = BlendMode.ColorBurn psd.apply_fx(wm, self.watermark_fxs[i]) else: diff --git a/src/templates/station.py b/src/templates/station.py index 098a2cbb..82609995 100644 --- a/src/templates/station.py +++ b/src/templates/station.py @@ -1,5 +1,5 @@ -from functools import cached_property from collections.abc import Callable +from functools import cached_property from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet diff --git a/src/templates/transform.py b/src/templates/transform.py index 20640214..a61b40ce 100644 --- a/src/templates/transform.py +++ b/src/templates/transform.py @@ -248,4 +248,5 @@ def pinlines_layer(self) -> ArtLayer | None: class IxalanTemplate(IxalanMod, NormalTemplate): """Template for the back face lands for transforming cards from Ixalan block.""" + pass diff --git a/src/utils/adobe.py b/src/utils/adobe.py index fc1ff572..c37036e4 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -56,7 +56,7 @@ KeyError, ValueError, TypeError, - OSError + OSError, ) PS_ERROR_CODES: dict[int, str] = { @@ -67,10 +67,9 @@ -2147023170: "Unable to make connection with Photoshop, please check the FAQ for solutions.", # Response: "Invalid index." -2147352565: "Failed to load a PSD template or other file, ensure template file isn't corrupted " - "and that you have allocated enough scratch disk space and RAM to Photoshop.", + "and that you have allocated enough scratch disk space and RAM to Photoshop.", # Response: "Exception occurred." -2147352567: "Photoshop does not appear to be installed. If Photoshop is installed, check the FAQ for solutions.", - # --> COMError Messages that don't contain a message string, but have been investigated # Reference: https://docs.google.com/document/d/1j5xkWCWeHEFUZUaVtF59ccvAm9zTFsZ1qJcmFsXvkCM -2147220261: "Invalid data type passed to action descriptor function.", @@ -80,7 +79,6 @@ -2147212704: "Action descriptor or layer object key/property is missing.", # Reference: https://docs.google.com/document/d/1Oz69nNO0jR9qBbhjv3SVlMmk8iRnZY1LG7VaX7pqB-U -2147220262: "Photoshop tried to load a PSD template or file that doesn't exist.", - # --> COMError Messages that don't contain a message string, but have been identified with testing # Test case: Pass a value to layer.textItem.color that isn't a SolidColor object -2147220279: "Wrong type of value passed to a Photoshop object property.", @@ -88,7 +86,7 @@ # Also: Observed when accessing the textItem property of a layer that contains an uninstalled font -2147213327: "Tried to interact with a text layer that is rasterized or has an uninstalled font.", # Test case: Delete a layer object, then try to delete it again. - -2147213404: "Tried to delete a layer that doesn't exist." + -2147213404: "Tried to delete a layer that doesn't exist.", } @@ -98,6 +96,7 @@ class LayerDimensions(TypedDict): """Calculated layer dimension info for a layer.""" + width: float height: float center_x: float @@ -107,6 +106,7 @@ class LayerDimensions(TypedDict): top: float bottom: float + """ * Util Classes """ @@ -122,9 +122,11 @@ def __init__(self, env: AppEnvironment | None = None): # Set error dialog state with suppress(Exception): - self.displayDialogs = DialogModes.DisplayErrorDialogs if ( - env and env.PS_ERROR_DIALOG - ) else DialogModes.DisplayNoDialogs + self.displayDialogs = ( + DialogModes.DisplayErrorDialogs + if (env and env.PS_ERROR_DIALOG) + else DialogModes.DisplayNoDialogs + ) """ * Handler Properties @@ -145,6 +147,7 @@ def is_error_dialog_enabled(self) -> bool: class PhotoshopHandler(ApplicationHandler): """Wrapper for a single global Photoshop Application object equipped with soft loading, caching mechanisms, environment settings, and more.""" + _instance = None _window_handle: int | None = None @@ -190,7 +193,7 @@ def refresh_app(self): except Exception as e: # Photoshop is either busy or unresponsive return OSError(get_photoshop_error_message(e)) - + # Clear window handle as it might have changed self._window_handle = None return @@ -297,8 +300,7 @@ def charIDToStringID(self, index: int) -> str: Returns: str: String representation of Char ID. """ - return self.typeIDToStringID( - self.charIDToTypeID(index)) + return self.typeIDToStringID(self.charIDToTypeID(index)) @cache def stringIDToCharID(self, index: int) -> str: @@ -310,8 +312,7 @@ def stringIDToCharID(self, index: int) -> str: Returns: str: Character representation of String ID. """ - return self.typeIDToCharID( - self.stringIDToTypeID(index)) + return self.typeIDToCharID(self.stringIDToTypeID(index)) """ * Executing Action Descriptors @@ -321,7 +322,7 @@ def executeAction( self, event_id: int, descriptor: ActionDescriptor | None = None, - display_dialogs: DialogModes = DialogModes.DisplayNoDialogs + display_dialogs: DialogModes = DialogModes.DisplayNoDialogs, ) -> ActionDescriptor: """Middleware to allow all dialogs when an error occurs upon calling executeAction in development mode. @@ -335,7 +336,9 @@ def executeAction( """ if self.is_error_dialog_enabled(): # Allow error dialogs if enabled in the app environment - return super().executeAction(event_id, descriptor, DialogModes.DisplayErrorDialogs) + return super().executeAction( + event_id, descriptor, DialogModes.DisplayErrorDialogs + ) return super().executeAction(event_id, descriptor, display_dialogs) """ @@ -345,17 +348,17 @@ def executeAction( @cache def supports_target_text_replace(self) -> bool: """bool: Checks if Photoshop version supports targeted text replacement.""" - return self.version_meets_requirement('22.0.0') + return self.version_meets_requirement("22.0.0") @cache def supports_webp(self) -> bool: """bool: Checks if Photoshop version supports WEBP files.""" - return self.version_meets_requirement('23.2.0') + return self.version_meets_requirement("23.2.0") @cache def supports_generative_fill(self) -> bool: """Checks if Photoshop version supports Generative Fill.""" - return self.version_meets_requirement('24.6.0') + return self.version_meets_requirement("24.6.0") def version_meets_requirement(self, value: str) -> bool: """Checks if Photoshop version meets or exceeds required value. @@ -388,7 +391,9 @@ class ReferenceLayer(ArtLayer): """A static ArtLayer whose properties such as width or height are not going to change. Most often used as a reference to position or size other layers.""" - def __init__(self, parent: Photoshop | None = None, app: PhotoshopHandler | None = None): + def __init__( + self, parent: Photoshop | None = None, app: PhotoshopHandler | None = None + ): self._global_app = app if app else PhotoshopHandler() super().__init__(parent=parent) @@ -397,10 +402,10 @@ def __init__(self, parent: Photoshop | None = None, app: PhotoshopHandler | None """ def duplicate( - self, - relativeObject: Layer | None = None, - insertionLocation: ElementPlacement | None = None - ) -> ArtLayer: + self, + relativeObject: Layer | None = None, + insertionLocation: ElementPlacement | None = None, + ) -> ArtLayer: """Duplicates the layer and returns it as a `ReferenceLayer` object.""" return ReferenceLayer(super().duplicate(relativeObject, insertionLocation)) @@ -437,7 +442,7 @@ def action_getter(self) -> ActionDescriptor: Action descriptor info object about the layer. """ ref = ActionReference() - ref.putIdentifier(self.sID('layer'), self.id) + ref.putIdentifier(self.sID("layer"), self.id) return self._global_app.executeActionGet(ref) """ @@ -456,15 +461,16 @@ def bounds_no_effects(self) -> LayerBounds: d = self.action_getter try: # Try getting bounds no effects - bounds = d.getObjectValue(self.sID('boundsNoEffects')) + bounds = d.getObjectValue(self.sID("boundsNoEffects")) except PS_EXCEPTIONS: # Try getting bounds - bounds = d.getObjectValue(self.sID('bounds')) + bounds = d.getObjectValue(self.sID("bounds")) return ( - bounds.getInteger(self.sID('left')), - bounds.getInteger(self.sID('top')), - bounds.getInteger(self.sID('right')), - bounds.getInteger(self.sID('bottom'))) + bounds.getInteger(self.sID("left")), + bounds.getInteger(self.sID("top")), + bounds.getInteger(self.sID("right")), + bounds.getInteger(self.sID("bottom")), + ) # Fallback to layer object bounds property return self.bounds @@ -475,22 +481,22 @@ def bounds_no_effects(self) -> LayerBounds: @cached_property def dims(self) -> LayerDimensions: """LayerDimensions: Returns dimensions of the layer (cached), including: - - bounds (left, right, top, bottom) - - height - - width - - center_x - - center_y + - bounds (left, right, top, bottom) + - height + - width + - center_x + - center_y """ return self.get_dimensions_from_bounds(self.bounds) @cached_property def dims_no_effects(self) -> LayerDimensions: """LayerDimensions: Returns dimensions of the layer (cached) without layer effects applied, including: - - bounds (left, right, top, bottom) - - height - - width - - center_x - - center_y + - bounds (left, right, top, bottom) + - height + - width + - center_x + - center_y """ return self.get_dimensions_from_bounds(self.bounds_no_effects) @@ -499,7 +505,9 @@ def dims_no_effects(self) -> LayerDimensions: """ @staticmethod - def get_dimensions_from_bounds(bounds: tuple[float,float,float,float]) -> LayerDimensions: + def get_dimensions_from_bounds( + bounds: tuple[float, float, float, float], + ) -> LayerDimensions: """Compute width and height based on a set of bounds given. Args: @@ -515,8 +523,11 @@ def get_dimensions_from_bounds(bounds: tuple[float,float,float,float]) -> LayerD height=height, center_x=round((width / 2) + bounds[0]), center_y=round((height / 2) + bounds[1]), - left=int(bounds[0]), right=int(bounds[2]), - top=int(bounds[1]), bottom=int(bounds[3])) + left=int(bounds[0]), + right=int(bounds[2]), + top=int(bounds[1]), + bottom=int(bounds[3]), + ) """ @@ -524,7 +535,7 @@ def get_dimensions_from_bounds(bounds: tuple[float,float,float,float]) -> LayerD """ -def try_photoshop[**P,T](func: Callable[P, T]) -> Callable[P, T | None]: +def try_photoshop[**P, T](func: Callable[P, T]) -> Callable[P, T | None]: """Decorator to handle trying to run a Photoshop action but allowing exceptions to fail silently. Args: @@ -533,11 +544,13 @@ def try_photoshop[**P,T](func: Callable[P, T]) -> Callable[P, T | None]: Returns: The wrapped function. """ + def wrapper(*args: P.args, **kwargs: P.kwargs): try: return func(*args, **kwargs) except PS_EXCEPTIONS: return + return wrapper @@ -556,11 +569,13 @@ def get_photoshop_error_message(err: Exception) -> str: Proper user response for this exception. """ return ( - "Photoshop is currently busy, close any dialogs and stop any actions.\n" - ) if 'busy' in str(err).lower() else ( - "Photoshop does not appear to be installed on your system.\n" - "Please close Proxyshop and install a fresh copy of Photoshop,\n" - "if Photoshop is installed, view the FAQ for troubleshooting.\n" + ("Photoshop is currently busy, close any dialogs and stop any actions.\n") + if "busy" in str(err).lower() + else ( + "Photoshop does not appear to be installed on your system.\n" + "Please close Proxyshop and install a fresh copy of Photoshop,\n" + "if Photoshop is installed, view the FAQ for troubleshooting.\n" + ) ) diff --git a/src/utils/build.py b/src/utils/build.py index 192a4231..2397324d 100644 --- a/src/utils/build.py +++ b/src/utils/build.py @@ -19,8 +19,8 @@ # Directory definitions SRC: Path = PATH.CWD -DST: Path = SRC / 'dist' -DIST_CONFIG: Path = PATH.SRC_DATA / 'build' / 'dist.yml' +DST: Path = SRC / "dist" +DIST_CONFIG: Path = PATH.SRC_DATA / "build" / "dist.yml" """ @@ -30,22 +30,26 @@ class DistConfigNames(TypedDict): """Maps the recognized names table in 'dist.yml' configuration.""" + zip: str class DistConfigSpec(TypedDict): """Maps the named spec file paths table in 'dist.yml' configuration.""" + release: list[str] console: list[str] class DistConfigMakeDir(TypedDict): """Maps the generated dirs table in 'dist.yml' configuration.""" + paths: list[list[str]] class DistConfigCopyDir(TypedDict): """Maps the settings for a dir which need to be copied to the release dir.""" + paths: list[list[str]] files: NotRequired[list[list[str]]] exclude_ext: NotRequired[list[str]] @@ -56,6 +60,7 @@ class DistConfigCopyDir(TypedDict): class DistConfig(TypedDict): """Maps the 'dist.yml' configuration which governs build behavior.""" + names: DistConfigNames spec: DistConfigSpec make: DistConfigMakeDir @@ -73,7 +78,7 @@ def generate_version_file(version: str): Args: version: Version string to use in the version file. """ - with open(Path(SRC, '__VERSION__.py'), 'w') as f: + with open(Path(SRC, "__VERSION__.py"), "w") as f: f.write(f"version='{version}'") @@ -84,7 +89,7 @@ def make_directories(config: DistConfig) -> None: config: Config data from 'dist.yml'. """ DST.mkdir(mode=777, parents=True, exist_ok=True) - for path in config['make']['paths']: + for path in config["make"]["paths"]: Path(DST, *path).mkdir(mode=777, parents=True, exist_ok=True) @@ -94,7 +99,7 @@ def copy_directory( x_files: list[str], x_dirs: list[str], x_ext: list[str] | None = None, - recursive: bool = True + recursive: bool = True, ) -> None: """Copy a directory from src to dst. @@ -139,21 +144,20 @@ def copy_app_files(config: DistConfig) -> None: Args: config: Config data from 'dist.yml'. """ - for _, DIR in config.get('copy', {}).items(): + for _, DIR in config.get("copy", {}).items(): # Copy directories - for path in DIR.get('paths', []): + for path in DIR.get("paths", []): copy_directory( src=Path(SRC, *path), dst=Path(DST, *path), - x_files=DIR.get('exclude_files', []), - x_dirs=DIR.get('exclude_dirs', []), - x_ext=DIR.get('exclude_ext', []), - recursive=bool(DIR.get('recursive', True))) + x_files=DIR.get("exclude_files", []), + x_dirs=DIR.get("exclude_dirs", []), + x_ext=DIR.get("exclude_ext", []), + recursive=bool(DIR.get("recursive", True)), + ) # Copy files - for file in DIR.get('files', []): - copy2( - src=Path(SRC, *file), - dst=Path(DST, *file)) + for file in DIR.get("files", []): + copy2(src=Path(SRC, *file), dst=Path(DST, *file)) """ @@ -169,17 +173,17 @@ def clear_build_files(clear_dist: bool = True) -> None: """ # Run pyclean on main directory and venv run(("pyclean", "-v", "."), check=True) - if (SRC / '.venv').is_dir(): + if (SRC / ".venv").is_dir(): run(("pyclean", "-v", ".venv")) # Remove build directory with suppress(Exception): - rmtree(os.path.join(SRC, 'build')) + rmtree(os.path.join(SRC, "build")) # Optionally remove dist directory if clear_dist: with suppress(Exception): - rmtree(os.path.join(SRC, 'dist')) + rmtree(os.path.join(SRC, "dist")) """ @@ -196,8 +200,7 @@ def build_zip(filename: str) -> None: ZIP_SRC = os.path.join(SRC, filename) with zipfile.ZipFile(ZIP_SRC, "w", zipfile.ZIP_DEFLATED) as zipf: for fp in glob(os.path.join(DST, "**/*"), recursive=True): - zipf.write(fp, arcname=fp.replace( - os.path.commonpath([DST, fp]), "")) + zipf.write(fp, arcname=fp.replace(os.path.commonpath([DST, fp]), "")) move(ZIP_SRC, os.path.join(DST, filename)) @@ -205,7 +208,7 @@ def build_release( version: str | None = None, console: bool = False, beta: bool = False, - zipped: bool = True + zipped: bool = True, ) -> None: """Build the app to executable release. @@ -223,12 +226,14 @@ def build_release( make_directories(dist_config) # Use provided version or fallback to project defined - version = version or get_project_version((SRC / 'pyproject').with_suffix('.toml')) + version = version or get_project_version((SRC / "pyproject").with_suffix(".toml")) generate_version_file(version) # Run Pyinstaller - spec_path: list[str] = dist_config['spec']['console'] if console else dist_config['spec']['release'] - PyInstaller.__main__.run([str(Path(SRC, *spec_path)), '--clean']) + spec_path: list[str] = ( + dist_config["spec"]["console"] if console else dist_config["spec"]["release"] + ) + PyInstaller.__main__.run([str(Path(SRC, *spec_path)), "--clean"]) # Copy our essential app files copy_app_files(dist_config) @@ -236,15 +241,16 @@ def build_release( # Build zip release if requested if zipped: build_zip( - filename=dist_config['names']['zip'].format( + filename=dist_config["names"]["zip"].format( version=version, - console='-console' if console else '', - beta='-beta' if beta else '' - )) + console="-console" if console else "", + beta="-beta" if beta else "", + ) + ) # Clear build files, except dist clear_build_files(clear_dist=False) - os.remove(Path(SRC, '__VERSION__.py')) + os.remove(Path(SRC, "__VERSION__.py")) """ @@ -262,8 +268,7 @@ def get_python_modules(path: Path) -> list[str]: List of module names. """ return [ - f[:-3] for f in os.listdir(path) if - f.endswith('.py') and f != "__init__.py" + f[:-3] for f in os.listdir(path) if f.endswith(".py") and f != "__init__.py" ] @@ -273,14 +278,14 @@ def generate_mkdocs(path: str) -> None: Args: path: Path to a python module directory. """ - directory = SRC / 'src' / path - parent = 'temps' if path == 'templates' else path + directory = SRC / "src" / path + parent = "temps" if path == "templates" else path for module in get_python_modules(directory): functions: list[str] = [] classes: list[str] = [] # Scan for functions and classes to document - with open(Path(directory, module).with_suffix('.py')) as file: + with open(Path(directory, module).with_suffix(".py")) as file: for node in ast.parse(file.read()).body: if isinstance(node, ast.FunctionDef): functions.append(f"src.{path}.{module}.{node.name}") @@ -289,31 +294,36 @@ def generate_mkdocs(path: str) -> None: # Write MD file with open( - Path(SRC, 'docs', parent, module).with_suffix('.md'), - "w", encoding='utf-8' + Path(SRC, "docs", parent, module).with_suffix(".md"), "w", encoding="utf-8" ) as f: - if module[0] == '_': + if module[0] == "_": module = module[1:] - module = module.title().replace('_', ' ') + module = module.title().replace("_", " ") f.write(f"# {module}\n") if classes: - [f.write( - f"\n::: {cls}\n" - f" options:\n" - f" show_root_members_full_path: false\n" - f" show_category_heading: true\n" - f" show_root_full_path: false\n" - f" show_root_heading: true\n" - ) for cls in classes] + [ + f.write( + f"\n::: {cls}\n" + f" options:\n" + f" show_root_members_full_path: false\n" + f" show_category_heading: true\n" + f" show_root_full_path: false\n" + f" show_root_heading: true\n" + ) + for cls in classes + ] if functions: - [f.write( - f"\n::: {func}\n" - f" options:\n" - f" show_root_members_full_path: false\n" - f" show_category_heading: true\n" - f" show_root_full_path: false\n" - f" show_root_heading: true\n" - ) for func in functions] + [ + f.write( + f"\n::: {func}\n" + f" options:\n" + f" show_root_members_full_path: false\n" + f" show_category_heading: true\n" + f" show_root_full_path: false\n" + f" show_root_heading: true\n" + ) + for func in functions + ] def generate_nav(headers: list[str], paths: list[str]) -> list[dict[str, list[str]]]: @@ -328,9 +338,11 @@ def generate_nav(headers: list[str], paths: list[str]) -> list[dict[str, list[st """ nav: list[dict[str, list[str]]] = [] for i, path in enumerate(paths): - parent = 'temps' if path == 'templates' else path - md_files = sorted([f for f in os.listdir(Path(SRC, 'docs', parent)) if f.endswith('.md')]) - nav_items = [f'{parent}/{f}' for f in md_files] # remove .md extension + parent = "temps" if path == "templates" else path + md_files = sorted( + [f for f in os.listdir(Path(SRC, "docs", parent)) if f.endswith(".md")] + ) + nav_items = [f"{parent}/{f}" for f in md_files] # remove .md extension nav.append({headers[i]: nav_items}) return nav @@ -341,15 +353,17 @@ def update_mkdocs_yml(nav: list[dict[str, list[str]]]) -> None: Args: nav: List of nav objects to insert into nav data in mkdocs.yml. """ - mkdocs_yml = load_data_file(Path(SRC, 'mkdocs.yml')) - mkdocs_yml['nav'] = [ - {'Home': 'index.md'}, - {'Changelog': 'changelog.md'}, - {'Reference': [ - *nav, - {'Text Layer Classes': 'text_layers.md'}, - {'Card Layouts': 'layouts.md'} - ]}, - {'License': 'license.md'} + mkdocs_yml = load_data_file(Path(SRC, "mkdocs.yml")) + mkdocs_yml["nav"] = [ + {"Home": "index.md"}, + {"Changelog": "changelog.md"}, + { + "Reference": [ + *nav, + {"Text Layer Classes": "text_layers.md"}, + {"Card Layouts": "layouts.md"}, + ] + }, + {"License": "license.md"}, ] - dump_data_file(mkdocs_yml, Path(SRC, 'mkdocs.yml'), config={'sort_keys': False}) + dump_data_file(mkdocs_yml, Path(SRC, "mkdocs.yml"), config={"sort_keys": False}) diff --git a/src/utils/download.py b/src/utils/download.py index 39c09cb4..129ae8c3 100644 --- a/src/utils/download.py +++ b/src/utils/download.py @@ -1,13 +1,12 @@ """ * Utils: Downloads and Updates """ -# Standard Library Imports + import shutil +from collections.abc import Callable from dataclasses import dataclass from pathlib import Path -from collections.abc import Callable -# Third Party Imports import requests import yarl from omnitils.fetch import download_file @@ -21,8 +20,9 @@ class HEADERS: Default = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " - "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/39.0.2171.95 Safari/537.36"} + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/39.0.2171.95 Safari/537.36" + } """ @@ -30,7 +30,9 @@ class HEADERS: """ -def download_cloudfront(url: yarl.URL, path: Path, callback: Callable[[int, int], None] | None = None) -> bool: +def download_cloudfront( + url: yarl.URL, path: Path, callback: Callable[[int, int], None] | None = None +) -> bool: """Download a template from cloudfront cached Amazon S3 bucket. Args: @@ -42,16 +44,13 @@ def download_cloudfront(url: yarl.URL, path: Path, callback: Callable[[int, int] True if download is successful, otherwise False. """ # Get a temp file - temp_path = get_temporary_file(path=path, ext='.amzn') + temp_path = get_temporary_file(path=path, ext=".amzn") # Start the download try: - download_file( - url=url, - path=temp_path, - callback=callback) + download_file(url=url, path=temp_path, callback=callback) shutil.move(temp_path, path) unpack_archive(path) - except (requests.RequestException, FileExistsError, FileNotFoundError): + except requests.RequestException, FileExistsError, FileNotFoundError: return False return True diff --git a/src/utils/fonts.py b/src/utils/fonts.py index 0bc95b43..3737422f 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -25,6 +25,7 @@ # Precompile font version pattern REG_FONT_VER: re.Pattern[str] = re.compile(r"\b(\d+\.\d+)\b") + class FontCache: @cached_property def user_fonts_dir(self) -> Path: @@ -34,6 +35,7 @@ def user_fonts_dir(self) -> Path: def user_font_files(self) -> list[Path]: return [path for path in self.user_fonts_dir.iterdir() if path.is_file()] + FONT_CACHE = FontCache() """ @@ -43,6 +45,7 @@ def user_font_files(self) -> list[Path]: class FontDetails(TypedDict): """Font name and current version.""" + name: str | None version: str | None @@ -70,7 +73,10 @@ def register_font(ps_app: PhotoshopHandler, font_path: str) -> bool: _logger.debug(f"Font '{osp.basename(font_path)}' added to font cache.") hwnd_broadcast = wintypes.HWND(-1) windll.user32.SendMessageW( - hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) + hwnd_broadcast, + wintypes.UINT(0x001D), + wintypes.WPARAM(0), + wintypes.LPARAM(0), ) ps_app.refreshFonts() except Exception: @@ -97,7 +103,10 @@ def unregister_font(ps_app: PhotoshopHandler, font_path: str) -> bool: _logger.debug(f"Font {osp.basename(font_path)} removed from font cache!") hwnd_broadcast = wintypes.HWND(-1) windll.user32.SendMessageW( - hwnd_broadcast, wintypes.UINT(0x001D), wintypes.WPARAM(0), wintypes.LPARAM(0) + hwnd_broadcast, + wintypes.UINT(0x001D), + wintypes.WPARAM(0), + wintypes.LPARAM(0), ) ps_app.refreshFonts() except Exception: @@ -110,10 +119,13 @@ def unregister_font(ps_app: PhotoshopHandler, font_path: str) -> bool: * Photoshop Font Utils """ -def is_font_available_in_ps(ps_app: PhotoshopHandler, font_post_script_name: str) -> bool: + +def is_font_available_in_ps( + ps_app: PhotoshopHandler, font_post_script_name: str +) -> bool: try: return bool(ps_app.fonts[font_post_script_name]) - except (COMError, KeyError): + except COMError, KeyError: return False @@ -126,22 +138,24 @@ def get_ps_font_dict(ps_app: PhotoshopHandler) -> dict[str, str]: Returns: Dictionary with postScriptName as key, display name as value. """ - fonts: dict[str,str] = {} + fonts: dict[str, str] = {} for f in ps_app.fonts: with suppress(*PS_EXCEPTIONS): fonts[f.name] = f.postScriptName return fonts + class FontInfo(TypedDict): name: str | None count: int + def get_document_fonts( ps_app: PhotoshopHandler, container: LayerSet | Document | None = None, fonts: dict[str, FontInfo] | None = None, - ps_fonts: dict[str, str] | None = None -) -> dict[str,FontInfo]: + ps_fonts: dict[str, str] | None = None, +) -> dict[str, FontInfo]: """Get a list of all fonts used in a given Photoshop Document or LayerSet. Args: @@ -164,14 +178,13 @@ def get_document_fonts( # Log a new font or update an existing one font = str(layer.textItem.font) if font in fonts: - fonts[font]['count'] += 1 + fonts[font]["count"] += 1 else: - fonts[font] = { - 'name': ps_fonts.get(font, None), - 'count': 1 - } + fonts[font] = {"name": ps_fonts.get(font, None), "count": 1} except PS_EXCEPTIONS as exc: - _logger.warning(f"Couldn't read font from layer: {layer.name}", exc_info=exc) + _logger.warning( + f"Couldn't read font from layer: {layer.name}", exc_info=exc + ) # Make additional calls for nested groups for group in container.layerSets: @@ -221,7 +234,9 @@ def get_fonts_from_folder(folder: str | os.PathLike[str]) -> dict[str, FontDetai # Get a list of the font names in your `fonts` folder with suppress(Exception): ext = (".otf", ".ttf", ".OTF", ".TTF") - local_fonts = [osp.join(folder, f) for f in os.listdir(folder) if f.endswith(ext)] + local_fonts = [ + osp.join(folder, f) for f in os.listdir(folder) if f.endswith(ext) + ] return {n[0]: n[1] for n in [get_font_details(f) for f in local_fonts] if n} return {} @@ -233,10 +248,10 @@ def get_installed_fonts_dict() -> dict[str, FontDetails]: Dictionary with postScriptName as key, and tuple of display name and version as value. """ with suppress(Exception): - system_fonts_dir = os.path.join(os.path.join(os.environ['WINDIR']), 'Fonts') + system_fonts_dir = os.path.join(os.path.join(os.environ["WINDIR"]), "Fonts") return { **get_fonts_from_folder(FONT_CACHE.user_fonts_dir), - **get_fonts_from_folder(system_fonts_dir) + **get_fonts_from_folder(system_fonts_dir), } return {} @@ -247,8 +262,7 @@ def get_installed_fonts_dict() -> dict[str, FontDetails]: def get_outdated_fonts( - fonts: dict[str, FontDetails], - missing: dict[str, FontDetails] | None = None + fonts: dict[str, FontDetails], missing: dict[str, FontDetails] | None = None ) -> dict[str, FontDetails]: """Compares the version of each font given against installed fonts. @@ -267,17 +281,21 @@ def get_outdated_fonts( # Check fonts for any outdated for name, data in fonts.items(): - if name in installed and installed[name].get('version'): - version = data['version'] - installed_version = installed[name]['version'] - if version and installed_version and parse(installed_version) < parse(version): + if name in installed and installed[name].get("version"): + version = data["version"] + installed_version = installed[name]["version"] + if ( + version + and installed_version + and parse(installed_version) < parse(version) + ): outdated[name] = data # Check missing fonts to see if found in installed dict, if so check for version change for k in list(missing.keys()): - if k in installed and installed[k].get('version'): - installed_version = installed[k]['version'] - missing_version = missing[k]['version'] + if k in installed and installed[k].get("version"): + installed_version = installed[k]["version"] + missing_version = missing[k]["version"] if ( installed_version and missing_version @@ -290,8 +308,7 @@ def get_outdated_fonts( def get_missing_fonts( - ps_app: PhotoshopHandler, - fonts: dict[str, FontDetails] + ps_app: PhotoshopHandler, fonts: dict[str, FontDetails] ) -> tuple[dict[str, FontDetails], dict[str, FontDetails]]: """Checks each font to see if it's present in the Photoshop font list. @@ -317,8 +334,7 @@ def get_missing_fonts( def check_app_fonts( - ps_app: PhotoshopHandler, - folders: list[str | os.PathLike[str]] + ps_app: PhotoshopHandler, folders: list[str | os.PathLike[str]] ) -> tuple[dict[str, FontDetails], dict[str, FontDetails]]: """Checks each font in a folder to see if it is installed or outdated. diff --git a/src/utils/manual_actions.py b/src/utils/manual_actions.py index cf38d4c9..e3d5a237 100644 --- a/src/utils/manual_actions.py +++ b/src/utils/manual_actions.py @@ -1,14 +1,11 @@ import subprocess from os import unlink from tempfile import NamedTemporaryFile -from typing import TypeVar from pydantic import BaseModel -T = TypeVar("T", bound=BaseModel) - -def manually_modify_model(model: T, text_editing_program: str) -> T: +def manually_modify_model[T: BaseModel](model: T, text_editing_program: str) -> T: """Opens model as JSON in a text editing program, e.g. notepad, for manual editing, waits for editing to be done and then converts the edited JSON back to model form. diff --git a/src/utils/windows.py b/src/utils/windows.py index a8d1f796..0ddc74a9 100644 --- a/src/utils/windows.py +++ b/src/utils/windows.py @@ -49,7 +49,7 @@ def enum_callback(handle: int, extra: list[int]): if ( # Try to skip windows that aren't visible in taskbar not ( - win32gui.GetWindowLong(handle, win32con.GWL_STYLE) + win32gui.GetWindowLong(handle, win32con.GWL_STYLE) # pyright: ignore[reportUnknownMemberType] & win32con.WS_EX_APPWINDOW ) # win32gui.GetParent(handle) @@ -66,10 +66,10 @@ def enum_callback(handle: int, extra: list[int]): ) try: for module_handle in win32process.EnumProcessModules(process_handle): - process_file_path: str = win32process.GetModuleFileNameEx( + process_file_path: str = win32process.GetModuleFileNameEx( # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] process_handle, module_handle ) - if process_file_path.endswith(suffix): + if process_file_path.endswith(suffix): # pyright: ignore[reportUnknownMemberType] return handle finally: win32api.CloseHandle(process_handle) From e91810804c791f683345c921fbd7fd768a78cb07 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 5 Apr 2026 13:04:06 +0300 Subject: [PATCH 104/190] build(pyproject.toml): Update dependencies, import formatting rules and type checking preferences --- poetry.lock | 1695 +++++++++++++++++++++++------------------------- pyproject.toml | 54 +- 2 files changed, 831 insertions(+), 918 deletions(-) diff --git a/poetry.lock b/poetry.lock index d06a4337..5efb130e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -41,14 +41,14 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, - {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, ] [[package]] @@ -246,23 +246,28 @@ files = [ [[package]] name = "brotlicffi" -version = "1.2.0.0" +version = "1.2.0.1" description = "Python CFFI bindings to the Brotli library" optional = false python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation == \"PyPy\"" files = [ - {file = "brotlicffi-1.2.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9458d08a7ccde8e3c0afedbf2c70a8263227a68dea5ab13590593f4c0a4fd5f4"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84e3d0020cf1bd8b8131f4a07819edee9f283721566fe044a20ec792ca8fd8b7"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33cfb408d0cff64cd50bef268c0fed397c46fbb53944aa37264148614a62e990"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-win32.whl", hash = "sha256:23e5c912fdc6fd37143203820230374d24babd078fc054e18070a647118158f6"}, - {file = "brotlicffi-1.2.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:f139a7cdfe4ae7859513067b736eb44d19fae1186f9e99370092f6915216451b"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fa102a60e50ddbd08de86a63431a722ea216d9bc903b000bf544149cc9b823dc"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d3c4332fc808a94e8c1035950a10d04b681b03ab585ce897ae2a360d479037c"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb4eb5830026b79a93bf503ad32b2c5257315e9ffc49e76b2715cffd07c8e3db"}, - {file = "brotlicffi-1.2.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3832c66e00d6d82087f20a972b2fc03e21cd99ef22705225a6f8f418a9158ecc"}, - {file = "brotlicffi-1.2.0.0.tar.gz", hash = "sha256:34345d8d1f9d534fcac2249e57a4c3c8801a33c9942ff9f8574f67a175e17adb"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c85e65913cf2b79c57a3fdd05b98d9731d9255dc0cb696b09376cc091b9cddd"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:535f2d05d0273408abc13fc0eebb467afac17b0ad85090c8913690d40207dac5"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce17eb798ca59ecec67a9bb3fd7a4304e120d1cd02953ce522d959b9a84d58ac"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-win32.whl", hash = "sha256:3c9544f83cb715d95d7eab3af4adbbef8b2093ad6382288a83b3a25feb1a57ec"}, + {file = "brotlicffi-1.2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:625f8115d32ae9c0740d01ea51518437c3fbaa3e78d41cb18459f6f7ac326000"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:91ba5f0ccc040f6ff8f7efaf839f797723d03ed46acb8ae9408f99ffd2572cf4"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9a670c6811af30a4bd42d7116dc5895d3b41beaa8ed8a89050447a0181f5ce"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3314a3476f59e5443f9f72a6dff16edc0c3463c9b318feaef04ae3e4683f5a"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-win32.whl", hash = "sha256:82ea52e2b5d3145b6c406ebd3efb0d55db718b7ad996bd70c62cec0439de1187"}, + {file = "brotlicffi-1.2.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:da2e82a08e7778b8bc539d27ca03cdd684113e81394bfaaad8d0dfc6a17ddede"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e015af99584c6db1490a69a210c765953e473e63adc2d891ac3062a737c9e851"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37cb587d32bf7168e2218c455e22e409ad1f3157c6c71945879a311f3e6b6abf"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d6ba65dd528892b4d9960beba2ae011a753620bcfc66cf6fa3cee18d7b0baa4"}, + {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2a5575653b0672638ba039b82fda56854934d7a6a24d4b8b5033f73ab43cbc1"}, + {file = "brotlicffi-1.2.0.1.tar.gz", hash = "sha256:c20d5c596278307ad06414a6d95a892377ea274a5c6b790c2548c009385d621c"}, ] [package.dependencies] @@ -407,137 +412,153 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, - {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, - {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.2" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, + {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, + {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, ] [package.dependencies] @@ -584,14 +605,14 @@ tomlkit = ">=0.8.0,<1.0.0" [[package]] name = "comtypes" -version = "1.4.15" +version = "1.4.16" description = "Pure Python COM package" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "comtypes-1.4.15-py3-none-any.whl", hash = "sha256:cda90486de8762ec57d7ce04e68721920911f3f03415cb29afdf7609c427c7e3"}, - {file = "comtypes-1.4.15.tar.gz", hash = "sha256:c72b9968a4e920087183a364c5a13b174e02b11c302cdd92690d14c95ac1b312"}, + {file = "comtypes-1.4.16-py3-none-any.whl", hash = "sha256:e18d85179ff12955524c5a8c3bc09cb3c0d890f1da4d7123d14244c7b78f84c8"}, + {file = "comtypes-1.4.16.tar.gz", hash = "sha256:cd66d1add01265cface4df51ba1e31cd1657e04463c281c802e737e79e1ba93c"}, ] [[package]] @@ -757,74 +778,74 @@ files = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.25.2" description = "A platform independent file lock." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047"}, - {file = "filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3"}, + {file = "filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70"}, + {file = "filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694"}, ] [[package]] name = "fonttools" -version = "4.61.1" +version = "4.62.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24"}, - {file = "fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958"}, - {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da"}, - {file = "fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6"}, - {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1"}, - {file = "fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881"}, - {file = "fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47"}, - {file = "fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6"}, - {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09"}, - {file = "fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37"}, - {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb"}, - {file = "fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9"}, - {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87"}, - {file = "fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56"}, - {file = "fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a"}, - {file = "fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7"}, - {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e"}, - {file = "fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2"}, - {file = "fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796"}, - {file = "fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d"}, - {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8"}, - {file = "fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0"}, - {file = "fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261"}, - {file = "fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9"}, - {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c"}, - {file = "fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e"}, - {file = "fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5"}, - {file = "fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd"}, - {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3"}, - {file = "fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d"}, - {file = "fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c"}, - {file = "fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b"}, - {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd"}, - {file = "fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e"}, - {file = "fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c"}, - {file = "fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75"}, - {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063"}, - {file = "fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2"}, - {file = "fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c"}, - {file = "fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c"}, - {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa"}, - {file = "fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91"}, - {file = "fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19"}, - {file = "fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba"}, - {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7"}, - {file = "fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118"}, - {file = "fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5"}, - {file = "fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b"}, - {file = "fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371"}, - {file = "fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69"}, + {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c"}, + {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a"}, + {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3"}, + {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23"}, + {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d"}, + {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae"}, + {file = "fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed"}, + {file = "fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9"}, + {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7"}, + {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14"}, + {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7"}, + {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b"}, + {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1"}, + {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416"}, + {file = "fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53"}, + {file = "fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2"}, + {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974"}, + {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9"}, + {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936"}, + {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392"}, + {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04"}, + {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d"}, + {file = "fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c"}, + {file = "fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42"}, + {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79"}, + {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe"}, + {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68"}, + {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1"}, + {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069"}, + {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9"}, + {file = "fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24"}, + {file = "fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056"}, + {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca"}, + {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca"}, + {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782"}, + {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae"}, + {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7"}, + {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a"}, + {file = "fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800"}, + {file = "fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e"}, + {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82"}, + {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260"}, + {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4"}, + {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b"}, + {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87"}, + {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c"}, + {file = "fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a"}, + {file = "fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e"}, + {file = "fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd"}, + {file = "fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d"}, ] [package.extras] @@ -894,13 +915,14 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3. [[package]] name = "griffelib" -version = "2.0.0" +version = "2.0.2" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f"}, + {file = "griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1"}, + {file = "griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e"}, ] [package.extras] @@ -946,14 +968,14 @@ files = [ [[package]] name = "identify" -version = "2.6.17" +version = "2.6.18" description = "File identification library for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0"}, - {file = "identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d"}, + {file = "identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737"}, + {file = "identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd"}, ] [package.extras] @@ -1103,214 +1125,129 @@ files = [ [[package]] name = "kiwisolver" -version = "1.4.9" +version = "1.5.0" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, - {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, - {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, - {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, - {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, - {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, - {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, - {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, - {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, - {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, - {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, - {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, - {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, - {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, - {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, - {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, - {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, - {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, - {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, - {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, - {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, - {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, - {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, - {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, - {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, - {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, - {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, - {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, - {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, - {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, - {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, -] - -[[package]] -name = "librt" -version = "0.8.1" -description = "Mypyc runtime library" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, - {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, - {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, - {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, - {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, - {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, - {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, - {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, - {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, - {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, - {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, - {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, - {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, - {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, - {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, - {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, - {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, - {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, - {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, - {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, - {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, - {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, - {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, - {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, - {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, - {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, - {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, - {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, - {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, - {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, - {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, - {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, - {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, - {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, - {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, - {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, - {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, - {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, - {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, - {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, - {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, - {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, - {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, - {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, - {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, - {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, - {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, - {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, - {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, - {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, - {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, - {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, - {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, - {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, - {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, - {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, - {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, - {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, - {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, - {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, - {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, - {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, - {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, - {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, - {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, - {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1"}, + {file = "kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a"}, ] [[package]] @@ -1698,29 +1635,30 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-gen-files" -version = "0.6.0" +version = "0.6.1" description = "MkDocs plugin to programmatically generate documentation pages during the build" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_gen_files-0.6.0-py3-none-any.whl", hash = "sha256:815af15f3e2dbfda379629c1b95c02c8e6f232edf2a901186ea3b204ab1135b2"}, - {file = "mkdocs_gen_files-0.6.0.tar.gz", hash = "sha256:52022dc14dcc0451e05e54a8f5d5e7760351b6701eff816d1e9739577ec5635e"}, + {file = "mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189"}, + {file = "mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763"}, ] [package.dependencies] -mkdocs = ">=1.4.1" +mkdocs = ">=1.4.1,<=1.6.1" +properdocs = ">=1.6.5" [[package]] name = "mkdocs-get-deps" -version = "0.2.0" -description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +version = "0.2.2" +description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, - {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, + {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, + {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, ] [package.dependencies] @@ -1746,14 +1684,14 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.2.1" +version = "7.2.2" description = "Mkdocs Markdown includer plugin." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_include_markdown_plugin-7.2.1-py3-none-any.whl", hash = "sha256:30da634c568ea5d5f9e5881d51f80ac30d8c5f891cec160344ad7a0fdaea6286"}, - {file = "mkdocs_include_markdown_plugin-7.2.1.tar.gz", hash = "sha256:5d94db87b06cd303619dbaebba5f7f43a3ded7fd7709451d26f08c176376ffec"}, + {file = "mkdocs_include_markdown_plugin-7.2.2-py3-none-any.whl", hash = "sha256:f2ec4487cf32d3e33ca528f9366f20fb9280ded9c8d1630eb2bbda244962dcd1"}, + {file = "mkdocs_include_markdown_plugin-7.2.2.tar.gz", hash = "sha256:f052ccb741eccf498116b826c1d78a2d761c56747372594709441cee0963fbc9"}, ] [package.dependencies] @@ -1765,14 +1703,14 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.7.3" +version = "9.7.6" description = "Documentation that simply works" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d"}, - {file = "mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22"}, + {file = "mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba"}, + {file = "mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69"}, ] [package.dependencies] @@ -1781,7 +1719,7 @@ backrefs = ">=5.7.post1" colorama = ">=0.4" jinja2 = ">=3.1" markdown = ">=3.2" -mkdocs = ">=1.6" +mkdocs = ">=1.6,<2" mkdocs-material-extensions = ">=1.3" paginate = ">=0.5" pygments = ">=2.16" @@ -1840,18 +1778,19 @@ mkdocs-material = ">=8.3.3" [[package]] name = "mkdocs-same-dir" -version = "0.1.3" +version = "0.1.4" description = "MkDocs plugin to allow placing mkdocs.yml in the same directory as documentation" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mkdocs_same_dir-0.1.3-py3-none-any.whl", hash = "sha256:3d094649e2e47efcf90a8b0051a4c2b837aaf4137a28c8e334ba9465804a317e"}, - {file = "mkdocs_same_dir-0.1.3.tar.gz", hash = "sha256:c849556b1d79ae270947f41bb89d442aa1e858ab6ec6423eb178ae76a7f984fc"}, + {file = "mkdocs_same_dir-0.1.4-py3-none-any.whl", hash = "sha256:75103f722b2ab86a860c1a850d52ab2867b2e334f6b0c0dbe1c6d09ccd9c9897"}, + {file = "mkdocs_same_dir-0.1.4.tar.gz", hash = "sha256:68e8a6cd023833d1b797563b57c0bc732156170a00f615404f1d0e8544ed7e39"}, ] [package.dependencies] -mkdocs = ">=1.0.3" +mkdocs = ">=1.2,<=1.6.1" +properdocs = ">=1.6.5" [[package]] name = "mkdocstrings" @@ -2069,79 +2008,6 @@ check = ["check-manifest", "flake8", "flake8-black", "isort (>=5.0.3)", "pygment test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "hypothesis", "pyannotate", "pytest", "pytest-cov"] type = ["mypy", "mypy-extensions"] -[[package]] -name = "mypy" -version = "1.19.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, - {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, - {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, - {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, - {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, - {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, - {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, - {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, - {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, - {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, - {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, - {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, - {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, - {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, - {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, - {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, - {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, - {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, - {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, - {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, - {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, - {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, - {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, - {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, - {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, - {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, - {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, -] - -[package.dependencies] -librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - [[package]] name = "nodeenv" version = "1.10.0" @@ -2156,84 +2022,84 @@ files = [ [[package]] name = "numpy" -version = "2.4.2" +version = "2.4.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["dev"] files = [ - {file = "numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7"}, - {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73"}, - {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1"}, - {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32"}, - {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390"}, - {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413"}, - {file = "numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda"}, - {file = "numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695"}, - {file = "numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e"}, - {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27"}, - {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548"}, - {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f"}, - {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460"}, - {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba"}, - {file = "numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f"}, - {file = "numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85"}, - {file = "numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98"}, - {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef"}, - {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7"}, - {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499"}, - {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb"}, - {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7"}, - {file = "numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110"}, - {file = "numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622"}, - {file = "numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913"}, - {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab"}, - {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82"}, - {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f"}, - {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554"}, - {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257"}, - {file = "numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657"}, - {file = "numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b"}, - {file = "numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1"}, - {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74"}, - {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a"}, - {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325"}, - {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909"}, - {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a"}, - {file = "numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a"}, - {file = "numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75"}, - {file = "numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef"}, - {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d"}, - {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8"}, - {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5"}, - {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e"}, - {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a"}, - {file = "numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443"}, - {file = "numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236"}, - {file = "numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0"}, - {file = "numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0"}, - {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40"}, + {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d"}, + {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502"}, + {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd"}, + {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5"}, + {file = "numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e"}, + {file = "numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e"}, + {file = "numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8"}, + {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121"}, + {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e"}, + {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44"}, + {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d"}, + {file = "numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827"}, + {file = "numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a"}, + {file = "numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c"}, + {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103"}, + {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83"}, + {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed"}, + {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959"}, + {file = "numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed"}, + {file = "numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf"}, + {file = "numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93"}, + {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e"}, + {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40"}, + {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e"}, + {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392"}, + {file = "numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008"}, + {file = "numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8"}, + {file = "numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f"}, + {file = "numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc"}, + {file = "numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74"}, + {file = "numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150"}, + {file = "numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871"}, + {file = "numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e"}, + {file = "numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119"}, + {file = "numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0"}, ] [[package]] @@ -2367,103 +2233,103 @@ resolved_reference = "4cd7f34986a17da959295dc698650d5e5b58e3f0" [[package]] name = "pillow" -version = "12.1.1" +version = "12.2.0" description = "Python Imaging Library (fork)" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0"}, - {file = "pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713"}, - {file = "pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b"}, - {file = "pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b"}, - {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4"}, - {file = "pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4"}, - {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e"}, - {file = "pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff"}, - {file = "pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40"}, - {file = "pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23"}, - {file = "pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9"}, - {file = "pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32"}, - {file = "pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38"}, - {file = "pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5"}, - {file = "pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090"}, - {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af"}, - {file = "pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b"}, - {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5"}, - {file = "pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d"}, - {file = "pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c"}, - {file = "pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563"}, - {file = "pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80"}, - {file = "pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052"}, - {file = "pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984"}, - {file = "pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79"}, - {file = "pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293"}, - {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397"}, - {file = "pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0"}, - {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3"}, - {file = "pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35"}, - {file = "pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a"}, - {file = "pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6"}, - {file = "pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523"}, - {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e"}, - {file = "pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9"}, - {file = "pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6"}, - {file = "pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60"}, - {file = "pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2"}, - {file = "pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850"}, - {file = "pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289"}, - {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e"}, - {file = "pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717"}, - {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a"}, - {file = "pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029"}, - {file = "pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b"}, - {file = "pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1"}, - {file = "pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a"}, - {file = "pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da"}, - {file = "pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc"}, - {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c"}, - {file = "pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8"}, - {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20"}, - {file = "pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13"}, - {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf"}, - {file = "pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524"}, - {file = "pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986"}, - {file = "pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c"}, - {file = "pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3"}, - {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af"}, - {file = "pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f"}, - {file = "pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642"}, - {file = "pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd"}, - {file = "pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202"}, - {file = "pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f"}, - {file = "pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f"}, - {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f"}, - {file = "pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e"}, - {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0"}, - {file = "pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb"}, - {file = "pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f"}, - {file = "pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15"}, - {file = "pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f"}, - {file = "pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8"}, - {file = "pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9"}, - {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60"}, - {file = "pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7"}, - {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f"}, - {file = "pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586"}, - {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce"}, - {file = "pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8"}, - {file = "pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36"}, - {file = "pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b"}, - {file = "pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735"}, - {file = "pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e"}, - {file = "pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4"}, + {file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"}, + {file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"}, + {file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"}, + {file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"}, + {file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"}, + {file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"}, + {file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"}, + {file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"}, + {file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"}, + {file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"}, + {file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"}, + {file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"}, + {file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"}, + {file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"}, + {file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"}, + {file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"}, + {file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"}, + {file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"}, + {file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"}, + {file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"}, + {file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"}, + {file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"}, + {file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"}, + {file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"}, ] [package.extras] @@ -2476,14 +2342,14 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, - {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, + {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, + {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, ] [[package]] @@ -2668,47 +2534,76 @@ files = [ {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, ] +[[package]] +name = "properdocs" +version = "1.6.7" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd"}, + {file = "properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +packaging = ">=20.5" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] + [[package]] name = "psd-tools" -version = "1.13.1" +version = "1.14.2" description = "Python package for working with Adobe Photoshop PSD files" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "psd_tools-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35f743f0b794756febf5bf5f3cf49b4853e9246bd82bf9736d0430563905bde0"}, - {file = "psd_tools-1.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d47b3aa2595ff099a9a4ac2cfae6534e377f0bf4b7be2fb2f53e7b477f079290"}, - {file = "psd_tools-1.13.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:258081498acf9e251f59c9f420c5fc566d7b712323fed23a33c4fa46b90b3851"}, - {file = "psd_tools-1.13.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:16101523054fde08a97131e2a03e8d3e27c1a64a4d30b939c63af141a161396f"}, - {file = "psd_tools-1.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4e783a8c5a5f705bb91b4059659732427c1fdd608e1081776dda64adbf925efe"}, - {file = "psd_tools-1.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9cc381f8fa01c9726eeb74f1a2f808ad0c2f2a645b9a5038661dcfe1c976cc10"}, - {file = "psd_tools-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:f11631ddaaec43a983fa7670ba9096b2f21d96562c88694941de8d00a41ee2c1"}, - {file = "psd_tools-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c9df9f4bfe42e3a5cebcb0c7bdd4a67e5d002475e3ec57ff3e91562747e156f"}, - {file = "psd_tools-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37e6f876fddb6bc0f8ff1ca683bfc2d63cf0ce919dd2dd6d29a7e8219cd55735"}, - {file = "psd_tools-1.13.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ab92e844107b2c2d2e0a3df3e1417e0f10dab8dba412d707b1c0740e2fb9fc"}, - {file = "psd_tools-1.13.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f76643bff1e8b0b79b8ce0c6db7f77635198a69d4665ee70e176df5e56930aa"}, - {file = "psd_tools-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97fea076e6cdddbaf8c0c9764b8a8f9d7a231f644aec1fc8670069f52dc827e7"}, - {file = "psd_tools-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a435804aa1d5c08d58a4f9d03835e3cdda0adf21e2a69bd68cbac2685bc107c0"}, - {file = "psd_tools-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:1d3d69d92b1b7c8e66f3f1c2e9b75482a3156dd2607bf533707a55f7fac1d3ce"}, - {file = "psd_tools-1.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:dde29ca13834a1d3f229e8ebec006e667e86a561654937c19d55edb84fe2095b"}, - {file = "psd_tools-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f8cb18642bca7c625b0b5f3d3176c260279ee0769b514dc10c92b08134043561"}, - {file = "psd_tools-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a08e550e4d1810315ef909d2bb2fb3d30f12ced30d834d340de2e227b54c3334"}, - {file = "psd_tools-1.13.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f601ec0ee41f9a369576d8fd3dce1c151f368404a883731a5ad18b54b3a105b3"}, - {file = "psd_tools-1.13.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7faf5d57c6c88c77faf8d62712e5eff4c4265049cacf0a354a3d39bcb22b813e"}, - {file = "psd_tools-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4fe00ab68cd212da4e7194d505f77ea02cf1c87a7799328c61a255994003c6c8"}, - {file = "psd_tools-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f5659f91b18fb5b53ca6b0a86e5c8049cc27c15f20fb01c203b80f5ebd4a9749"}, - {file = "psd_tools-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b55146f3145cc2d1217cdfd15eb297206432c8562c8998b613c66534cc0852"}, - {file = "psd_tools-1.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:7846b7adc7bce5a640e740671d111495e9e3b8a040ab565bf8f2917d5b7b0d1b"}, - {file = "psd_tools-1.13.1-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:3b203ecbb2bcb13b3dba60f054ec97a2a8c0a0c57a0b585020e8e3d0d988be22"}, - {file = "psd_tools-1.13.1-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:6b7ea99f024b2c49de83da356a90b8483e20cd04196f4cfc95aab556b8253729"}, - {file = "psd_tools-1.13.1-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68466fbe62b5fa60afb378264ad09067e7a3ab3803aefebd9dc80dc45c3c73f"}, - {file = "psd_tools-1.13.1-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457da0c2fe999f6e3712c0ff370c92e45289c2bf1d3ff833b7fb569ebbedebdf"}, - {file = "psd_tools-1.13.1-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9826975864428fd60e556bdff9fb2cbf1b562ed85ba89a702f4ee74cea932629"}, - {file = "psd_tools-1.13.1-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e45d879a6c7ad48bc9982b1f79b94617b66616203451cb2b773dd09372b5b403"}, - {file = "psd_tools-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:b77ab02b84fc3b065915d283a6057e94b385276b5ed035add0513194faa230a1"}, - {file = "psd_tools-1.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:3245c695df71bd6a9661771f39da8bb0474439fea8d1e620a3b976208de43605"}, - {file = "psd_tools-1.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:9bfdd95230d263c90b2a76ee1f9e396732549a1c5ffb9b644cdd8f40dc63c34f"}, - {file = "psd_tools-1.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:059661d2f2caeeffe8f5a2b1140e9e0941f711d05e9bf11276334393e4a58238"}, + {file = "psd_tools-1.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4d84744bf55bcc17bdcb43b87784dd0eb91758d1ff1fda042f4f0f2c5775141"}, + {file = "psd_tools-1.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05816681b0b976052e8c305162310a287eaf5897fbf253e442a9fcac8f4627d2"}, + {file = "psd_tools-1.14.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c815ca9a505f2680cd6edda1c08c05fdd2edf48a60847196d1256cb673175a"}, + {file = "psd_tools-1.14.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a6d9243ee27c4df103df63043290a57b5d39185bb7ada06d51b49e0d0781df6"}, + {file = "psd_tools-1.14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a015b2ab7bbf52bf8ed6bc671201252648e47a54975627cf01cd748f1c0336a2"}, + {file = "psd_tools-1.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e858a613ed0e7a7b95633fe773cb065321617eeb40cbca0cbccbc57a255154"}, + {file = "psd_tools-1.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:23d9a495aaded4caf554df72c51a066a49473c667714b08b9b39e448f32de6a4"}, + {file = "psd_tools-1.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aeaa742816c711a2e30f8896b8c07c9941c7b2d1b25ebcf1e4359f5d578c1ae8"}, + {file = "psd_tools-1.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e17a8d96ad42ed69fcd7f8c06b62158581925ae4070a7087f70c60e268ce39"}, + {file = "psd_tools-1.14.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84ff0ab83a2c9bff36a7e5b040973c3895d09460e6c0d51906e111c0cae78db0"}, + {file = "psd_tools-1.14.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35af03b0618c53c4f41683644f858c60c5fb4b9a323c65ddcf02c4b6b56ef482"}, + {file = "psd_tools-1.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a2321fa8df22535982faa9b2084b6055eb8297a9bc46c92709c46c9e3f7c6c39"}, + {file = "psd_tools-1.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f68ac8798fd2d634eb00e32307d3d0074877ce9bba8c41c4755f752cf0f39355"}, + {file = "psd_tools-1.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:cd78029e09f830765b9d56419db0443abd5b477d7f3a582a5128af78fdb6f211"}, + {file = "psd_tools-1.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:0097f41175efabc1739515cb1fb626c39879a382d07c04803118502802fca66c"}, + {file = "psd_tools-1.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0f40256c6fb985965abbd1eca51d7cbd3421842381e4c5f91c7aeaf8314eb817"}, + {file = "psd_tools-1.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23afcd57ad54f1632c1e9592df7a584a9f4891e5f43df8a9757b5a2c18c4e6a"}, + {file = "psd_tools-1.14.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:087a008a831ec810a0c6709cba4fadb5698fc70aa231a3a87ceda7b746085391"}, + {file = "psd_tools-1.14.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c1b9a79121c0f67656b14ee01b5c2adccdf3523ec242f0991601c1043b965db"}, + {file = "psd_tools-1.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ce526ef1f130a21c382cfc07995b0ecc692a0af0f55d812de1b90025f01ce1e"}, + {file = "psd_tools-1.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23a5b890ef459bd7265c3ff93614142549a1961558944514a231ee90a969e8b1"}, + {file = "psd_tools-1.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:fd77b5762698442c0b2465ab37d218e9bddb9e08ac78f94831b21d410ffaecea"}, + {file = "psd_tools-1.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:29184e35af61d109dab6503378f4aee057e37d5162813b2680247428d9f52460"}, + {file = "psd_tools-1.14.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:17f84d68a6b1aa785cfce884546ac4d86eb5e13d9a26d37c30237e356526de21"}, + {file = "psd_tools-1.14.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:b0031d859f80db5547d13fff7c60f046beaaa8b0463bcc548848117b15c784aa"}, + {file = "psd_tools-1.14.2-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddffe3216cc733dec2995f92e936e059291529d80c4c9c992554429e22c4d71a"}, + {file = "psd_tools-1.14.2-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:868c9607b93d07bc5723ab32733d0658fc187da2efe5e993e2f94e3b6d1f45c0"}, + {file = "psd_tools-1.14.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23ce7f52c6fc71dd06358f1e6ac77f668547e073a75ff9e681cef9ee9deaa90a"}, + {file = "psd_tools-1.14.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c98dc8bfa51e1293fbbfd3c6aa14d1b95b2eea5e87392f3c3c63455c7914e774"}, + {file = "psd_tools-1.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b041214fcf52109db0e11389cd6d66aabf9d9c92f21d21259d0b72062efca3d2"}, + {file = "psd_tools-1.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba45ff7a37ba00e10deb53ff1617a8f1b16baa05eadf40d808c12b3f4e817af7"}, + {file = "psd_tools-1.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:ade664b0c1320ea81f3b1d31c0a50ba2317a9d66ae064d32f384310e95138585"}, + {file = "psd_tools-1.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:1e90a329e0cd72b706e56bb0a5d4eadfbd969097c1a5cc2026d5720860c93e49"}, ] [package.dependencies] @@ -2856,14 +2751,14 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pyclean" -version = "3.5.0" +version = "3.6.0" description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pyclean-3.5.0-py3-none-any.whl", hash = "sha256:584801fecd52a7690ba85a141c21ecfbbfb69bcfcd03b2b1b87f5e0d8c7a7c29"}, - {file = "pyclean-3.5.0.tar.gz", hash = "sha256:6033b7cb728fbdacc5912dc72e09baee76aeef328430f3e9ccb1a78def4fbfd4"}, + {file = "pyclean-3.6.0-py3-none-any.whl", hash = "sha256:3e5f9c83f2472df5bdafa78ed533bd38690756e0a7b3b6f11c8b29c9affc728b"}, + {file = "pyclean-3.6.0.tar.gz", hash = "sha256:a46c179be187999d50d2d3362b2c94856e60f77de32ca7ec2db4eeb16bc39199"}, ] [[package]] @@ -3112,14 +3007,14 @@ yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -3162,14 +3057,14 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2026.1" +version = "2026.4" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyinstaller_hooks_contrib-2026.1-py3-none-any.whl", hash = "sha256:66ad4888ba67de6f3cfd7ef554f9dd1a4389e2eb19f84d7129a5a6818e3f2180"}, - {file = "pyinstaller_hooks_contrib-2026.1.tar.gz", hash = "sha256:a5f0891a1e81e92406ab917d9e76adfd7a2b68415ee2e35c950a7b3910bc361b"}, + {file = "pyinstaller_hooks_contrib-2026.4-py3-none-any.whl", hash = "sha256:1de1a5e49a878122010b88c7e295502bc69776c157c4a4dc78741a4e6178b00f"}, + {file = "pyinstaller_hooks_contrib-2026.4.tar.gz", hash = "sha256:766c281acb1ecc32e21c8c667056d7ebf5da0aabd5e30c219f9c2a283620eeaa"}, ] [package.dependencies] @@ -3178,14 +3073,14 @@ setuptools = ">=42.0.0" [[package]] name = "pymdown-extensions" -version = "10.21" +version = "10.21.2" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f"}, - {file = "pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5"}, + {file = "pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638"}, + {file = "pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc"}, ] [package.dependencies] @@ -3289,60 +3184,60 @@ test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchm [[package]] name = "pyside6" -version = "6.10.2" +version = "6.11.0" description = "Python bindings for the Qt cross-platform application and UI framework" optional = false -python-versions = "<3.15,>=3.9" +python-versions = "<3.15,>=3.10" groups = ["main"] files = [ - {file = "pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:4b084293caa7845d0064aaf6af258e0f7caae03a14a33537d0a552131afddaf0"}, - {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1b89ce8558d4b4f35b85bff1db90d680912e4d3ce9e79ff804d6fef1d1a151ef"}, - {file = "pyside6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:0439f5e9b10ebe6177981bac9e219096ec970ac6ec215bef055279802ba50601"}, - {file = "pyside6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:032bad6b18a17fcbf4dddd0397f49b07f8aae7f1a45b7e4de7037bf7fd6e0edf"}, - {file = "pyside6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:65a59ad0bc92525639e3268d590948ce07a80ee97b55e7a9200db41d493cac31"}, + {file = "pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f"}, + {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00"}, + {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e"}, + {file = "pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc"}, + {file = "pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e"}, ] [package.dependencies] -PySide6_Addons = "6.10.2" -PySide6_Essentials = "6.10.2" -shiboken6 = "6.10.2" +PySide6_Addons = "6.11.0" +PySide6_Essentials = "6.11.0" +shiboken6 = "6.11.0" [[package]] name = "pyside6-addons" -version = "6.10.2" +version = "6.11.0" description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" optional = false -python-versions = "<3.15,>=3.9" +python-versions = "<3.15,>=3.10" groups = ["main"] files = [ - {file = "pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:0de7d0c9535e17d5e3b634b61314a1867f3b0f6d35c3d7cdc99efc353192faff"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:030a851163b51dbf0063be59e9ddb6a9e760bde89a28e461ccc81a224d286eaf"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:fcee0373e3fd7b98f014094e5e37b4a39e4de7c5a47c13f654a7d557d4a426ad"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:c20150068525a17494f3b6576c5d61c417cf9a5870659e29f5ebd83cd20a78ea"}, - {file = "pyside6_addons-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:3d18db739b46946ba7b722d8ad4cc2097135033aa6ea57076e64d591e6a345f3"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e"}, + {file = "pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8"}, ] [package.dependencies] -PySide6_Essentials = "6.10.2" -shiboken6 = "6.10.2" +PySide6_Essentials = "6.11.0" +shiboken6 = "6.11.0" [[package]] name = "pyside6-essentials" -version = "6.10.2" +version = "6.11.0" description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" optional = false -python-versions = "<3.15,>=3.9" +python-versions = "<3.15,>=3.10" groups = ["main"] files = [ - {file = "pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:1dee2cb9803ff135f881dadeb5c0edcef793d1ec4f8a9140a1348cecb71074e1"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:660aea45bfa36f1e06f799b934c2a7df963bd31abc5083e8bb8a5bfaef45686b"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:c2b028e4c6f8047a02c31f373408e23b4eedfd405f56c6aba8d0525c29472835"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:0741018c2b6395038cad4c41775cfae3f13a409e87995ac9f7d89e5b1fb6b22a"}, - {file = "pyside6_essentials-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:db5f4913648bb6afddb8b347edae151ee2378f12bceb03c8b2515a530a4b38d9"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf"}, + {file = "pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187"}, ] [package.dependencies] -shiboken6 = "6.10.2" +shiboken6 = "6.11.0" [[package]] name = "pytest" @@ -3383,14 +3278,14 @@ six = ">=1.5" [[package]] name = "python-discovery" -version = "1.1.0" +version = "1.2.1" description = "Python interpreter discovery" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b"}, - {file = "python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268"}, + {file = "python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502"}, + {file = "python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e"}, ] [package.dependencies] @@ -3574,25 +3469,25 @@ prompt_toolkit = ">=2.0,<4.0" [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, + {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, + {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, ] [package.dependencies] -certifi = ">=2017.4.17" +certifi = ">=2023.5.7" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "rich" @@ -3615,19 +3510,19 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "setuptools" -version = "82.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +version = "82.0.1" +description = "Most extensible Python build backend with support for C/C++ extension modules" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0"}, - {file = "setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb"}, + {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, + {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] @@ -3636,17 +3531,17 @@ type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.deve [[package]] name = "shiboken6" -version = "6.10.2" +version = "6.11.0" description = "Python/C++ bindings helper module" optional = false -python-versions = "<3.15,>=3.9" +python-versions = "<3.15,>=3.10" groups = ["main"] files = [ - {file = "shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:3bd4e94e9a3c8c1fa8362fd752d399ef39265d5264e4e37bae61cdaa2a00c8c7"}, - {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ace0790032d9cb0adda644b94ee28d59410180d9773643bb6cf8438c361987ad"}, - {file = "shiboken6-6.10.2-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:f74d3ed1f92658077d0630c39e694eb043aeb1d830a5d275176c45d07147427f"}, - {file = "shiboken6-6.10.2-cp39-abi3-win_amd64.whl", hash = "sha256:10f3c8c5e1b8bee779346f21c10dbc14cff068f0b0b4e62420c82a6bf36ac2e7"}, - {file = "shiboken6-6.10.2-cp39-abi3-win_arm64.whl", hash = "sha256:20c671645d70835af212ee05df60361d734c5305edb2746e9875c6a31283f963"}, + {file = "shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b"}, + {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053"}, + {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9"}, + {file = "shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1"}, + {file = "shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4"}, ] [[package]] @@ -3663,14 +3558,14 @@ files = [ [[package]] name = "smmap" -version = "5.0.2" +version = "5.0.3" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, + {file = "smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f"}, + {file = "smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c"}, ] [[package]] @@ -3714,59 +3609,59 @@ files = [ [[package]] name = "tomli" -version = "2.4.0" +version = "2.4.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, - {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, - {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, - {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, - {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, - {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, - {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, - {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, - {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, - {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, - {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, - {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, - {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, - {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, - {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, - {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, - {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, - {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, - {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, - {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, - {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, - {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, - {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, - {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, - {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, - {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, - {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, - {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, - {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, - {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, - {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, - {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, - {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, + {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, + {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, + {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, + {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, + {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, + {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, + {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, + {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, + {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, + {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, + {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, + {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, + {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, + {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, + {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, + {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, + {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, + {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, + {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, + {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, + {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, + {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, + {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, + {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, + {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, + {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, + {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, + {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, + {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, + {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, + {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, + {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, ] [[package]] @@ -3805,26 +3700,26 @@ telegram = ["requests"] [[package]] name = "types-pywin32" -version = "311.0.0.20251008" +version = "311.0.0.20260402" description = "Typing stubs for pywin32" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_pywin32-311.0.0.20251008-py3-none-any.whl", hash = "sha256:775e1046e0bad6d29ca47501301cce67002f6661b9cebbeca93f9c388c53fab4"}, - {file = "types_pywin32-311.0.0.20251008.tar.gz", hash = "sha256:d6d4faf8e0d7fdc0e0a1ff297b80be07d6d18510f102d793bf54e9e3e86f6d06"}, + {file = "types_pywin32-311.0.0.20260402-py3-none-any.whl", hash = "sha256:4db644fcf40ee85a3ee2551f110d009e427c01569ed4670bb53cfe999df0929f"}, + {file = "types_pywin32-311.0.0.20260402.tar.gz", hash = "sha256:637f041065f02fb49cbaba530ae8cf2e483b5d2c145a9bf97fd084c3e913c7e3"}, ] [[package]] name = "types-requests" -version = "2.32.4.20260107" +version = "2.33.0.20260402" description = "Typing stubs for requests" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d"}, - {file = "types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f"}, + {file = "types_requests-2.33.0.20260402-py3-none-any.whl", hash = "sha256:c98372d7124dd5d10af815ee25c013897592ff92af27b27e22c98984102c3254"}, + {file = "types_requests-2.33.0.20260402.tar.gz", hash = "sha256:1bdd3ada9b869741c5c4b887d2c8b4e38284a1449751823b5ebbccba3eefd9da"}, ] [package.dependencies] @@ -3877,14 +3772,14 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "21.1.0" +version = "21.2.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07"}, - {file = "virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44"}, + {file = "virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f"}, + {file = "virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098"}, ] [package.dependencies] @@ -3999,86 +3894,102 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "wrapt" -version = "2.1.1" +version = "2.1.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "wrapt-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e927375e43fd5a985b27a8992327c22541b6dede1362fc79df337d26e23604f"}, - {file = "wrapt-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c99544b6a7d40ca22195563b6d8bc3986ee8bb82f272f31f0670fe9440c869"}, - {file = "wrapt-2.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2be3fa5f4efaf16ee7c77d0556abca35f5a18ad4ac06f0ef3904c3399010ce9"}, - {file = "wrapt-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67c90c1ae6489a6cb1a82058902caa8006706f7b4e8ff766f943e9d2c8e608d0"}, - {file = "wrapt-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05c0db35ccffd7480143e62df1e829d101c7b86944ae3be7e4869a7efa621f53"}, - {file = "wrapt-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0c2ec9f616755b2e1e0bf4d0961f59bb5c2e7a77407e7e2c38ef4f7d2fdde12c"}, - {file = "wrapt-2.1.1-cp310-cp310-win32.whl", hash = "sha256:203ba6b3f89e410e27dbd30ff7dccaf54dcf30fda0b22aa1b82d560c7f9fe9a1"}, - {file = "wrapt-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:6f9426d9cfc2f8732922fc96198052e55c09bb9db3ddaa4323a18e055807410e"}, - {file = "wrapt-2.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:69c26f51b67076b40714cff81bdd5826c0b10c077fb6b0678393a6a2f952a5fc"}, - {file = "wrapt-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c366434a7fb914c7a5de508ed735ef9c133367114e1a7cb91dfb5cd806a1549"}, - {file = "wrapt-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d6a2068bd2e1e19e5a317c8c0b288267eec4e7347c36bc68a6e378a39f19ee7"}, - {file = "wrapt-2.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:891ab4713419217b2aed7dd106c9200f64e6a82226775a0d2ebd6bef2ebd1747"}, - {file = "wrapt-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef36a0df38d2dc9d907f6617f89e113c5892e0a35f58f45f75901af0ce7d81"}, - {file = "wrapt-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76e9af3ebd86f19973143d4d592cbf3e970cf3f66ddee30b16278c26ae34b8ab"}, - {file = "wrapt-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ff562067485ebdeaef2fa3fe9b1876bc4e7b73762e0a01406ad81e2076edcebf"}, - {file = "wrapt-2.1.1-cp311-cp311-win32.whl", hash = "sha256:9e60a30aa0909435ec4ea2a3c53e8e1b50ac9f640c0e9fe3f21fd248a22f06c5"}, - {file = "wrapt-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7d79954f51fcf84e5ec4878ab4aea32610d70145c5bbc84b3370eabfb1e096c2"}, - {file = "wrapt-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:d3ffc6b0efe79e08fd947605fd598515aebefe45e50432dc3b5cd437df8b1ada"}, - {file = "wrapt-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab8e3793b239db021a18782a5823fcdea63b9fe75d0e340957f5828ef55fcc02"}, - {file = "wrapt-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c0300007836373d1c2df105b40777986accb738053a92fe09b615a7a4547e9f"}, - {file = "wrapt-2.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2b27c070fd1132ab23957bcd4ee3ba707a91e653a9268dc1afbd39b77b2799f7"}, - {file = "wrapt-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b0e36d845e8b6f50949b6b65fc6cd279f47a1944582ed4ec8258cd136d89a64"}, - {file = "wrapt-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4aeea04a9889370fcfb1ef828c4cc583f36a875061505cd6cd9ba24d8b43cc36"}, - {file = "wrapt-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d88b46bb0dce9f74b6817bc1758ff2125e1ca9e1377d62ea35b6896142ab6825"}, - {file = "wrapt-2.1.1-cp312-cp312-win32.whl", hash = "sha256:63decff76ca685b5c557082dfbea865f3f5f6d45766a89bff8dc61d336348833"}, - {file = "wrapt-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b828235d26c1e35aca4107039802ae4b1411be0fe0367dd5b7e4d90e562fcbcd"}, - {file = "wrapt-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:75128507413a9f1bcbe2db88fd18fbdbf80f264b82fa33a6996cdeaf01c52352"}, - {file = "wrapt-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9646e17fa7c3e2e7a87e696c7de66512c2b4f789a8db95c613588985a2e139"}, - {file = "wrapt-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:428cfc801925454395aa468ba7ddb3ed63dc0d881df7b81626cdd433b4e2b11b"}, - {file = "wrapt-2.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5797f65e4d58065a49088c3b32af5410751cd485e83ba89e5a45e2aa8905af98"}, - {file = "wrapt-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a2db44a71202c5ae4bb5f27c6d3afbc5b23053f2e7e78aa29704541b5dad789"}, - {file = "wrapt-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8d5350c3590af09c1703dd60ec78a7370c0186e11eaafb9dda025a30eee6492d"}, - {file = "wrapt-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d9b076411bed964e752c01b49fd224cc385f3a96f520c797d38412d70d08359"}, - {file = "wrapt-2.1.1-cp313-cp313-win32.whl", hash = "sha256:0bb7207130ce6486727baa85373503bf3334cc28016f6928a0fa7e19d7ecdc06"}, - {file = "wrapt-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:cbfee35c711046b15147b0ae7db9b976f01c9520e6636d992cd9e69e5e2b03b1"}, - {file = "wrapt-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:7d2756061022aebbf57ba14af9c16e8044e055c22d38de7bf40d92b565ecd2b0"}, - {file = "wrapt-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4814a3e58bc6971e46baa910ecee69699110a2bf06c201e24277c65115a20c20"}, - {file = "wrapt-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:106c5123232ab9b9f4903692e1fa0bdc231510098f04c13c3081f8ad71c3d612"}, - {file = "wrapt-2.1.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1a40b83ff2535e6e56f190aff123821eea89a24c589f7af33413b9c19eb2c738"}, - {file = "wrapt-2.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:789cea26e740d71cf1882e3a42bb29052bc4ada15770c90072cb47bf73fb3dbf"}, - {file = "wrapt-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ba49c14222d5e5c0ee394495a8655e991dc06cbca5398153aefa5ac08cd6ccd7"}, - {file = "wrapt-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ac8cda531fe55be838a17c62c806824472bb962b3afa47ecbd59b27b78496f4e"}, - {file = "wrapt-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:b8af75fe20d381dd5bcc9db2e86a86d7fcfbf615383a7147b85da97c1182225b"}, - {file = "wrapt-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:45c5631c9b6c792b78be2d7352129f776dd72c605be2c3a4e9be346be8376d83"}, - {file = "wrapt-2.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:da815b9263947ac98d088b6414ac83507809a1d385e4632d9489867228d6d81c"}, - {file = "wrapt-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aa1765054245bb01a37f615503290d4e207e3fd59226e78341afb587e9c1236"}, - {file = "wrapt-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:feff14b63a6d86c1eee33a57f77573649f2550935981625be7ff3cb7342efe05"}, - {file = "wrapt-2.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81fc5f22d5fcfdbabde96bb3f5379b9f4476d05c6d524d7259dc5dfb501d3281"}, - {file = "wrapt-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:951b228ecf66def855d22e006ab9a1fc12535111ae7db2ec576c728f8ddb39e8"}, - {file = "wrapt-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ddf582a95641b9a8c8bd643e83f34ecbbfe1b68bc3850093605e469ab680ae3"}, - {file = "wrapt-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fc5c500966bf48913f795f1984704e6d452ba2414207b15e1f8c339a059d5b16"}, - {file = "wrapt-2.1.1-cp314-cp314-win32.whl", hash = "sha256:4aa4baadb1f94b71151b8e44a0c044f6af37396c3b8bcd474b78b49e2130a23b"}, - {file = "wrapt-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:860e9d3fd81816a9f4e40812f28be4439ab01f260603c749d14be3c0a1170d19"}, - {file = "wrapt-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3c59e103017a2c1ea0ddf589cbefd63f91081d7ce9d491d69ff2512bb1157e23"}, - {file = "wrapt-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9fa7c7e1bee9278fc4f5dd8275bc8d25493281a8ec6c61959e37cc46acf02007"}, - {file = "wrapt-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39c35e12e8215628984248bd9c8897ce0a474be2a773db207eb93414219d8469"}, - {file = "wrapt-2.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:94ded4540cac9125eaa8ddf5f651a7ec0da6f5b9f248fe0347b597098f8ec14c"}, - {file = "wrapt-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0af328373f97ed9bdfea24549ac1b944096a5a71b30e41c9b8b53ab3eec04a"}, - {file = "wrapt-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4ad839b55f0bf235f8e337ce060572d7a06592592f600f3a3029168e838469d3"}, - {file = "wrapt-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0d89c49356e5e2a50fa86b40e0510082abcd0530f926cbd71cf25bee6b9d82d7"}, - {file = "wrapt-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:f4c7dd22cf7f36aafe772f3d88656559205c3af1b7900adfccb70edeb0d2abc4"}, - {file = "wrapt-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f76bc12c583ab01e73ba0ea585465a41e48d968f6d1311b4daec4f8654e356e3"}, - {file = "wrapt-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7ea74fc0bec172f1ae5f3505b6655c541786a5cabe4bbc0d9723a56ac32eb9b9"}, - {file = "wrapt-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e03b3d486eb39f5d3f562839f59094dcee30c4039359ea15768dc2214d9e07c"}, - {file = "wrapt-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fdf3073f488ce4d929929b7799e3b8c52b220c9eb3f4a5a51e2dc0e8ff07881"}, - {file = "wrapt-2.1.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cb4f59238c6625fae2eeb72278da31c9cfba0ff4d9cbe37446b73caa0e9bcf7"}, - {file = "wrapt-2.1.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f794a1c148871b714cb566f5466ec8288e0148a1c417550983864b3981737cd"}, - {file = "wrapt-2.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:95ef3866631c6da9ce1fc0f1e17b90c4c0aa6d041fc70a11bc90733aee122e1a"}, - {file = "wrapt-2.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66bc1b2446f01cbbd3c56b79a3a8435bcd4178ac4e06b091913f7751a7f528b8"}, - {file = "wrapt-2.1.1-cp39-cp39-win32.whl", hash = "sha256:1b9e08e57cabc32972f7c956d10e85093c5da9019faa24faf411e7dd258e528c"}, - {file = "wrapt-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e75ad48c3cca739f580b5e14c052993eb644c7fa5b4c90aa51193280b30875ae"}, - {file = "wrapt-2.1.1-cp39-cp39-win_arm64.whl", hash = "sha256:9ccd657873b7f964711447d004563a2bc08d1476d7a1afcad310f3713e6f50f4"}, - {file = "wrapt-2.1.1-py3-none-any.whl", hash = "sha256:3b0f4629eb954394a3d7c7a1c8cca25f0b07cefe6aa8545e862e9778152de5b7"}, - {file = "wrapt-2.1.1.tar.gz", hash = "sha256:5fdcb09bf6db023d88f312bd0767594b414655d58090fc1c46b3414415f67fac"}, + {file = "wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c"}, + {file = "wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e"}, + {file = "wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394"}, + {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45"}, + {file = "wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d"}, + {file = "wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71"}, + {file = "wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc"}, + {file = "wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb"}, + {file = "wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842"}, + {file = "wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9"}, + {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15"}, + {file = "wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b"}, + {file = "wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1"}, + {file = "wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a"}, + {file = "wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9"}, + {file = "wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8"}, + {file = "wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1"}, + {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2"}, + {file = "wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0"}, + {file = "wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63"}, + {file = "wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf"}, + {file = "wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b"}, + {file = "wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca"}, + {file = "wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8"}, + {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413"}, + {file = "wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6"}, + {file = "wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1"}, + {file = "wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf"}, + {file = "wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b"}, + {file = "wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015"}, + {file = "wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67"}, + {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a"}, + {file = "wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd"}, + {file = "wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f"}, + {file = "wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679"}, + {file = "wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9"}, + {file = "wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c"}, + {file = "wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586"}, + {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19"}, + {file = "wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508"}, + {file = "wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04"}, + {file = "wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575"}, + {file = "wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb"}, + {file = "wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044"}, + {file = "wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2"}, + {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3"}, + {file = "wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7"}, + {file = "wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5"}, + {file = "wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00"}, + {file = "wrapt-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5e0fa9cc32300daf9eb09a1f5bdc6deb9a79defd70d5356ba453bcd50aef3742"}, + {file = "wrapt-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:710f6e5dfaf6a5d5c397d2d6758a78fecd9649deb21f1b645f5b57a328d63050"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:305d8a1755116bfdad5dda9e771dcb2138990a1d66e9edd81658816edf51aed1"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0d8fc30a43b5fe191cf2b1a0c82bab2571dadd38e7c0062ee87d6df858dd06e"}, + {file = "wrapt-2.1.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5d516e22aedb7c9c1d47cba1c63160b1a6f61ec2f3948d127cd38d5cfbb556f"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:45914e8efbe4b9d5102fcf0e8e2e3258b83a5d5fba9f8f7b6d15681e9d29ffe0"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:478282ebd3795a089154fb16d3db360e103aa13d3b2ad30f8f6aac0d2207de0e"}, + {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3756219045f73fb28c5d7662778e4156fbd06cf823c4d2d4b19f97305e52819c"}, + {file = "wrapt-2.1.2-cp39-cp39-win32.whl", hash = "sha256:b8aefb4dbb18d904b96827435a763fa42fc1f08ea096a391710407a60983ced8"}, + {file = "wrapt-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e5aeab8fe15c3dff75cfee94260dcd9cded012d4ff06add036c28fae7718593b"}, + {file = "wrapt-2.1.2-cp39-cp39-win_arm64.whl", hash = "sha256:f069e113743a21a3defac6677f000068ebb931639f789b5b226598e247a4c89e"}, + {file = "wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8"}, + {file = "wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e"}, ] [package.extras] @@ -4230,4 +4141,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.14,<3.15" -content-hash = "64f161887655a5bbac62d7f32da658c61a2f849ba7e37ca5dba284ebbd5f0515" +content-hash = "12247e39605d25d0d586fef8a2e1083974c4e229f194ca608da9fe970921fe06" diff --git a/pyproject.toml b/pyproject.toml index da9c66c5..501ccd25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "proxyshop" version = "1.13.2" description = "Photoshop automation tool for generating high quality Magic the Gathering card renders." -authors = [{name = "Investigamer", email = "freethoughtleft@gmail.com"}] +authors = [{ name = "Investigamer", email = "freethoughtleft@gmail.com" }] license = "MPL-2.0" readme = "README.md" keywords = [ @@ -30,51 +30,49 @@ Sponsor = "https://patreon.com/mpcfill" include = 'src/../' [tool.poetry.dependencies] -photoshop-python-api = {git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation"} -requests = "^2.32.5" -Pillow = "^12.1.1" +photoshop-python-api = { git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation" } +requests = "^2.33.1" +Pillow = "^12.2.0" typing-extensions = "^4.15.0" limits = "^5.8.0" backoff = "^2.2.1" pathvalidate = "^3.3.1" -fonttools = "^4.61.1" +fonttools = "^4.62.1" pyyaml = "^6.0.3" tqdm = "^4.67.3" -click = "^8.3.1" -tomli = "^2.4.0" -yarl = "^1.22.0" +click = "8.3.2" +yarl = "^1.23.0" pydantic = "^2.12.5" -pydantic-settings = "^2.13.0" -omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} -hexproof = {git = "https://github.com/pappnu/hexproof.git", branch = "dev"} -rich = "^14.3.2" +pydantic-settings = "^2.13.1" +omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } +hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } +rich = "^14.3.3" pywin32 = "^311.0.0" -pyside6 = "^6.10.2" +pyside6 = "^6.11.0" [tool.poetry.group.dev.dependencies] pytest = "^9.0.2" -mypy = "^1.19.1" -commitizen = "^4.13.7" -setuptools = "^82.0.0" +commitizen = "^4.13.9" +setuptools = "^82.0.1" matplotlib = "^3.10.8" -psd-tools = "^1.12.1" -pyclean = "^3.5.0" +psd-tools = "^1.14.2" +pyclean = "^3.6.0" pyinstaller = "^6.19.0" pre-commit = "^4.5.1" mkdocs = "^1.6.1" -mkdocs-material = "^9.7.1" -mkdocs-include-markdown-plugin = "^7.2.1" +mkdocs-material = "^9.7.6" +mkdocs-include-markdown-plugin = "^7.2.2" mkdocs-pymdownx-material-extras = "^2.8.0" mkdocs-minify-plugin = "^0.8.0" -mkdocstrings-python = "^2.0.2" -mkdocs-gen-files = "^0.6.0" +mkdocstrings-python = "^2.0.3" +mkdocs-gen-files = "^0.6.1" mkdocs-autolinks-plugin = "^0.7.1" -mkdocs-same-dir = "^0.1.3" +mkdocs-same-dir = "^0.1.4" mkdocs-git-revision-date-plugin = "^0.3.2" -mkdocstrings = {extras = ["python"], version = "^1.0.3"} +mkdocstrings = { extras = ["python"], version = "^1.0.3" } memory-profiler = "^0.61.0" -types-pywin32 = "^311.0.0.20251008" -types-requests = "^2.32.4.20260107" +types-pywin32 = "^311.0.0.20260402" +types-requests = "^2.33.0.20260402" [tool.poetry.scripts] proxyshop = 'main:launch_cli' @@ -99,6 +97,10 @@ extend-select = [ ] ignore = ["F403", "UP015"] +[tool.ruff.lint.isort] +split-on-trailing-comma = false + [tool.pyright] typeCheckingMode = "strict" reportMissingTypeStubs = "information" +reportUnnecessaryTypeIgnoreComment = "warning" From 157fdcb098cf8db8056ece89ad8479041231c030 Mon Sep 17 00:00:00 2001 From: Hanno Date: Sun, 5 Apr 2026 22:16:03 +0200 Subject: [PATCH 105/190] Make sure render spec contain the absolute path to images --- src/render_spec.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 97f68c23..8af29275 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -29,7 +29,7 @@ class RenderConfiguration: @dataclass class CardSpec: spec: str - actual_path: str | None + actual_path: Path | None class RenderSpec(TypedDict): @@ -58,7 +58,7 @@ def parse_render_spec(file_path: Path) -> RenderSpec: # Extract just the spec name file_name = file_path.stem - parent_dir = str(file_path.parent) + parent_dir = file_path.parent # Load all the content and get rid of empty lines and comments spec_lines = open(file_path, "r").read().splitlines() @@ -103,28 +103,32 @@ def append_config( ) def append_card(card_spec: CardSpec): - spec, path = astuple(card_spec) + spec = card_spec.spec + # Make sure the extension doesn't contain a ']' as that implies # we have something without extension using a config that contains # something with extension, e.g. [art=file.png] if not re.match(r"\.[^\]]+$", spec): spec += ".png" + # Pretend this is a file right next to the spec and parse that - full_card_path = file_path.parent / Path(spec).name + full_card_path = parent_dir / Path(spec).name card_info = parse_card_info(full_card_path) + + path = card_spec.actual_path if path is not None and "art" not in card_info["kwargs"]: - card_info["kwargs"]["art"] = path + card_info["kwargs"]["art"] = str(path) cards.append(card_info) if "*" in spec_base: specs = glob.glob(spec_base, root_dir=parent_dir, recursive=True) specs = [ - CardSpec(s.split(".")[0], s) for s in specs if not s.endswith(".txt") + CardSpec(s.split(".")[0], parent_dir / s) for s in specs if not s.endswith(".txt") ] else: abs_spec_base = file_path.parent / Path(spec_base).name if os.path.exists(abs_spec_base): - specs = [CardSpec(spec_base.split(".")[0], str(abs_spec_base))] + specs = [CardSpec(spec_base.split(".")[0], abs_spec_base)] else: specs = [CardSpec(spec_base, None)] From 49ace5f474e00af7251be75e0435a6ddf352a529 Mon Sep 17 00:00:00 2001 From: Hanno Date: Sun, 5 Apr 2026 22:16:42 +0200 Subject: [PATCH 106/190] Remove Maintain.Folder.Structure and instead imply this when render from a render spec --- src/_config.py | 3 --- src/data/config/app.toml | 6 ------ src/render_spec.py | 10 ++++++++-- src/templates/_core.py | 12 ++++-------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/_config.py b/src/_config.py index fa2d6516..330e15aa 100644 --- a/src/_config.py +++ b/src/_config.py @@ -108,9 +108,6 @@ def update_definitions(self): option="Output.File.Name", fallback="#name (#frame<, #suffix>) [#set] {#num}", ) - self.maintain_folder_structure = self.file.getboolean( - section='APP.FILES', option='Maintain.Folder.Structure', - fallback=True) self.png_compression_level = self.file.getint( "APP.FILES", "PNG.Compression.Level", fallback=3 ) diff --git a/src/data/config/app.toml b/src/data/config/app.toml index 3f68ef8a..91ab7d0e 100644 --- a/src/data/config/app.toml +++ b/src/data/config/app.toml @@ -22,12 +22,6 @@ desc = """Choose how the rendered card images are named. See the README for adva type = "string" default = "#name (#frame<, #suffix>) [#set] {#num}" -[FILES."Maintain.Folder.Structure"] -title = "Maintain Folder Structure" -desc = """Choose whether to flatten all images into the `out` folder or maintain the folder structure of the `art` folder.""" -type = "bool" -default = 1 - [FILES."Overwrite.Duplicate"] title = "Overwrite Duplicates" desc = """Overwrite rendered files with identical file names.""" diff --git a/src/render_spec.py b/src/render_spec.py index 8af29275..a466d499 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -116,8 +116,14 @@ def append_card(card_spec: CardSpec): card_info = parse_card_info(full_card_path) path = card_spec.actual_path - if path is not None and "art" not in card_info["kwargs"]: - card_info["kwargs"]["art"] = str(path) + if path is not None: + if "art" not in card_info["kwargs"]: + card_info["kwargs"]["art"] = str(path) + if "dir" not in card_info["kwargs"]: + if parent_dir in path.parents: + rel_dir = path.relative_to(parent_dir).parent + card_info["kwargs"]["dir"] = str(rel_dir) + cards.append(card_info) if "*" in spec_base: diff --git a/src/templates/_core.py b/src/templates/_core.py index 22b0a9c1..d8c3c869 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -247,6 +247,8 @@ def save_mode(self) -> Callable[[Path, Document | None], None]: @cached_property def output_directory(self) -> Path: """Directory where the rendered image will be saved to.""" + if output_directory := self.layout.file['kwargs'].get("dir", None): + return PATH.OUT / Path(output_directory) return PATH.OUT @cached_property @@ -287,10 +289,6 @@ def output_file_name(self) -> Path: f".{self.config.output_file_type}" ) - if self.config.maintain_folder_structure and self.art_file.is_relative_to(PATH.ART): - relative_path = self.art_file.parent.relative_to(PATH.ART) - path = path.parent / relative_path / path.name - # Are we overwriting duplicate names? if not self.config.overwrite_duplicate: path = get_unique_filename(path) @@ -1657,10 +1655,8 @@ async def execute(self) -> bool: await self.pause_async("Rendering paused for manual editing.") # Make sure output folder exists - if self.config.maintain_folder_structure: - output_folder = self.output_file_name.parent - if not os.path.exists(output_folder): - os.makedirs(output_folder) + if not os.path.exists(self.output_directory): + os.makedirs(self.output_directory) # Save the document if not self.run_tasks( From 03d28697556a7bcc9e38c7fad053b7ddb8d8be28 Mon Sep 17 00:00:00 2001 From: Hanno Date: Sun, 5 Apr 2026 22:17:12 +0200 Subject: [PATCH 107/190] Include .txt files when opening a file dialog for "images and data" --- src/gui/qml/models/batch_rendering_model.py | 2 +- src/gui/qml/models/file_dialog_model.py | 4 ++-- src/gui/qml/models/template_list_model.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index f47417cb..414a151e 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -214,7 +214,7 @@ async def action(): selections = await self._file_dialog_model.select_images( dialog_id="batch_mode_render", filters=[ - FileDialogModel.IMAGES_AND_JSON_FILTER, + FileDialogModel.IMAGES_AND_DATA_FILTER, FileDialogModel.ALL_FILTER, ], ) diff --git a/src/gui/qml/models/file_dialog_model.py b/src/gui/qml/models/file_dialog_model.py index aef51fef..8d408324 100644 --- a/src/gui/qml/models/file_dialog_model.py +++ b/src/gui/qml/models/file_dialog_model.py @@ -20,8 +20,8 @@ class FileDialogModel(BaseDialogModel): ALL_FILTER = "All (*)" PSD_FILTER = "PSD (*.psd)" IMAGES_FILTER = "Images (*.png *.jpg *.jpeg *.jxl *.avif *.webp)" - IMAGES_AND_JSON_FILTER = ( - "Images and data (*.png *.jpg *.jpeg *.jxl *.avif *.webp *.json)" + IMAGES_AND_DATA_FILTER = ( + "Images and data (*.png *.jpg *.jpeg *.jxl *.avif *.webp *.json *.txt)" ) def __init__( diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 51a928f2..837b4b2b 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -175,7 +175,7 @@ async def action(): selections = await self._file_dialog_model.select_images( dialog_id="template_list_render", filters=[ - FileDialogModel.IMAGES_AND_JSON_FILTER, + FileDialogModel.IMAGES_AND_DATA_FILTER, FileDialogModel.ALL_FILTER, ], ) From 23aea414b2e9523910fb34fe52f82129d2671380 Mon Sep 17 00:00:00 2001 From: Hanno Date: Sun, 5 Apr 2026 22:37:55 +0200 Subject: [PATCH 108/190] Port support for 'tmpl' argument that allows selecting a template form filename --- src/gui/qml/models/batch_rendering_model.py | 2 + src/gui/qml/models/template_list_model.py | 2 + src/gui/qml/models/test_renders_model.py | 1 + src/render/setup.py | 42 ++++++++++++++------- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index 414a151e..5129aa62 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -72,6 +72,7 @@ def __init__( self._render_queue = render_queue self._test_renders_model = test_renders_model + self.template_library = template_library self.built_in_templates_by_layout: dict[ LayoutCategory, dict[str, AssembledTemplate] ] = {} @@ -199,6 +200,7 @@ def add_render( ) -> None: if render_operations := prepare_render_operations( self.template_choices, + self.template_library, (input,), file_dialog=self._file_dialog_model, message_dialog=self._message_dialog_model, diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 837b4b2b..42a55122 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -70,6 +70,7 @@ def __init__( self._message_dialog_model = message_dialog_model self._template_library = template_library self._test_renders_model = test_renders_model + self.template_library = template_library self.built_in_templates = template_library.built_in_templates_by_name self.plugin_templates = template_library.plugin_templates_by_name @@ -160,6 +161,7 @@ def add_render( ) -> None: if render_operations := prepare_render_operations( template, + self.template_library, (input,), file_dialog=self._file_dialog_model, message_dialog=self._message_dialog_model, diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index 7c9d3a40..695f2046 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -60,6 +60,7 @@ async def test_render( if render_operations := await to_thread( prepare_render_operations, template, + self._template_library, (parse_card_info(PATH.SRC_IMG_TEST, name_override=test_case),), file_dialog=self._file_dialog_model, message_dialog=self._message_dialog_model, diff --git a/src/render/setup.py b/src/render/setup.py index 0b2d10b3..dc3c2c4b 100644 --- a/src/render/setup.py +++ b/src/render/setup.py @@ -6,7 +6,7 @@ from src import CON, ENV from src._config import AppConfig -from src._loader import ConfigHandler, RenderableTemplate, get_template_class +from src._loader import ConfigHandler, RenderableTemplate, TemplateLibrary, get_template_class from src.cards import CardDetails from src.enums.mtg import LayoutCategory from src.layouts import NormalLayout, assign_layout, join_dual_card_layouts @@ -131,6 +131,7 @@ def resume(self) -> None: def prepare_render_operations( template_choices: RenderableTemplate | Mapping[LayoutCategory, RenderableTemplate], + template_library: TemplateLibrary, assets: Iterable[CardDetails | tuple[CardDetails, ScryfallCard]], file_dialog: FileDialogModel, message_dialog: MessageDialogContentModel, @@ -162,18 +163,33 @@ def prepare_render_operations( # assign_layout reports its own errors continue - if isinstance(template_choices, Mapping): - if not (template_to_use := template_choices.get(layout.category, None)): - _logger.error( - f"Skipping image { - card['file'].name - } because no template was specified for layout { - layout.category - }." - ) - continue - else: - template_to_use = template_choices + template_to_use: RenderableTemplate | None = None + + if 'tmpl' in card['kwargs']: + template_name = card['kwargs']['tmpl'] + if builtin_template := template_library.built_in_templates_by_name.get(template_name, None): + if layout.category in builtin_template.layout_categories: + template_to_use = builtin_template + if template_to_use is None: + for _, templates in template_library.plugin_templates_by_name.items(): + if plugin_template := templates.get(template_name, None): + if layout.category in plugin_template.layout_categories: + template_to_use = plugin_template + break + + if template_to_use is None: + if isinstance(template_choices, Mapping): + if not (template_to_use := template_choices.get(layout.category, None)): + _logger.error( + f"Skipping image { + card['file'].name + } because no template was specified for layout { + layout.category + }." + ) + continue + else: + template_to_use = template_choices if not ( class_name_and_file := template_to_use.get_class_name_and_file_for_layout( From 16be42d1add7594482b40736e7c4c332fea6bea4 Mon Sep 17 00:00:00 2001 From: Hanno Date: Tue, 7 Apr 2026 21:30:45 +0200 Subject: [PATCH 109/190] Move handling of 'art' argument to NormalLayout --- src/layouts.py | 13 +++++++++++-- src/templates/_core.py | 19 +++---------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index 2e9b6d97..2836e2d8 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -246,7 +246,16 @@ def template_file(self) -> Path: @cached_property def art_file(self) -> Path: """Path: Art image file path.""" - return Path(self.file["file"]) + art_file = self.file['kwargs'].get('art', None) + if art_file is not None: + art_file = Path(art_file) + if art_file.is_absolute(): + return art_file + else: + file = Path(self.file["file"]) + return file.parent / art_file + else: + return Path(self.file["file"]) @cached_property def scryfall_scan(self) -> str: @@ -1574,7 +1583,7 @@ def art_file(self) -> Path: @cached_property def art_files(self) -> list[Path]: """list[Path]: Two image files, second is appended during render process.""" - return [Path(self.file["file"])] + return [super().art_file] @cached_property def display_name(self) -> str: diff --git a/src/templates/_core.py b/src/templates/_core.py index d8c3c869..bb186a34 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -429,7 +429,7 @@ def is_flipside_creature(self) -> bool: @cached_property def is_art_vertical(self) -> bool: """bool: Returns True if art provided is vertically oriented, False if it is horizontal.""" - with Image.open(self.art_file) as image: + with Image.open(self.layout.art_file) as image: width, height = image.size if height > (width * 1.1): # Vertical orientation @@ -749,19 +749,6 @@ def process_layout_data(self) -> None: * Loading Artwork """ - @cached_property - def art_file(self) -> Path: - """Path to the art file to load.""" - art_file = self.layout.file['kwargs'].get('art', None) - if art_file is not None: - art_file = Path(art_file) - if art_file.is_absolute(): - return art_file - else: - return self.layout.art_file.parent / art_file - else: - return self.layout.art_file - @cached_property def art_action(self) -> Callable[[], None] | None: """Function that is called to perform an action on the imported art.""" @@ -783,7 +770,7 @@ def load_artwork( """Loads the specified art file into the specified layer. Args: - art_file: Optional path (as str or Path) to art file. Will use `self.art_file` + art_file: Optional path (as str or Path) to art file. Will use `self.layout.art_file` if not provided. art_layer: Optional `ArtLayer` where art image should be placed when imported. Will use `self.art_layer` property if not provided. @@ -792,7 +779,7 @@ def load_artwork( """ # Set default values - art_file = art_file or self.art_file + art_file = art_file or self.layout.art_file art_layer = art_layer or self.art_layer art_reference = art_reference or self.art_reference From d651cbe5eb3eff6e375605fb3433d4adb88b14b4 Mon Sep 17 00:00:00 2001 From: Hanno Date: Wed, 8 Apr 2026 11:41:22 +0200 Subject: [PATCH 110/190] Port render specs to validated YAML --- src/gui/qml/models/file_dialog_model.py | 2 +- src/render_spec.py | 302 +++++++++++++----------- src/utils/images.py | 13 +- 3 files changed, 168 insertions(+), 149 deletions(-) diff --git a/src/gui/qml/models/file_dialog_model.py b/src/gui/qml/models/file_dialog_model.py index 8d408324..7b56f27f 100644 --- a/src/gui/qml/models/file_dialog_model.py +++ b/src/gui/qml/models/file_dialog_model.py @@ -21,7 +21,7 @@ class FileDialogModel(BaseDialogModel): PSD_FILTER = "PSD (*.psd)" IMAGES_FILTER = "Images (*.png *.jpg *.jpeg *.jxl *.avif *.webp)" IMAGES_AND_DATA_FILTER = ( - "Images and data (*.png *.jpg *.jpeg *.jxl *.avif *.webp *.json *.txt)" + "Images and data (*.png *.jpg *.jpeg *.jxl *.avif *.webp *.json *.yaml *.yml)" ) def __init__( diff --git a/src/render_spec.py b/src/render_spec.py index a466d499..a33e193b 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -1,23 +1,47 @@ """ * Render Spec Module -* Handles parsing render spec file, aka text files that contain a set of configurations and cards to render +* Handles parsing render spec file, aka yaml files that contain a set of configurations and cards to render """ -# Standard Library Imports import os import re import glob -import re as regex from pathlib import Path from typing import Dict, TypedDict -from dataclasses import dataclass, astuple +from dataclasses import dataclass + +from pydantic import BaseModel, RootModel -# Local Imports from src.cards import CardDetails, parse_card_info +from src.utils.data_structures import parse_model -""" -* Types -""" +# region Model-Types + + +class ConfigModel(BaseModel): + name: str + settings: list[str] | str + + +class FileModel(BaseModel): + file: str + settings: list[str] | str = [] + + +class GroupModel(BaseModel): + files: list[FileModel | str] | FileModel | str + groups: list[GroupModel] | GroupModel = [] + settings: list[str] | str = [] + + +class RenderSpecModel(BaseModel): + files: list[FileModel | str] | FileModel | str = [] + groups: list[GroupModel] | GroupModel = [] + configs: list[ConfigModel] | ConfigModel = [] + + +# endregion Model-Types +# region Types @dataclass @@ -41,162 +65,154 @@ class RenderSpec(TypedDict): cards: list[CardDetails] -""" -* File parsing -""" +# endregion Types + +# region Parsing + +def parse_render_spec_file(render_spec_path: Path) -> RenderSpecModel: + return parse_model(render_spec_path, RootModel[RenderSpecModel]).root -def parse_render_spec(file_path: Path) -> RenderSpec: + +def parse_render_spec(render_spec_path: Path) -> RenderSpec: """Retrieve render spec from the input file. Args: - file_path: Path to the text file. + render_spec_path: Path to the yaml file. Returns: Dict containing the configurations and cards in this spec. """ - # Extract just the spec name - file_name = file_path.stem - parent_dir = file_path.parent - - # Load all the content and get rid of empty lines and comments - spec_lines = open(file_path, "r").read().splitlines() - spec_lines = [l.split("#")[0].strip() for l in spec_lines] - spec_lines = [l for l in spec_lines if l] - - # Split lines with configs and lines with cards - config_lines = [l for l in spec_lines if regex.match(r"([a-zA-Z0-9_ ]+):(.*)", l)] - card_lines = [l for l in spec_lines if l not in config_lines] - - # Find all the configurations first - configs: dict[str, RenderConfiguration] = {} - for l in config_lines: - [config_name, config_spec] = map(str.strip, l.split(":")) - configs[config_name] = RenderConfiguration( - name=config_name, - spec=config_spec, - ) + render_spec_data = parse_render_spec_file(render_spec_path) + + def as_list[T](values: list[T] | T) -> list[T]: + if isinstance(values, list): + return values # type: ignore + return [values] + + def join_settings(settings: list[str] | str) -> str: + if isinstance(settings, str): + return settings + return " ".join(settings) + + configs = { + c.name: RenderConfiguration(c.name, join_settings(c.settings)) + for c in as_list(render_spec_data.configs) + } + + render_spec_name = render_spec_path.stem + parent_dir = render_spec_path.parent - # Now find all the cards and parse them by using the configs cards: list[CardDetails] = [] - groups: list[list[CardSpec]] = [] - for l in card_lines: - # Entering a group - if l.startswith("{"): - groups.append([]) - l = l[1:].strip() - if not l: - continue - - parts = list(map(str.strip, l.split("|"))) - spec_base = parts[0] - - def append_config( - card: CardSpec, config: RenderConfiguration | str - ) -> CardSpec: - if isinstance(config, RenderConfiguration): - config = config.spec - return CardSpec( - card.spec + f" {config}", - card.actual_path, - ) - def append_card(card_spec: CardSpec): - spec = card_spec.spec - - # Make sure the extension doesn't contain a ']' as that implies - # we have something without extension using a config that contains - # something with extension, e.g. [art=file.png] - if not re.match(r"\.[^\]]+$", spec): - spec += ".png" - - # Pretend this is a file right next to the spec and parse that - full_card_path = parent_dir / Path(spec).name - card_info = parse_card_info(full_card_path) - - path = card_spec.actual_path - if path is not None: - if "art" not in card_info["kwargs"]: - card_info["kwargs"]["art"] = str(path) - if "dir" not in card_info["kwargs"]: - if parent_dir in path.parents: - rel_dir = path.relative_to(parent_dir).parent - card_info["kwargs"]["dir"] = str(rel_dir) - - cards.append(card_info) - - if "*" in spec_base: - specs = glob.glob(spec_base, root_dir=parent_dir, recursive=True) - specs = [ - CardSpec(s.split(".")[0], parent_dir / s) for s in specs if not s.endswith(".txt") + def resolve_file(file: str) -> list[CardSpec]: + if "*" in file: + specs = glob.glob(file, root_dir=parent_dir, recursive=True) + return [ + CardSpec(s.split(".")[0], parent_dir / s) + for s in specs + if not s.endswith(".yaml") and not s.endswith(".yml") ] else: - abs_spec_base = file_path.parent / Path(spec_base).name - if os.path.exists(abs_spec_base): - specs = [CardSpec(spec_base.split(".")[0], abs_spec_base)] + abs_file = render_spec_path.parent / Path(file) + if os.path.exists(abs_file): + return [CardSpec(file.split(".")[0], abs_file)] else: - specs = [CardSpec(spec_base, None)] + return [CardSpec(file, None)] + + def append_card(card_spec: CardSpec): + spec = card_spec.spec + + # Make sure the extension doesn't contain a ']' as that implies + # we have something without extension using a config that contains + # something with extension, e.g. [art=file.png] + if not re.match(r"\.[^\]]+$", spec): + spec += ".png" + + # Pretend this is a file right next to the spec and parse that + full_card_path = parent_dir / Path(spec).name + card_info = parse_card_info(full_card_path) + + path = card_spec.actual_path + if path is not None: + if "art" not in card_info["kwargs"]: + card_info["kwargs"]["art"] = str(path) + if "dir" not in card_info["kwargs"]: + if parent_dir in path.parents: + rel_dir = path.relative_to(parent_dir).parent + card_info["kwargs"]["dir"] = str(rel_dir) + + cards.append(card_info) + + def append_configs(card: CardSpec, card_configs: list[str]) -> CardSpec: + resolved_configs = [ + configs[c].spec if c in configs else c for c in card_configs + ] + config = " ".join(resolved_configs) + return CardSpec( + f"{card.spec} {config}", + card.actual_path, + ) - used_configs = parts[1:] - for c in used_configs: - if c in configs: - specs = [append_config(s, configs[c]) for s in specs] - else: - specs = [append_config(s, c) for s in specs] - - # If part of a group we need to just accumulate - if groups: - if l.startswith("}"): - # The group ended, so we assign this configuration to all the cards in it - group_spec = specs[0].spec[1:].strip() - ended_group = groups.pop() - ended_group = [append_config(c, group_spec) for c in ended_group] - - def expand_variable(card: CardSpec, variable: str, value: str | int): - return CardSpec( - card.spec.replace(variable, str(value)), - card.actual_path, - ) - - if groups: - # Replace special variables - ended_group = [ - expand_variable(c, "${GROUP_INDEX}", i) - for (i, c) in enumerate(ended_group) - ] - ended_group = [ - expand_variable(c, "${INNER_GROUP_INDEX}", i) - for (i, c) in enumerate(ended_group) - ] - - # If this was a nested group we just put these into the outer group - groups[-1].extend(ended_group) - else: - # Replace special variables - ended_group = [ - expand_variable(c, "${GROUP_INDEX}", i) - for (i, c) in enumerate(ended_group) - ] - ended_group = [ - expand_variable(c, "${OUTER_GROUP_INDEX}", i) - for (i, c) in enumerate(ended_group) - ] - - # Otherwise append to the render spec - for card in ended_group: - append_card(card) + def parse_group(group: GroupModel, root_group: bool = True) -> list[CardSpec]: + group_cards: list[CardSpec] = [] + group_settings = as_list(group.settings) + + for file_model in as_list(group.files): + if isinstance(file_model, FileModel): + file = file_model.file + settings = as_list(file_model.settings) + group_settings else: - # Append the card to the group - groups[-1].extend(specs) + file = file_model + settings = group_settings + + for card_spec in resolve_file(file): + card_spec = append_configs(card_spec, settings) + group_cards.append(card_spec) + + for nested_group in as_list(group.groups): + for card_spec in parse_group(nested_group): + card_spec = append_configs(card_spec, group_settings) + group_cards.append(card_spec) + + def expand_variable(card: CardSpec, variable: str, value: str | int): + return CardSpec( + card.spec.replace(variable, str(value)), + card.actual_path, + ) + + group_cards = [ + expand_variable(c, "${GROUP_INDEX}", i) for (i, c) in enumerate(group_cards) + ] + if root_group: + group_cards = [ + expand_variable(c, "${ROOT_GROUP_INDEX}", i) + for (i, c) in enumerate(group_cards) + ] + + return group_cards + + for card_model in as_list(render_spec_data.files): + if isinstance(card_model, str): + card = FileModel(file=card_model, settings=[]) else: - for card_spec in specs: - append_card(card_spec) + card = card_model + + for card_spec in resolve_file(card.file): + card_spec = append_configs(card_spec, as_list(card.settings)) + append_card(card_spec) + + for group in as_list(render_spec_data.groups): + for card_spec in parse_group(group): + append_card(card_spec) - # Return dictionary return { - "name": file_name, - "file": file_path, + "name": render_spec_name, + "file": render_spec_path, "configs": configs, "cards": cards, } + + +# endregion Parsing diff --git a/src/utils/images.py b/src/utils/images.py index c444cd97..148db4ff 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -59,8 +59,8 @@ def match_images_with_data_files( Pydantic.ValidationError: if some of the data files don't conform to the data model """ data_files = [pth for pth in paths if pth.suffix == ".json"] - render_specs = [pth for pth in paths if pth.suffix == ".txt"] - image_files = [pth for pth in paths if pth.suffix not in (".json", ".txt")] + render_specs = [pth for pth in paths if pth.suffix in (".yaml", ".yml")] + image_files = [pth for pth in paths if pth.suffix not in (".json", ".yaml", ".yml")] results: list[CardDetails | tuple[CardDetails, ScryfallCard]] = [] @@ -88,9 +88,12 @@ def add_card(card: CardDetails) -> None: try: for path in render_specs: - cards = parse_render_spec(path)["cards"] - for card in cards: - add_card(card) + try: + cards = parse_render_spec(path)["cards"] + for card in cards: + add_card(card) + except ValidationError as e: + raise _ValidationError(path) for path in image_files: card = parse_card_info(path) From 91ac919a61b54c66fa30c41c0c6fb08b84d16081 Mon Sep 17 00:00:00 2001 From: Hanno Date: Wed, 8 Apr 2026 11:41:44 +0200 Subject: [PATCH 111/190] Add a schema for render spec --- src/schmas/render_spec_schema.json | 186 +++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 src/schmas/render_spec_schema.json diff --git a/src/schmas/render_spec_schema.json b/src/schmas/render_spec_schema.json new file mode 100644 index 00000000..8a92cc59 --- /dev/null +++ b/src/schmas/render_spec_schema.json @@ -0,0 +1,186 @@ +{ + "$defs": { + "ConfigModel": { + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "settings": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "title": "Settings" + } + }, + "required": [ + "name", + "settings" + ], + "title": "ConfigModel", + "type": "object" + }, + "FileModel": { + "properties": { + "file": { + "title": "File", + "type": "string" + }, + "settings": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "default": [], + "title": "Settings" + } + }, + "required": [ + "file" + ], + "title": "FileModel", + "type": "object" + }, + "GroupModel": { + "properties": { + "files": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/FileModel" + }, + { + "type": "string" + } + ] + }, + "type": "array" + }, + { + "$ref": "#/$defs/FileModel" + }, + { + "type": "string" + } + ], + "title": "Files" + }, + "groups": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/GroupModel" + }, + "type": "array" + }, + { + "$ref": "#/$defs/GroupModel" + } + ], + "default": [], + "title": "Groups" + }, + "settings": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "string" + } + ], + "default": [], + "title": "Settings" + } + }, + "required": [ + "files" + ], + "title": "GroupModel", + "type": "object" + }, + "RenderSpecModel": { + "properties": { + "files": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/FileModel" + }, + { + "type": "string" + } + ] + }, + "type": "array" + }, + { + "$ref": "#/$defs/FileModel" + }, + { + "type": "string" + } + ], + "default": [], + "title": "Files" + }, + "groups": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/GroupModel" + }, + "type": "array" + }, + { + "$ref": "#/$defs/GroupModel" + } + ], + "default": [], + "title": "Groups" + }, + "configs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/ConfigModel" + }, + "type": "array" + }, + { + "$ref": "#/$defs/ConfigModel" + } + ], + "default": [], + "title": "Configs" + } + }, + "title": "RenderSpecModel", + "type": "object" + } + }, + "$ref": "#/$defs/RenderSpecModel", + "title": "RootModel[RenderSpecModel]" +} \ No newline at end of file From fb278b7868f2b203f1946b807ba80f861b4963fb Mon Sep 17 00:00:00 2001 From: Hanno Date: Thu, 9 Apr 2026 11:28:20 +0200 Subject: [PATCH 112/190] Avoid a number of list[T] | T in render spec model types --- src/render_spec.py | 60 ++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index a33e193b..4311d0f4 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -3,14 +3,16 @@ * Handles parsing render spec file, aka yaml files that contain a set of configurations and cards to render """ +from __future__ import annotations + import os import re import glob from pathlib import Path -from typing import Dict, TypedDict +from typing import Dict, TypedDict, Annotated, TypeVar from dataclasses import dataclass -from pydantic import BaseModel, RootModel +from pydantic import BaseModel, RootModel, BeforeValidator from src.cards import CardDetails, parse_card_info from src.utils.data_structures import parse_model @@ -18,26 +20,36 @@ # region Model-Types +def __ensure_list__[T](values: list[T] | T) -> list[T]: + if isinstance(values, list): + return values # type: ignore + return [values] + + +T = TypeVar("T") +EnsuredList = Annotated[list[T], BeforeValidator(__ensure_list__)] + + class ConfigModel(BaseModel): name: str - settings: list[str] | str + settings: EnsuredList[str] class FileModel(BaseModel): file: str - settings: list[str] | str = [] + settings: EnsuredList[str] = [] class GroupModel(BaseModel): - files: list[FileModel | str] | FileModel | str - groups: list[GroupModel] | GroupModel = [] - settings: list[str] | str = [] + files: EnsuredList[FileModel | str] + groups: EnsuredList[GroupModel] = [] + settings: EnsuredList[str] = [] class RenderSpecModel(BaseModel): - files: list[FileModel | str] | FileModel | str = [] - groups: list[GroupModel] | GroupModel = [] - configs: list[ConfigModel] | ConfigModel = [] + files: EnsuredList[FileModel | str] = [] + groups: EnsuredList[GroupModel] = [] + configs: EnsuredList[ConfigModel] = [] # endregion Model-Types @@ -86,19 +98,9 @@ def parse_render_spec(render_spec_path: Path) -> RenderSpec: render_spec_data = parse_render_spec_file(render_spec_path) - def as_list[T](values: list[T] | T) -> list[T]: - if isinstance(values, list): - return values # type: ignore - return [values] - - def join_settings(settings: list[str] | str) -> str: - if isinstance(settings, str): - return settings - return " ".join(settings) - configs = { - c.name: RenderConfiguration(c.name, join_settings(c.settings)) - for c in as_list(render_spec_data.configs) + c.name: RenderConfiguration(c.name, " ".join(c.settings)) + for c in render_spec_data.configs } render_spec_name = render_spec_path.stem @@ -157,12 +159,12 @@ def append_configs(card: CardSpec, card_configs: list[str]) -> CardSpec: def parse_group(group: GroupModel, root_group: bool = True) -> list[CardSpec]: group_cards: list[CardSpec] = [] - group_settings = as_list(group.settings) + group_settings = group.settings - for file_model in as_list(group.files): + for file_model in group.files: if isinstance(file_model, FileModel): file = file_model.file - settings = as_list(file_model.settings) + group_settings + settings = file_model.settings + group_settings else: file = file_model settings = group_settings @@ -171,7 +173,7 @@ def parse_group(group: GroupModel, root_group: bool = True) -> list[CardSpec]: card_spec = append_configs(card_spec, settings) group_cards.append(card_spec) - for nested_group in as_list(group.groups): + for nested_group in group.groups: for card_spec in parse_group(nested_group): card_spec = append_configs(card_spec, group_settings) group_cards.append(card_spec) @@ -193,17 +195,17 @@ def expand_variable(card: CardSpec, variable: str, value: str | int): return group_cards - for card_model in as_list(render_spec_data.files): + for card_model in render_spec_data.files: if isinstance(card_model, str): card = FileModel(file=card_model, settings=[]) else: card = card_model for card_spec in resolve_file(card.file): - card_spec = append_configs(card_spec, as_list(card.settings)) + card_spec = append_configs(card_spec, card.settings) append_card(card_spec) - for group in as_list(render_spec_data.groups): + for group in render_spec_data.groups: for card_spec in parse_group(group): append_card(card_spec) From 9476c811bc16ac396526032a2fd000423f41c838 Mon Sep 17 00:00:00 2001 From: Hanno Date: Thu, 9 Apr 2026 13:41:37 +0200 Subject: [PATCH 113/190] Change configs to be of type dict[str, str] --- src/render_spec.py | 30 +++++++++++++----------------- src/schmas/render_spec_schema.json | 27 +++++++++++++++------------ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 4311d0f4..37200411 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -9,7 +9,7 @@ import re import glob from pathlib import Path -from typing import Dict, TypedDict, Annotated, TypeVar +from typing import TypedDict, Annotated, TypeVar from dataclasses import dataclass from pydantic import BaseModel, RootModel, BeforeValidator @@ -19,15 +19,21 @@ # region Model-Types - def __ensure_list__[T](values: list[T] | T) -> list[T]: if isinstance(values, list): return values # type: ignore return [values] +def __ensure_str__(values: list[str] | str) -> str: + if isinstance(values, list): + return ' '.join(values) + return values + + T = TypeVar("T") EnsuredList = Annotated[list[T], BeforeValidator(__ensure_list__)] +EnsuredStr = Annotated[str, BeforeValidator(__ensure_str__)] class ConfigModel(BaseModel): @@ -49,19 +55,13 @@ class GroupModel(BaseModel): class RenderSpecModel(BaseModel): files: EnsuredList[FileModel | str] = [] groups: EnsuredList[GroupModel] = [] - configs: EnsuredList[ConfigModel] = [] + configs: dict[str, EnsuredStr] = {} # endregion Model-Types # region Types -@dataclass -class RenderConfiguration: - name: str - spec: str - - @dataclass class CardSpec: spec: str @@ -73,7 +73,7 @@ class RenderSpec(TypedDict): name: str file: Path - configs: Dict[str, RenderConfiguration] + configs: dict[str, str] cards: list[CardDetails] @@ -83,6 +83,7 @@ class RenderSpec(TypedDict): def parse_render_spec_file(render_spec_path: Path) -> RenderSpecModel: + print(RootModel[RenderSpecModel].model_json_schema()) return parse_model(render_spec_path, RootModel[RenderSpecModel]).root @@ -98,11 +99,6 @@ def parse_render_spec(render_spec_path: Path) -> RenderSpec: render_spec_data = parse_render_spec_file(render_spec_path) - configs = { - c.name: RenderConfiguration(c.name, " ".join(c.settings)) - for c in render_spec_data.configs - } - render_spec_name = render_spec_path.stem parent_dir = render_spec_path.parent @@ -149,7 +145,7 @@ def append_card(card_spec: CardSpec): def append_configs(card: CardSpec, card_configs: list[str]) -> CardSpec: resolved_configs = [ - configs[c].spec if c in configs else c for c in card_configs + render_spec_data.configs[c] if c in render_spec_data.configs else c for c in card_configs ] config = " ".join(resolved_configs) return CardSpec( @@ -212,7 +208,7 @@ def expand_variable(card: CardSpec, variable: str, value: str | int): return { "name": render_spec_name, "file": render_spec_path, - "configs": configs, + "configs": render_spec_data.configs, "cards": cards, } diff --git a/src/schmas/render_spec_schema.json b/src/schmas/render_spec_schema.json index 8a92cc59..de687de2 100644 --- a/src/schmas/render_spec_schema.json +++ b/src/schmas/render_spec_schema.json @@ -162,19 +162,22 @@ "title": "Groups" }, "configs": { - "anyOf": [ - { - "items": { - "$ref": "#/$defs/ConfigModel" + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" }, - "type": "array" - }, - { - "$ref": "#/$defs/ConfigModel" - } - ], - "default": [], - "title": "Configs" + { + "type": "string" + } + ] + }, + "default": {}, + "title": "Configs", + "type": "object" } }, "title": "RenderSpecModel", From 24d7697b8adbb54c04842a20e72dc817bd392821 Mon Sep 17 00:00:00 2001 From: Hanno Date: Thu, 9 Apr 2026 13:42:57 +0200 Subject: [PATCH 114/190] Turn RenderSpec into a @dataclass --- src/render_spec.py | 23 +++++++++++++---------- src/utils/images.py | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index 37200411..4ffb45a7 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -9,7 +9,7 @@ import re import glob from pathlib import Path -from typing import TypedDict, Annotated, TypeVar +from typing import Annotated, TypeVar from dataclasses import dataclass from pydantic import BaseModel, RootModel, BeforeValidator @@ -19,6 +19,7 @@ # region Model-Types + def __ensure_list__[T](values: list[T] | T) -> list[T]: if isinstance(values, list): return values # type: ignore @@ -27,7 +28,7 @@ def __ensure_list__[T](values: list[T] | T) -> list[T]: def __ensure_str__(values: list[str] | str) -> str: if isinstance(values, list): - return ' '.join(values) + return " ".join(values) return values @@ -68,7 +69,8 @@ class CardSpec: actual_path: Path | None -class RenderSpec(TypedDict): +@dataclass +class RenderSpec: """Render spec obtained from parsing the file.""" name: str @@ -145,7 +147,8 @@ def append_card(card_spec: CardSpec): def append_configs(card: CardSpec, card_configs: list[str]) -> CardSpec: resolved_configs = [ - render_spec_data.configs[c] if c in render_spec_data.configs else c for c in card_configs + render_spec_data.configs[c] if c in render_spec_data.configs else c + for c in card_configs ] config = " ".join(resolved_configs) return CardSpec( @@ -205,12 +208,12 @@ def expand_variable(card: CardSpec, variable: str, value: str | int): for card_spec in parse_group(group): append_card(card_spec) - return { - "name": render_spec_name, - "file": render_spec_path, - "configs": render_spec_data.configs, - "cards": cards, - } + return RenderSpec( + name=render_spec_name, + file=render_spec_path, + configs=render_spec_data.configs, + cards=cards, + ) # endregion Parsing diff --git a/src/utils/images.py b/src/utils/images.py index 148db4ff..6ff15cb8 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -89,7 +89,7 @@ def add_card(card: CardDetails) -> None: try: for path in render_specs: try: - cards = parse_render_spec(path)["cards"] + cards = parse_render_spec(path).cards for card in cards: add_card(card) except ValidationError as e: From e80f3a2c873ad261258f4416a325365b18f75452 Mon Sep 17 00:00:00 2001 From: Hanno Date: Fri, 10 Apr 2026 11:56:33 +0200 Subject: [PATCH 115/190] Add details about render specs to the README --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 816f0a27..3d321495 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,59 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou ### Rendering -- **Render**: Opens a file picker for choosing images to render. Template used for rendering is determined based on the tab you are in and what templates you have selected. Completed renders are saved to `out/` under Proxyshop's directory. +- **Render**: Opens a file picker for choosing images to render. Template used for rendering is determined based on the tab you are in and what templates you have selected. Completed renders are saved to `out/` under Proxyshop's directory. The chosen files can also be YAML (.yaml, .yml) or JSON (.json), see [Render Specifications](#render_specifications) and [Custom Cards](#custom_cards) for details. - **Templates tab**: Allows selecting a specific template to render cards in. If a card has a layout that isn't supporeted by the selected template it won't be rendered. - **Batch mode tab**: Allows selecting a different template per card layout (e.g. Normal, Transform, Planeswalker, etc.). If some layout is left without a selection cards of that type are skipped. The batch mode is similar to how the old Proxyshop GUI used to function. - **Queue**: Allows viewing what cards have been queued for rendering. Individual entries can be removed from the queue or the whole queue can be cleared. - **Cancel/Resume**: Cancel aborts rendering. The currently active operation is removed but the rest of the queue is left intact. Resume can be used to proceed with the rest of the queue after a cancel. +### Render Specifications + +You can load a YAML file (.yaml or .yml) which contains a specification for queueing multipe cards at once. In these specifications you can add settings to cards without changing their filenames, as well as use glob-patterns to queue multiple files with the same settings. A schema for render specifications is available in the `schemas` folder. + +
+ +Expand for a description and usage-example of Render Specifications + +- Reusable configurations can be defined as follows: +```yml +- configs: + DOM: "[DOM]" # This config specifies the set DOM + SLD: "[sym=STAR]" # Force the STAR set-icon +``` + +- Cards to render can be defined as follows: +```yml +- files: + # Just a string, which is a path relative to the Render Specification + - "Normal/*.*" # Render all cards in the folder Normal + # A string, which is a path relative to the Render Specification + # and a string with additional settings + - file: "Borderless/*.*" # Render all cards in the folder Borderless ... + settings: "[tmpl=Borderless]" # ... using the Borderless template + # Settings can also be a list of strings, and can use the configs defined earlier + - file: "UB/*.*" + settings: + - "[tmpl=Universes Beyond]" + - "SLD" +``` +In the above example, if you have a file `UB/Dungeon Descent.png` it will be rendered from that file, but as if its filename was `Dungeon Descent [tmpl=Universes Beyond] [sym=STAR].png`. Since the same is true for all files in the `UB` folder this makes it easy to render batches of files without fiddling with many filenames. + +- Lastly, you can also define groups, which contain a set of files (or other groups) and settings: +```yml +- groups: + - files: # List of files in this group, same syntax as bove + - "Normal/*.*" + - file: "Borderless/*.*" + settings: "[tmpl=Borderless]" + - file: "UB/*.*" + settings: + - "[tmpl=Universes Beyond]" + - "SLD" + - settings: "(My Favorite Artist)" # Render all files as being by this artist +``` +
+ ### Updater The updater window allows downloading and updating template files. The files can originate from Proxyshop or installed plugins. From 3ac69a4dbc3143b09e4dbc6cc9b78b9f8e0acdc6 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 10 Apr 2026 15:22:43 +0300 Subject: [PATCH 116/190] feat: Add rudimentary support for Prepare cards --- src/commands/test/frame_logic.py | 4 +- src/data/tests/template_renders.toml | 4 + src/enums/layers.py | 9 +++ src/enums/mtg.py | 4 + src/layouts.py | 94 +++++++++++++++------- src/templates/prepare.py | 114 +++++++++++++++++++++++++++ src/utils/scryfall.py | 37 ++++----- 7 files changed, 217 insertions(+), 49 deletions(-) create mode 100644 src/templates/prepare.py diff --git a/src/commands/test/frame_logic.py b/src/commands/test/frame_logic.py index b4fe037d..3cc582e8 100644 --- a/src/commands/test/frame_logic.py +++ b/src/commands/test/frame_logic.py @@ -18,7 +18,7 @@ from src.cards import CardDetails, get_card_data, process_card_data from src.console import LogColors from src.enums.mtg import CardTextPatterns -from src.layouts import CardLayout, layout_map +from src.layouts import NormalLayout, layout_map _logger = getLogger(__name__) @@ -38,7 +38,7 @@ def get_frame_logic_cases() -> dict[str, dict[str, FrameData]]: return load_data_file(Path(PATH.SRC_DATA_TESTS, "frame_data.toml")) -def format_result(layout: CardLayout) -> FrameData: +def format_result(layout: NormalLayout) -> FrameData: """Format frame logic test result for comparison. Args: diff --git a/src/data/tests/template_renders.toml b/src/data/tests/template_renders.toml index 19937120..22d032d2 100644 --- a/src/data/tests/template_renders.toml +++ b/src/data/tests/template_renders.toml @@ -77,6 +77,10 @@ Gemrazer = "Creature" "Kasmina, Enigma Sage" = "Dual" "Karn Liberated" = "Colorless" +[prepare] +"Emeritus of Truce [SOS] {13}" = "Mono Color with Instant" +"Lluwen, Exchange Student [SOS] {199}" = "Legendary Dual Color with Sorcery" + [prototype] "Rust Goliath" = "Flavor text" "Phyrexian Fleshgorger" = "Normal" diff --git a/src/enums/layers.py b/src/enums/layers.py index 21a53b3c..048566dd 100644 --- a/src/enums/layers.py +++ b/src/enums/layers.py @@ -251,6 +251,15 @@ class LAYERS(StrEnum): DIVIDER_ADVENTURE = "Divider - Adventure" WINGS = "Wings" + # Prepare + PREPARE = "Prepare" + NAME_PREPARE = "Card Name - Prepare" + TYPE_LINE_PREPARE = "Typeline - Prepare" + MANA_COST_PREPARE = "Mana Cost - Prepare" + RULES_TEXT_PREPARE = "Rules Text - Prepare" + TEXTBOX_REFERENCE_PREPARE = "Textbox Reference - Prepare" + DIVIDER_PREPARE = "Divider - Prepare" + # Battles DEFENSE = "Defense" DEFENSE_REFERENCE = "Defense Reference" diff --git a/src/enums/mtg.py b/src/enums/mtg.py index c6b862bf..e71e6c08 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -26,6 +26,7 @@ class LayoutCategory(StrEnum): Planeswalker = "Planeswalker" PlaneswalkerMDFC = "PW MDFC" PlaneswalkerTransform = "PW Transform" + Prepare = "Prepare" Prototype = "Prototype" Saga = "Saga" Split = "Split" @@ -58,6 +59,7 @@ class LayoutType(StrEnum): PlaneswalkerMDFCFront = "pw_mdfc_front" PlaneswalkerTransformBack = "pw_tf_back" PlaneswalkerTransformFront = "pw_tf_front" + Prepare = "prepare" Prototype = "prototype" Saga = "saga" Split = "split" @@ -80,6 +82,7 @@ class LayoutScryfall(StrEnum): Class = "class" Saga = "saga" Adventure = "adventure" + Prepare = "prepare" Mutate = "mutate" Prototype = "prototype" Battle = "battle" @@ -121,6 +124,7 @@ class LayoutScryfall(StrEnum): LayoutCategory.Mutate: (LayoutType.Mutate,), LayoutCategory.Prototype: (LayoutType.Prototype,), LayoutCategory.Adventure: (LayoutType.Adventure,), + LayoutCategory.Prepare: (LayoutType.Prepare,), LayoutCategory.Leveler: (LayoutType.Leveler,), LayoutCategory.Split: (LayoutType.Split,), LayoutCategory.Battle: (LayoutType.Battle,), diff --git a/src/layouts.py b/src/layouts.py index df75cfc4..f87dfd8f 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -181,6 +181,16 @@ def join_dual_card_layouts[T: NormalLayout | None]( return normal + add +def _get_other_side_color_identity(colors: list[str], mana_cost: str) -> str: + if check_hybrid_mana_cost(colors, mana_cost): + return LAYERS.LAND + if len(colors) > 1: + return LAYERS.GOLD + if not colors: + return LAYERS.COLORLESS + return colors[0] + + """ * Layout Classes """ @@ -1276,13 +1286,9 @@ def color_identity_adventure(self) -> list[str]: @cached_property def adventure_colors(self) -> str: """Color identity of adventure side frame elements.""" - if check_hybrid_mana_cost(self.color_identity_adventure, self.mana_adventure): - return LAYERS.LAND - if len(self.color_identity_adventure) > 1: - return LAYERS.GOLD - if not self.color_identity_adventure: - return LAYERS.COLORLESS - return self.color_identity_adventure[0] + return _get_other_side_color_identity( + self.color_identity_adventure, self.mana_adventure + ) class LevelerLayout(NormalLayout): @@ -1894,6 +1900,8 @@ class StationDetails(TypedDict): class StationLayout(NormalLayout): + """Station card layout, introduced in Edge of Eternities.""" + _pt_pattern = re.compile(r"([0-9]+)/([0-9]+)") _station_level_pattern = re.compile(r"\n([0-9]+\+) \| ") @@ -1949,33 +1957,60 @@ def stations(self) -> list[StationDetails]: return out +class PrepareLayout(NormalLayout): + """Prepare card layout, introduced in Secrets of Strixhaven.""" + + @cached_property + def type(self) -> LayoutType: + return LayoutType.Prepare + + @cached_property + def spell_side(self) -> ScryfallCardFace: + if self.scryfall.card_faces: + return self.scryfall.card_faces[1] + raise ValueError( + f"Scryfall data doesn't have a card face for spell side: {pprint(self.scryfall)}" + ) + + @cached_property + def mana_prepare(self) -> str: + return self.spell_side.mana_cost or "" + + @cached_property + def name_prepare(self) -> str: + return self._get_card_name(self.spell_side) + + @cached_property + def type_line_prepare(self) -> str: + return self.spell_side.type_line or "" + + @cached_property + def oracle_text_prepare(self) -> str: + if self.is_alt_lang and self.spell_side.printed_text: + return self.spell_side.printed_text + return self.spell_side.oracle_text or "" + + @cached_property + def flavor_text_prepare(self) -> str: + return self.spell_side.flavor_text or "" + + @cached_property + def colors_prepare(self) -> list[str]: + """Colors present in the spell side mana cost.""" + return [n for n in get_ordered_colors(get_mana_cost_colors(self.mana_prepare))] + + @cached_property + def color_identity_prepare(self) -> str: + """Color identity of spell side frame elements.""" + return _get_other_side_color_identity(self.colors_prepare, self.mana_prepare) + + """ * Types & Enums """ -"""All card layout classes.""" -CardLayout = ( - NormalLayout - | TransformLayout - | ModalDoubleFacedLayout - | AdventureLayout - | LevelerLayout - | SagaLayout - | MutateLayout - | PrototypeLayout - | ClassLayout - | SplitLayout - | PlanarLayout - | TokenLayout -) - -"""Planeswalker card layout classes.""" -PlaneswalkerLayouts = ( - PlaneswalkerLayout | PlaneswalkerTransformLayout | PlaneswalkerMDFCLayout -) - """Maps Scryfall layout names to their respective layout class.""" -layout_map: dict[str, type[CardLayout]] = { +layout_map: dict[str, type[NormalLayout]] = { # Definitions supported by Scryfall natively LayoutScryfall.Normal: NormalLayout, LayoutScryfall.Split: SplitLayout, @@ -1987,6 +2022,7 @@ def stations(self) -> list[StationDetails]: LayoutScryfall.Class: ClassLayout, LayoutScryfall.Saga: SagaLayout, LayoutScryfall.Adventure: AdventureLayout, + LayoutScryfall.Prepare: PrepareLayout, LayoutScryfall.Mutate: MutateLayout, LayoutScryfall.Prototype: PrototypeLayout, LayoutScryfall.Battle: BattleLayout, diff --git a/src/templates/prepare.py b/src/templates/prepare.py new file mode 100644 index 00000000..27121483 --- /dev/null +++ b/src/templates/prepare.py @@ -0,0 +1,114 @@ +""" +* Prepare Templates +""" + +from collections.abc import Callable +from functools import cached_property + +from photoshop.api._artlayer import ArtLayer + +import src.helpers as psd +import src.text_layers as text_classes +from src.enums.layers import LAYERS +from src.layouts import PrepareLayout +from src.templates._core import NormalTemplate + + +class PrepareMod(NormalTemplate): + """A modifier class which adds functionality required by Prepare cards, + introduced in Secrets of Strixhaven. + + Adds: + * Spell side text layers (Mana cost, name, typeline, and oracle text) and textbox reference. + """ + + @cached_property + def is_prepare(self) -> bool: + return isinstance(self.layout, PrepareLayout) + + # region Text Layers + + @cached_property + def text_layer_methods(self) -> list[Callable[[], None]]: + """Add Prepare text layers step.""" + funcs = super().text_layer_methods + if isinstance(self.layout, PrepareLayout): + funcs.append(self.text_layers_prepare) + return funcs + + @cached_property + def text_layer_name_prepare(self) -> ArtLayer | None: + """Name for the prepare side.""" + return psd.getLayer(LAYERS.NAME_PREPARE, self.text_group) + + @cached_property + def text_layer_mana_prepare(self) -> ArtLayer | None: + """Mana cost for the prepare side.""" + return psd.getLayer(LAYERS.MANA_COST_PREPARE, self.text_group) + + @cached_property + def text_layer_type_prepare(self) -> ArtLayer | None: + """Type line for the prepare side.""" + return psd.getLayer(LAYERS.TYPE_LINE_PREPARE, self.text_group) + + @cached_property + def text_layer_rules_prepare(self) -> ArtLayer | None: + """Rules text for the prepare side.""" + return psd.getLayer(LAYERS.RULES_TEXT_PREPARE, self.text_group) + + @cached_property + def divider_layer_prepare(self) -> ArtLayer | None: + """Flavor divider for the prepare side.""" + return psd.getLayer(LAYERS.DIVIDER_PREPARE, self.text_group) + + # region Text Layers + + # region References + + @cached_property + def textbox_reference_prepare(self) -> ArtLayer | None: + return psd.get_reference_layer( + LAYERS.TEXTBOX_REFERENCE_PREPARE, self.text_group + ) + + # endregion References + + # region Methods + + def text_layers_prepare(self) -> None: + if isinstance(self.layout, PrepareLayout): + # Add prepare text layers + if self.text_layer_mana_prepare: + self.text.append( + text_classes.FormattedTextField( + layer=self.text_layer_mana_prepare, + contents=self.layout.mana_prepare, + ) + ) + if self.text_layer_name_prepare: + self.text.append( + text_classes.ScaledTextField( + layer=self.text_layer_name_prepare, + contents=self.layout.name_prepare, + reference=self.text_layer_mana_prepare, + ) + ) + if self.text_layer_rules_prepare: + self.text.append( + text_classes.FormattedTextArea( + layer=self.text_layer_rules_prepare, + contents=self.layout.oracle_text_prepare, + reference=self.textbox_reference_prepare, + flavor=self.layout.flavor_text_prepare, + centered=False, + ) + ) + if self.text_layer_type_prepare: + self.text.append( + text_classes.TextField( + layer=self.text_layer_type_prepare, + contents=self.layout.type_line_prepare, + ) + ) + + # endregion Methods diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 5d26fcc8..0b936e3e 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -34,30 +34,31 @@ P = ParamSpec("P") LayoutType = Literal[ - "normal", - "split", - "flip", - "transform", - "modal_dfc", - "meld", - "leveler", - "class", - "case", - "saga", "adventure", - "mutate", - "prototype", + "art_series", + "augment", "battle", - "planar", - "scheme", - "vanguard", - "token", + "case", + "class", "double_faced_token", "emblem", - "augment", + "flip", "host", - "art_series", + "leveler", + "meld", + "modal_dfc", + "mutate", + "normal", + "planar", + "prepare", + "prototype", "reversible_card", + "saga", + "scheme", + "split", + "token", + "transform", + "vanguard", # Non-official extensions "planeswalker", "planeswalker_tf", From 39cd91021bd0031b87037e87df008be90a9d2dd1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 10 Apr 2026 15:24:24 +0300 Subject: [PATCH 117/190] refactor: Remove redundant code --- src/cards.py | 19 ------------------- src/templates/adventure.py | 5 +---- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/cards.py b/src/cards.py index d8f7537a..61cf6e8e 100644 --- a/src/cards.py +++ b/src/cards.py @@ -288,25 +288,6 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: elif "Battle" in card_face.type_line: data.layout = "battle" - # Fix Adventure Land mana costs locally, as Scryfall seems unwilling to - # fix the issue on their end even after reporting it and waiting for months. - # Once Scryfall corrects their data this fix can be removed. - if data.layout == "adventure": - card_faces = data.card_faces - main_face = card_faces[0] - adventure_face = card_faces[1] - if ( - main_face.type_line - and main_face.type_line.startswith("Land ") - and main_face.mana_cost - and not adventure_face.mana_cost - ): - # Swap the Land and Adventure faces' mana costs - main_face.mana_cost, adventure_face.mana_cost = ( - adventure_face.mana_cost, - main_face.mana_cost, - ) - return data # Add Mutate layout diff --git a/src/templates/adventure.py b/src/templates/adventure.py index 4fcfd97e..7984bb00 100644 --- a/src/templates/adventure.py +++ b/src/templates/adventure.py @@ -11,7 +11,7 @@ import src.helpers as psd import src.text_layers as text_classes from src.enums.layers import LAYERS -from src.layouts import AdventureLayout, NormalLayout +from src.layouts import AdventureLayout from src.schema.colors import ColorObject, GradientConfig from src.templates._core import NormalTemplate from src.templates._vector import VectorTemplate @@ -28,9 +28,6 @@ class AdventureMod(NormalTemplate): * Adventure side text layers (Mana cost, name, typeline, and oracle text) and textbox reference. """ - def __init__(self, layout: NormalLayout): - super().__init__(layout) - """ * Mixin Methods """ From 0fab3855399c4b6e2cd1984b4765fee0e14e5933 Mon Sep 17 00:00:00 2001 From: Hanno Date: Fri, 10 Apr 2026 21:09:42 +0200 Subject: [PATCH 118/190] Add notes on supported kwargs --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3d321495..00ec4568 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,11 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou ``` Brainstorm [SLD] {175}$My Creator Name.jpg ``` +- Proxyshop also has support for additional key-value pairs in the filename via `[key=value]`. + - Force Set-Symbol `[sym=SET]`: The `SET` part of this follows the same rules as `[SET]`, but will only affect the set-symbol. + - Template `[tmpl=TEMPLATE]`: Forces Proxyshop to render the card with the given template. Note that `TEMPLATE` has to match exactly the name of a template as seen in the UI _and_ the template has to support the card's layout. Otherwise Proxyshop will fall back to the selected template. + - Art-File `[art=FILE]`: Force to use a specific art, other than the art-file. `FILE` has to be an absolute path or relative to the art-file. + - Nickname: `[nick=NICKNAME]`: Use the specified nickname, only supported by select templates, e.g. Borderless. # 💻 Using the Proxyshop GUI From 704c4c9f1938f0ef476412b771c8c3c9e02d445e Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 11 Apr 2026 08:43:44 +0300 Subject: [PATCH 119/190] fix: Ensure image transform output folder exists and discard alpha when transforming to JPEG --- src/gui/qml/models/image_transform_model.py | 5 +++-- src/utils/images.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/qml/models/image_transform_model.py b/src/gui/qml/models/image_transform_model.py index 9c86bc0e..aab12c4e 100644 --- a/src/gui/qml/models/image_transform_model.py +++ b/src/gui/qml/models/image_transform_model.py @@ -106,9 +106,10 @@ async def action(): async def transform_image(self, image: Path) -> None: try: + out_dir = image.parent / "compressed" + out_dir.mkdir(parents=True, exist_ok=True) out_path = ( - image.parent - / "compressed" + out_dir / image.with_suffix( IMAGE_ENCODING_TO_SUFFIX_MAPPING[self._image_file_format] ).name diff --git a/src/utils/images.py b/src/utils/images.py index 282c6c9c..77c0d790 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -39,6 +39,8 @@ def save_scaled_card_image( if image_format == "png": save_kwargs = {"optimize": True} elif image_format == "jpeg": + if f.mode == "RGBA": + f = f.convert("RGB") save_kwargs = {"optimize": True, "quality": quality} elif image_format == "webp": save_kwargs = {"quality": quality, "method": 6} From 215caebcc48cb862bfb96a6e21c9973a474d6792 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 8 Apr 2026 15:56:46 +0300 Subject: [PATCH 120/190] refactor: Simplify code --- src/gui/qml/models/batch_rendering_model.py | 4 ++-- src/gui/qml/models/template_list_model.py | 5 +++-- src/render/setup.py | 16 +++++++++++----- src/templates/_core.py | 8 +++----- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index 5129aa62..068a326b 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -71,8 +71,8 @@ def __init__( self._message_dialog_model = message_dialog_model self._render_queue = render_queue self._test_renders_model = test_renders_model + self._template_library = template_library - self.template_library = template_library self.built_in_templates_by_layout: dict[ LayoutCategory, dict[str, AssembledTemplate] ] = {} @@ -200,7 +200,7 @@ def add_render( ) -> None: if render_operations := prepare_render_operations( self.template_choices, - self.template_library, + self._template_library, (input,), file_dialog=self._file_dialog_model, message_dialog=self._message_dialog_model, diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 42a55122..2eb38e7a 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -70,7 +70,8 @@ def __init__( self._message_dialog_model = message_dialog_model self._template_library = template_library self._test_renders_model = test_renders_model - self.template_library = template_library + self._template_library = template_library + self.built_in_templates = template_library.built_in_templates_by_name self.plugin_templates = template_library.plugin_templates_by_name @@ -161,7 +162,7 @@ def add_render( ) -> None: if render_operations := prepare_render_operations( template, - self.template_library, + self._template_library, (input,), file_dialog=self._file_dialog_model, message_dialog=self._message_dialog_model, diff --git a/src/render/setup.py b/src/render/setup.py index dc3c2c4b..8e1427d7 100644 --- a/src/render/setup.py +++ b/src/render/setup.py @@ -6,7 +6,12 @@ from src import CON, ENV from src._config import AppConfig -from src._loader import ConfigHandler, RenderableTemplate, TemplateLibrary, get_template_class +from src._loader import ( + ConfigHandler, + RenderableTemplate, + TemplateLibrary, + get_template_class, +) from src.cards import CardDetails from src.enums.mtg import LayoutCategory from src.layouts import NormalLayout, assign_layout, join_dual_card_layouts @@ -165,13 +170,14 @@ def prepare_render_operations( template_to_use: RenderableTemplate | None = None - if 'tmpl' in card['kwargs']: - template_name = card['kwargs']['tmpl'] - if builtin_template := template_library.built_in_templates_by_name.get(template_name, None): + if template_name := card["kwargs"].get("tmpl"): + if builtin_template := template_library.built_in_templates_by_name.get( + template_name, None + ): if layout.category in builtin_template.layout_categories: template_to_use = builtin_template if template_to_use is None: - for _, templates in template_library.plugin_templates_by_name.items(): + for templates in template_library.plugin_templates_by_name.values(): if plugin_template := templates.get(template_name, None): if layout.category in plugin_template.layout_categories: template_to_use = plugin_template diff --git a/src/templates/_core.py b/src/templates/_core.py index bb186a34..420a8c9b 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -2,7 +2,6 @@ * CORE PROXYSHOP TEMPLATES """ -import os from collections.abc import Callable, Sequence from contextlib import suppress from functools import cached_property @@ -247,8 +246,8 @@ def save_mode(self) -> Callable[[Path, Document | None], None]: @cached_property def output_directory(self) -> Path: """Directory where the rendered image will be saved to.""" - if output_directory := self.layout.file['kwargs'].get("dir", None): - return PATH.OUT / Path(output_directory) + if output_directory := self.layout.file["kwargs"].get("dir", None): + return PATH.OUT / output_directory return PATH.OUT @cached_property @@ -1642,8 +1641,7 @@ async def execute(self) -> bool: await self.pause_async("Rendering paused for manual editing.") # Make sure output folder exists - if not os.path.exists(self.output_directory): - os.makedirs(self.output_directory) + self.output_directory.mkdir(parents=True, exist_ok=True) # Save the document if not self.run_tasks( From 96cfe38350634a9a4a1397091f408b5f1c10b52c Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 8 Apr 2026 15:59:18 +0300 Subject: [PATCH 121/190] fix(match_images_with_data_files): Retain exception information when reraising it --- src/utils/images.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/utils/images.py b/src/utils/images.py index 6ff15cb8..d2266890 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -1,8 +1,8 @@ +from dataclasses import dataclass from logging import getLogger from os import PathLike from pathlib import Path from typing import IO -from dataclasses import dataclass from PIL import Image from pydantic import ValidationError @@ -81,8 +81,8 @@ def add_card(card: CardDetails) -> None: ScryfallCard.model_validate_json(data_file.read_bytes()), ) ) - except ValidationError: - raise _ValidationError(data_file) + except ValidationError as e: + raise _ValidationError(data_file) from e else: results.append(card) @@ -93,7 +93,10 @@ def add_card(card: CardDetails) -> None: for card in cards: add_card(card) except ValidationError as e: - raise _ValidationError(path) + raise _ValidationError(path) from e + + for card in cards: + add_card(card) for path in image_files: card = parse_card_info(path) From 5086ba7dde07df5f2083bf02e4497a61af160295 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 14 Apr 2026 14:24:49 +0300 Subject: [PATCH 122/190] fix(BaseTemplate): Fix Pyright type error by providing an explicit type for a fallback dict --- src/templates/_core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/templates/_core.py b/src/templates/_core.py index 420a8c9b..d29a6862 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -22,7 +22,7 @@ import src.helpers as psd from src import APP, CON -from src._state import PATH +from src._state import PATH, WatermarkFormat from src.cards import sanitize_card_filename, strip_reminder_text from src.enums.layers import LAYERS from src.enums.mtg import CardTextPatterns, MagicIcons @@ -1100,7 +1100,9 @@ def create_watermark(self) -> None: return # Get watermark custom settings if available - wm_details = CON.watermarks.get(self.layout.watermark, {}) + wm_details: WatermarkFormat | dict[str, float] = CON.watermarks.get( + self.layout.watermark, {} + ) # Import and frame the watermark wm = psd.import_svg( From fc126ded288adc5fe77daf120e763493ba0112a7 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 1 Nov 2023 13:41:44 +0100 Subject: [PATCH 123/190] Add basic single-card panorama mode (broken commit) --- src/helpers/position.py | 44 ++++++++++++++++++++++++++ src/layouts.py | 4 +++ src/templates/_core.py | 70 ++++++++++++++++++++++------------------- src/templates/normal.py | 15 +++++++-- 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 69d9c541..3d7902cd 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -250,6 +250,50 @@ def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) """ +def frame_panoramas( + layer: ArtLayer | LayerSet, + reference: ReferenceLayer, + anchor: AnchorPosition = AnchorPosition.TopLeft, +) -> list[ArtLayer]: + """ + Scale, duplicate and position a layer onto a set of layers within the bounds of a reference layer to make a borderless panorama. + @param layer: Layer to scale and position. + @param reference: Reference frame to position within. + @param anchor: Anchor position for scaling the layer. + """ + # Get layer and reference dimensions and scale the layer to fit the height + layer_dim = get_layer_dimensions(layer) + ref_dim = get_layer_dimensions(reference) + scale = 100 * ref_dim["height"] / layer_dim["height"] + layer.resize(scale, scale, anchor) + + # Get layer dimensions and check how many segments we'll have + # If we're "close to" having an additional segment we scale the layer more + layer_dim = get_layer_dimensions(layer) + num_segments = int(layer_dim["width"] // ref_dim["width"]) + num_segments_precise = layer_dim["width"] / ref_dim["width"] + if ( + num_segments_precise > num_segments + and (num_segments_precise - num_segments) > 0.75 + ): + scale = 100 * (num_segments + 1) / num_segments_precise + layer.resize(scale, scale, anchor) + num_segments = num_segments + 1 + + # Align the original layer on the left + alignments = ("left", "center_y") + align(alignments, layer, ref_dim) + + # Duplicate layer n times, moving over every time + new_layers: list[ArtLayer | LayerSet] = [layer] + for i in range(1, num_segments): + new_layer: LayerSet = new_layers[i - 1].duplicate() + new_layer.translate(-ref_dim["width"], 0) + new_layers.append(new_layer) + + return new_layers + + def frame_layer( layer: ArtLayer | LayerSet, ref: ArtLayer | LayerSet | LayerDimensions, diff --git a/src/layouts.py b/src/layouts.py index 870043e9..b5e0906f 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -233,6 +233,10 @@ def is_transform(self) -> bool: @cached_property def is_mdfc(self) -> bool: return False + + @cached_property + def is_panorama(self) -> bool: + return self.file['kwargs'].get('pano_size', None) is not None """ * Core Data diff --git a/src/templates/_core.py b/src/templates/_core.py index d29a6862..69f533dc 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -800,9 +800,12 @@ def load_artwork( layer=art_layer, path=art_file, docref=self.docref ) - if art_reference: - # Frame the artwork + # Frame the artwork + if self.layout.is_panorama and art_reference is not None: + art_layers = psd.frame_panoramas(self.active_layer, art_reference) + elif art_reference: psd.frame_layer(layer=art_layer, ref=art_reference) + art_layers = [art_layer] if self.config.pause_for_manual_art_alignment: if not self.pause("Adjust the art alignment manually."): @@ -810,37 +813,38 @@ def load_artwork( # Perform content aware fill if needed if self.is_content_aware_enabled: - if self.config.fill_mode == FillMode.CONTENT_AWARE_FILL: - psd.content_aware_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - art_selection_hook=self.art_fill_selection_hook, - ) - elif self.config.fill_mode == FillMode.GENERATIVE_FILL: - if _doc_generated := psd.generative_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - close_doc=bool(not self.config.select_variation), - docref=self.docref, - art_selection_hook=self.art_fill_selection_hook, - ): - # Document reference was returned, await user intervention - if not self.pause("Select a Generative Fill variation."): - return - _doc_generated.close(SaveOptions.SaveChanges) - return - elif self.config.fill_mode == FillMode.REMOVE_CONTENT_FILL: - psd.remove_content_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - art_selection_hook=self.art_fill_selection_hook, - ) + for art_layer in art_layers: + if self.config.fill_mode == FillMode.CONTENT_AWARE_FILL: + psd.content_aware_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, + ) + elif self.config.fill_mode == FillMode.GENERATIVE_FILL: + if _doc_generated := psd.generative_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + close_doc=bool(not self.config.select_variation), + docref=self.docref, + art_selection_hook=self.art_fill_selection_hook, + ): + # Document reference was returned, await user intervention + if not self.pause("Select a Generative Fill variation."): + return + _doc_generated.close(SaveOptions.SaveChanges) + return + elif self.config.fill_mode == FillMode.REMOVE_CONTENT_FILL: + psd.remove_content_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, + ) def paste_scryfall_scan( self, rotate: bool = False, visible: bool = True diff --git a/src/templates/normal.py b/src/templates/normal.py index 2b3007cf..15570cfe 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1453,6 +1453,14 @@ def crown_texture_enabled(self) -> bool: ) ) + @cached_property + def panorama_mode_enabled(self) -> bool: + """Returns True if panorama mode is enabled.""" + return bool(cfg.get_setting( + section="FRAME", + key="Panorama.Mode", + default=False)) + @cached_property def multicolor_textbox(self) -> bool: """Returns True if Textbox for multicolored cards should use blended colors.""" @@ -1547,8 +1555,11 @@ def frame_type(self) -> str: @cached_property def art_frame(self) -> str: - # Use different positioning based on textbox size - return f"{LAYERS.ART_FRAME} {self.size}" + if self.panorama_mode_enabled: + return f"{LAYERS.FULL_ART_FRAME}" + else: + # Use different positioning based on textbox size + return f"{LAYERS.ART_FRAME} {self.size}" """ * Bool From f86b1b517f0e56ab40f89e6d77d8a92bccd91b15 Mon Sep 17 00:00:00 2001 From: Mal Date: Fri, 3 Nov 2023 13:21:04 +0100 Subject: [PATCH 124/190] Proof of concept multi-card panorama mode --- src/helpers/position.py | 19 ++++-------- src/layouts.py | 4 +++ src/templates/_core.py | 66 ++++++++++++++++++++--------------------- 3 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 3d7902cd..a1c25567 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -250,13 +250,14 @@ def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) """ -def frame_panoramas( +def frame_panorama( layer: ArtLayer | LayerSet, reference: ReferenceLayer, + panorama_element: int, anchor: AnchorPosition = AnchorPosition.TopLeft, -) -> list[ArtLayer]: +) -> None: """ - Scale, duplicate and position a layer onto a set of layers within the bounds of a reference layer to make a borderless panorama. + Scale and position a layer onto within the bounds of a reference layer to make a borderless panorama. @param layer: Layer to scale and position. @param reference: Reference frame to position within. @param anchor: Anchor position for scaling the layer. @@ -280,18 +281,10 @@ def frame_panoramas( layer.resize(scale, scale, anchor) num_segments = num_segments + 1 - # Align the original layer on the left + # Align the original layer on the left, then move it according to the given number alignments = ("left", "center_y") align(alignments, layer, ref_dim) - - # Duplicate layer n times, moving over every time - new_layers: list[ArtLayer | LayerSet] = [layer] - for i in range(1, num_segments): - new_layer: LayerSet = new_layers[i - 1].duplicate() - new_layer.translate(-ref_dim["width"], 0) - new_layers.append(new_layer) - - return new_layers + layer.translate(-ref_dim["width"] * panorama_element, 0) def frame_layer( diff --git a/src/layouts.py b/src/layouts.py index b5e0906f..02dbdfa9 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -238,6 +238,10 @@ def is_mdfc(self) -> bool: def is_panorama(self) -> bool: return self.file['kwargs'].get('pano_size', None) is not None + @cached_property + def panorama_element(self) -> int: + return int(self.file['kwargs'].get('pano_idx', 0)) + """ * Core Data """ diff --git a/src/templates/_core.py b/src/templates/_core.py index 69f533dc..28efe3c2 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -802,10 +802,9 @@ def load_artwork( # Frame the artwork if self.layout.is_panorama and art_reference is not None: - art_layers = psd.frame_panoramas(self.active_layer, art_reference) + psd.frame_panorama(art_layer, art_reference, self.layout.panorama_element) elif art_reference: psd.frame_layer(layer=art_layer, ref=art_reference) - art_layers = [art_layer] if self.config.pause_for_manual_art_alignment: if not self.pause("Adjust the art alignment manually."): @@ -813,38 +812,37 @@ def load_artwork( # Perform content aware fill if needed if self.is_content_aware_enabled: - for art_layer in art_layers: - if self.config.fill_mode == FillMode.CONTENT_AWARE_FILL: - psd.content_aware_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - art_selection_hook=self.art_fill_selection_hook, - ) - elif self.config.fill_mode == FillMode.GENERATIVE_FILL: - if _doc_generated := psd.generative_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - close_doc=bool(not self.config.select_variation), - docref=self.docref, - art_selection_hook=self.art_fill_selection_hook, - ): - # Document reference was returned, await user intervention - if not self.pause("Select a Generative Fill variation."): - return - _doc_generated.close(SaveOptions.SaveChanges) - return - elif self.config.fill_mode == FillMode.REMOVE_CONTENT_FILL: - psd.remove_content_fill_edges( - layer=art_layer, - contract=self.config.fill_contract, - smooth=self.config.fill_smooth, - feather=self.config.fill_feather, - art_selection_hook=self.art_fill_selection_hook, - ) + if self.config.fill_mode == FillMode.CONTENT_AWARE_FILL: + psd.content_aware_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, + ) + elif self.config.fill_mode == FillMode.GENERATIVE_FILL: + if _doc_generated := psd.generative_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + close_doc=bool(not self.config.select_variation), + docref=self.docref, + art_selection_hook=self.art_fill_selection_hook, + ): + # Document reference was returned, await user intervention + if not self.pause("Select a Generative Fill variation."): + return + _doc_generated.close(SaveOptions.SaveChanges) + return + elif self.config.fill_mode == FillMode.REMOVE_CONTENT_FILL: + psd.remove_content_fill_edges( + layer=art_layer, + contract=self.config.fill_contract, + smooth=self.config.fill_smooth, + feather=self.config.fill_feather, + art_selection_hook=self.art_fill_selection_hook, + ) def paste_scryfall_scan( self, rotate: bool = False, visible: bool = True From 25411d88bdc1228473d4c00524dd79f3cc3ab5cf Mon Sep 17 00:00:00 2001 From: Mal Date: Thu, 14 Dec 2023 14:13:55 +0100 Subject: [PATCH 125/190] Add vertical panorama support --- src/helpers/position.py | 41 ++++++++++++++++++++++------------------- src/layouts.py | 12 ++++++++++++ src/templates/normal.py | 10 +++++++++- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index a1c25567..288ce6fe 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -254,37 +254,40 @@ def frame_panorama( layer: ArtLayer | LayerSet, reference: ReferenceLayer, panorama_element: int, + panorama_size: tuple[int, int], anchor: AnchorPosition = AnchorPosition.TopLeft, ) -> None: """ - Scale and position a layer onto within the bounds of a reference layer to make a borderless panorama. + Scale and position a layer within the bounds of a reference layer to make a borderless panorama. @param layer: Layer to scale and position. @param reference: Reference frame to position within. @param anchor: Anchor position for scaling the layer. """ - # Get layer and reference dimensions and scale the layer to fit the height - layer_dim = get_layer_dimensions(layer) + # Get layer and full reference dimensions + art_dim: LayerDimensions = get_layer_dimensions(layer) ref_dim = get_layer_dimensions(reference) - scale = 100 * ref_dim["height"] / layer_dim["height"] + ref_dim["width"] = ref_dim["width"] * panorama_size[0] + ref_dim["height"] = ref_dim["height"] * panorama_size[1] + + # Scale the layer to fit either the largest dimension + scale = 100 * max( + (ref_dim["width"] / art_dim["width"]), (ref_dim["height"] / art_dim["height"]) + ) layer.resize(scale, scale, anchor) - # Get layer dimensions and check how many segments we'll have - # If we're "close to" having an additional segment we scale the layer more - layer_dim = get_layer_dimensions(layer) - num_segments = int(layer_dim["width"] // ref_dim["width"]) - num_segments_precise = layer_dim["width"] / ref_dim["width"] - if ( - num_segments_precise > num_segments - and (num_segments_precise - num_segments) > 0.75 - ): - scale = 100 * (num_segments + 1) / num_segments_precise - layer.resize(scale, scale, anchor) - num_segments = num_segments + 1 - - # Align the original layer on the left, then move it according to the given number + # Align the original layer on the left alignments = ("left", "center_y") align(alignments, layer, ref_dim) - layer.translate(-ref_dim["width"] * panorama_element, 0) + layer.translate(0, ref_dim["height"] / panorama_size[1]) + + # Move the layer according to the given index + ref_dim = ( + reference if isinstance(reference, dict) else get_layer_dimensions(reference) + ) + pano_x = panorama_element % panorama_size[0] + pano_y = panorama_element // panorama_size[0] + layer.translate(-ref_dim["width"] * pano_x, 0) + layer.translate(0, -ref_dim["height"] * pano_y) def frame_layer( diff --git a/src/layouts.py b/src/layouts.py index 02dbdfa9..f466ebb0 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -242,6 +242,18 @@ def is_panorama(self) -> bool: def panorama_element(self) -> int: return int(self.file['kwargs'].get('pano_idx', 0)) + @cached_property + def panorama_size(self) -> tuple[int, int]: + panorama_size = self.file['kwargs'].get('pano_size', "1x1") + panorama_size = [int(c) for c in panorama_size.split("x")] + match len(panorama_size): + case 0: + return (1, 1) + case 1: + return (panorama_size[0], 1) + case _: + return (panorama_size[0], panorama_size[1]) + """ * Core Data """ diff --git a/src/templates/normal.py b/src/templates/normal.py index 15570cfe..d2b30f7c 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1461,6 +1461,11 @@ def panorama_mode_enabled(self) -> bool: key="Panorama.Mode", default=False)) + @cached_property + def panorama_is_horizontal(self) -> bool: + """Returns True if panorama goes both down and to the side.""" + return self.panorama_mode_enabled and self.layout.file['panorama_size'][1] > 1 + @cached_property def multicolor_textbox(self) -> bool: """Returns True if Textbox for multicolored cards should use blended colors.""" @@ -1556,7 +1561,10 @@ def frame_type(self) -> str: @cached_property def art_frame(self) -> str: if self.panorama_mode_enabled: - return f"{LAYERS.FULL_ART_FRAME}" + if self.panorama_is_horizontal: + return f"{LAYERS.FULL_CARD_FRAME}" + else: + return f"{LAYERS.FULL_ART_FRAME}" else: # Use different positioning based on textbox size return f"{LAYERS.ART_FRAME} {self.size}" From 89e0bfb8115a517b576751bba0c31aa727d723ab Mon Sep 17 00:00:00 2001 From: Mal Date: Sat, 6 Jan 2024 12:37:37 +0100 Subject: [PATCH 126/190] Fix panorama mode --- src/helpers/position.py | 10 +++++----- src/layouts.py | 11 +++++++++-- src/templates/_core.py | 2 +- src/templates/normal.py | 2 +- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 288ce6fe..597625ad 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -253,7 +253,7 @@ def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) def frame_panorama( layer: ArtLayer | LayerSet, reference: ReferenceLayer, - panorama_element: int, + panorama_position: tuple[int, int], panorama_size: tuple[int, int], anchor: AnchorPosition = AnchorPosition.TopLeft, ) -> None: @@ -284,10 +284,10 @@ def frame_panorama( ref_dim = ( reference if isinstance(reference, dict) else get_layer_dimensions(reference) ) - pano_x = panorama_element % panorama_size[0] - pano_y = panorama_element // panorama_size[0] - layer.translate(-ref_dim["width"] * pano_x, 0) - layer.translate(0, -ref_dim["height"] * pano_y) + pano_x = -ref_dim["width"] * panorama_position[0] + pano_y = -ref_dim["height"] * panorama_position[1] + layer.translate(pano_x, 0) + layer.translate(0, pano_y) def frame_layer( diff --git a/src/layouts.py b/src/layouts.py index f466ebb0..8de1d54e 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -239,8 +239,15 @@ def is_panorama(self) -> bool: return self.file['kwargs'].get('pano_size', None) is not None @cached_property - def panorama_element(self) -> int: - return int(self.file['kwargs'].get('pano_idx', 0)) + def panorama_element(self) -> tuple[int, int]: + pano_pos_arg = self.file['kwargs'].get('pano_pos', None) + if pano_pos_arg is not None and pano_pos_arg.count('x') == 1: + return tuple(int(s) for s in pano_pos_arg.split('x')) # type: ignore + else: + pano_elem = int(self.file['kwargs'].get('pano_elem', 0)) + pano_x = pano_elem % self.panorama_size[0] + pano_y = pano_elem // self.panorama_size[0] + return (pano_x, pano_y) @cached_property def panorama_size(self) -> tuple[int, int]: diff --git a/src/templates/_core.py b/src/templates/_core.py index 28efe3c2..74ddcca5 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -802,7 +802,7 @@ def load_artwork( # Frame the artwork if self.layout.is_panorama and art_reference is not None: - psd.frame_panorama(art_layer, art_reference, self.layout.panorama_element) + psd.frame_panorama(art_layer, art_reference, self.layout.panorama_element, self.layout.panorama_size) elif art_reference: psd.frame_layer(layer=art_layer, ref=art_reference) diff --git a/src/templates/normal.py b/src/templates/normal.py index d2b30f7c..75899ff3 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1464,7 +1464,7 @@ def panorama_mode_enabled(self) -> bool: @cached_property def panorama_is_horizontal(self) -> bool: """Returns True if panorama goes both down and to the side.""" - return self.panorama_mode_enabled and self.layout.file['panorama_size'][1] > 1 + return self.panorama_mode_enabled and self.panorama_size[1] > 1 @cached_property def multicolor_textbox(self) -> bool: From 68a7752e38a4aba531fdf4d34d711c07be8e4eb8 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 09:29:00 +0200 Subject: [PATCH 127/190] Hide border on vertical panoramas --- src/layouts.py | 10 +++++++++- src/templates/_core.py | 13 ++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index 8de1d54e..b1d5a7b7 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -233,10 +233,18 @@ def is_transform(self) -> bool: @cached_property def is_mdfc(self) -> bool: return False + + """ + * Panorama Data + """ @cached_property def is_panorama(self) -> bool: - return self.file['kwargs'].get('pano_size', None) is not None + return self.panorama_size[0] > 1 or self.panorama_size[1] > 1 + + @cached_property + def is_vertical_panorama(self) -> bool: + return self.panorama_size[1] > 1 @cached_property def panorama_element(self) -> tuple[int, int]: diff --git a/src/templates/_core.py b/src/templates/_core.py index 74ddcca5..1e8f4a99 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -1206,11 +1206,14 @@ def border_color(self) -> str: @try_photoshop def color_border(self) -> None: """Color this card's border based on given setting.""" - if self.border_group and self.border_color != BorderColor.Black: - psd.apply_fx( - self.border_group, - [EffectColorOverlay(color=psd.get_color(self.border_color))], - ) + if self.border_group is not None: + if self.layout.is_vertical_panorama: + self.border_group.visible = False + elif self.border_color != BorderColor.Black: + psd.apply_fx( + self.border_group, + [EffectColorOverlay(color=psd.get_color(self.border_color))], + ) """ * Formatted Text Layers From b88ad8fb247345294bb4f802a4ae8d7b922ef743 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 12:05:55 +0200 Subject: [PATCH 128/190] Remove printout of render spec schema --- src/render_spec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/render_spec.py b/src/render_spec.py index 4ffb45a7..b9cb1142 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -85,7 +85,6 @@ class RenderSpec: def parse_render_spec_file(render_spec_path: Path) -> RenderSpecModel: - print(RootModel[RenderSpecModel].model_json_schema()) return parse_model(render_spec_path, RootModel[RenderSpecModel]).root From f66a1207e58b7a0f4fe3a7a5f8c88ebc2e3d3b89 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 13:38:05 +0200 Subject: [PATCH 129/190] Remove accidental double-cards when rendering a render spec --- src/utils/images.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/images.py b/src/utils/images.py index ffb05a42..dd16e3be 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -92,8 +92,6 @@ def add_card(card: CardDetails) -> None: for path in render_specs: try: cards = parse_render_spec(path).cards - for card in cards: - add_card(card) except ValidationError as e: raise _ValidationError(path) from e From 5d6fd25fa406fd6057186e7ea83274969567a293 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 13:39:05 +0200 Subject: [PATCH 130/190] Add API to get document/card dimensions as if they were layers --- src/helpers/bounds.py | 30 ++++++++++++++++++++++++++++++ src/templates/_core.py | 5 +---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index 24be1804..82f0d3a1 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -6,6 +6,7 @@ from typing import TypedDict from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from src import APP @@ -120,6 +121,35 @@ def get_layer_height(layer: ArtLayer | LayerSet) -> float | int: return int(bounds[3] - bounds[1]) +def get_document_dimensions(document: Document) -> LayerDimensions: + """Compute the width and height dimensions of a document. + + Args: + document: A document object + + Returns: + Dict containing height, width, and positioning locations. + """ + doc_width: float = int(document.width) + doc_height: float = int(document.height) + return get_dimensions_from_bounds((0, 0, doc_width, doc_height)) + + +def get_card_dimensions(document: Document) -> LayerDimensions: + """Compute the width and height dimensions of a document. + + Args: + document: A document object + + Returns: + Dict containing height, width, and positioning locations. + """ + bleed = int(document.resolution / 8) + doc_width: float = int(document.width) + doc_height: float = int(document.height) + return get_dimensions_from_bounds((bleed, bleed, doc_width - bleed, doc_height - bleed)) + + """ * Bounds and Dimensions, No Effects """ diff --git a/src/templates/_core.py b/src/templates/_core.py index 1e8f4a99..87105f03 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -873,10 +873,7 @@ def paste_scryfall_scan( layer.rotate(90) # Frame the layer and position it above the art layer - bleed = int(self.docref.resolution / 8) - dims = psd.get_dimensions_from_bounds( - (bleed, bleed, self.docref.width - bleed, self.docref.height - bleed) - ) + dims = psd.get_card_dimensions(self.docref) psd.frame_layer(layer, dims) if self.art_layer: layer.move(self.art_layer, ElementPlacement.PlaceBefore) From 6bea456a94a1bd9b62e4887c540881abdc98dc59 Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 13:39:48 +0200 Subject: [PATCH 131/190] Frame panorama within card area as opposed to a reference layer --- src/helpers/position.py | 22 +++++++++------------- src/templates/_core.py | 4 ++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 597625ad..acc59de9 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -20,6 +20,7 @@ get_layer_dimensions, get_layer_height, get_layer_width, + get_card_dimensions, ) from src.helpers.selection import ( check_selection_bounds, @@ -252,7 +253,7 @@ def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) def frame_panorama( layer: ArtLayer | LayerSet, - reference: ReferenceLayer, + document: Document, panorama_position: tuple[int, int], panorama_size: tuple[int, int], anchor: AnchorPosition = AnchorPosition.TopLeft, @@ -265,29 +266,24 @@ def frame_panorama( """ # Get layer and full reference dimensions art_dim: LayerDimensions = get_layer_dimensions(layer) - ref_dim = get_layer_dimensions(reference) - ref_dim["width"] = ref_dim["width"] * panorama_size[0] - ref_dim["height"] = ref_dim["height"] * panorama_size[1] + ref_dim = get_card_dimensions(document) + panorama_dim = (ref_dim["width"] * panorama_size[0], + ref_dim["height"] * panorama_size[1]) # Scale the layer to fit either the largest dimension scale = 100 * max( - (ref_dim["width"] / art_dim["width"]), (ref_dim["height"] / art_dim["height"]) + (panorama_dim[0] / art_dim["width"]), (panorama_dim[1] / art_dim["height"]) ) layer.resize(scale, scale, anchor) - # Align the original layer on the left - alignments = ("left", "center_y") + # Align the original layer on the top-left + alignments = ("left", "top") align(alignments, layer, ref_dim) - layer.translate(0, ref_dim["height"] / panorama_size[1]) # Move the layer according to the given index - ref_dim = ( - reference if isinstance(reference, dict) else get_layer_dimensions(reference) - ) pano_x = -ref_dim["width"] * panorama_position[0] pano_y = -ref_dim["height"] * panorama_position[1] - layer.translate(pano_x, 0) - layer.translate(0, pano_y) + layer.translate(pano_x, pano_y) def frame_layer( diff --git a/src/templates/_core.py b/src/templates/_core.py index 87105f03..40ce41a0 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -801,8 +801,8 @@ def load_artwork( ) # Frame the artwork - if self.layout.is_panorama and art_reference is not None: - psd.frame_panorama(art_layer, art_reference, self.layout.panorama_element, self.layout.panorama_size) + if self.layout.is_panorama: + psd.frame_panorama(art_layer, self.docref, self.layout.panorama_element, self.layout.panorama_size) elif art_reference: psd.frame_layer(layer=art_layer, ref=art_reference) From 775eb56f9fa9e7812d12f9332667e8c0934be3ad Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 15 Apr 2026 13:39:55 +0200 Subject: [PATCH 132/190] Remove some accidentally merged code --- src/templates/normal.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/templates/normal.py b/src/templates/normal.py index 75899ff3..2b3007cf 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1453,19 +1453,6 @@ def crown_texture_enabled(self) -> bool: ) ) - @cached_property - def panorama_mode_enabled(self) -> bool: - """Returns True if panorama mode is enabled.""" - return bool(cfg.get_setting( - section="FRAME", - key="Panorama.Mode", - default=False)) - - @cached_property - def panorama_is_horizontal(self) -> bool: - """Returns True if panorama goes both down and to the side.""" - return self.panorama_mode_enabled and self.panorama_size[1] > 1 - @cached_property def multicolor_textbox(self) -> bool: """Returns True if Textbox for multicolored cards should use blended colors.""" @@ -1560,14 +1547,8 @@ def frame_type(self) -> str: @cached_property def art_frame(self) -> str: - if self.panorama_mode_enabled: - if self.panorama_is_horizontal: - return f"{LAYERS.FULL_CARD_FRAME}" - else: - return f"{LAYERS.FULL_ART_FRAME}" - else: - # Use different positioning based on textbox size - return f"{LAYERS.ART_FRAME} {self.size}" + # Use different positioning based on textbox size + return f"{LAYERS.ART_FRAME} {self.size}" """ * Bool From b1f3faa84b9c29dfd4a0e1065c9de7b271ff1395 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 15 Apr 2026 15:20:09 +0300 Subject: [PATCH 133/190] fix(scryfall.py): Limit Scryfall requests to 2 per second as that is the new requirement --- src/utils/scryfall.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index 0b936e3e..ab1fc123 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -359,10 +359,8 @@ class ScryfallError(BaseModel): # Rate limiter to safely limit Scryfall requests _rate_limit_storage = MemoryStorage() _scryfall_rate_limit = MovingWindowRateLimiter(_rate_limit_storage) -# Using a limit of 10 per second seems to trigger Scryfall's rate limiting, -# even though that's what Scryfall recommends. We don't need to make that many -# simultaneous requests, so a lower limit is used. -_rate_limit = RateLimitItemPerSecond(5) +# https://scryfall.com/docs/api/rate-limits +_rate_limit = RateLimitItemPerSecond(2) # Scryfall HTTP header scryfall_http_header = HEADERS.Default.copy() From 64fa88afc346ef35a7b569586559a57e103717de Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 29 Apr 2026 09:42:58 +0200 Subject: [PATCH 134/190] Add instructions for panoramas via an example to the README --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 00ec4568..275011a7 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,37 @@ In the above example, if you have a file `UB/Dungeon Descent.png` it will be ren ``` +#### Scene/Panorama Renders +One example use-case of the _Render Specifications_ is the rendering of scenes, internally referred to as panoramas. An example of a scene are the six cards with Collector Number from _301_ to _306_ in the set _TLA_. To render a panorama of these cards from a single seamless artwork is currently not possible without _Render Specifications_. The following would render this scene: +```yaml +configs: + AVATAR: + - "[art=Avatar-Scene.png]" + - "[pano_size=3x2]" + - "[pano_elem=${GROUP_INDEX}]" + +groups: + files: + - "Combustion Technique" + - "Zuko, Conflicted" + - "Azula, Cunning Usurper" + - "Aang, at the Crossroads" + - "Katara, the Fearless" + - "Dai Li Agents" + settings: "AVATAR" +``` + +Note the reusable configuration _AVATAR_, which defines the size of the panorama as well as each card's position in that panorama. `${GROUP_INDEX}` will simply be replaced by the position in the list of files for each card. Lastly the art is replaced with the seamless art in the file `Avatar-Scene.png`. Once this _Render Specification_ is fully evaluated it will contain the following renders. +```txt +Combustion Technique [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=0] +Zuko, Conflicted [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=1] +Azula, Cunning Usurper [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=2] +Aang, at the Crossroads [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=3] +Katara, the Fearless [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=4] +Dai Li Agents [art=Avatar-Scene.png] [pano_size=3x2] [pano_elem=5] +``` +Proxyshop will use these to automatically position the artwork within the frame such that the resulting cards will seamlessly fit next to each other once bleed edge is trimmed. + ### Updater The updater window allows downloading and updating template files. The files can originate from Proxyshop or installed plugins. From df214bc466e170f10ffde752b0f6f616ad28bafe Mon Sep 17 00:00:00 2001 From: Mal Date: Wed, 29 Apr 2026 09:50:34 +0200 Subject: [PATCH 135/190] Suggested refactors --- src/layouts.py | 55 +++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index b1d5a7b7..f34b3868 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -237,37 +237,36 @@ def is_mdfc(self) -> bool: """ * Panorama Data """ - + @cached_property def is_panorama(self) -> bool: return self.panorama_size[0] > 1 or self.panorama_size[1] > 1 - + @cached_property def is_vertical_panorama(self) -> bool: return self.panorama_size[1] > 1 @cached_property def panorama_element(self) -> tuple[int, int]: - pano_pos_arg = self.file['kwargs'].get('pano_pos', None) - if pano_pos_arg is not None and pano_pos_arg.count('x') == 1: - return tuple(int(s) for s in pano_pos_arg.split('x')) # type: ignore + pano_pos_arg = self.file["kwargs"].get("pano_pos", None) + if ( + pano_pos_arg is not None + and len(parts := pano_pos_arg.split("x", maxsplit=1)) == 2 + ): + return (int(parts[0]), int(parts[1])) else: - pano_elem = int(self.file['kwargs'].get('pano_elem', 0)) + pano_elem = int(self.file["kwargs"].get("pano_elem", 0)) pano_x = pano_elem % self.panorama_size[0] pano_y = pano_elem // self.panorama_size[0] return (pano_x, pano_y) @cached_property def panorama_size(self) -> tuple[int, int]: - panorama_size = self.file['kwargs'].get('pano_size', "1x1") - panorama_size = [int(c) for c in panorama_size.split("x")] - match len(panorama_size): - case 0: - return (1, 1) - case 1: - return (panorama_size[0], 1) - case _: - return (panorama_size[0], panorama_size[1]) + panorama_size = self.file["kwargs"].get("pano_size", "1x1") + panorama_size = [int(c) for c in panorama_size.split("x", maxsplit=1)] + if len(panorama_size) < 2: + panorama_size = panorama_size + [1] * (2 - len(panorama_size)) + return (panorama_size[0], panorama_size[1]) """ * Core Data @@ -291,7 +290,7 @@ def template_file(self) -> Path: @cached_property def art_file(self) -> Path: """Path: Art image file path.""" - art_file = self.file['kwargs'].get('art', None) + art_file = self.file["kwargs"].get("art", None) if art_file is not None: art_file = Path(art_file) if art_file.is_absolute(): @@ -351,13 +350,11 @@ def card(self) -> ScryfallCard | ScryfallCardFace: if not matching_face: face_listing = "\n".join(f" - {face.name}" for face in faces) - _logger.warning( - f"None of the card faces
{ + _logger.warning(f"None of the card faces
{ face_listing }
matches the input file's name { self.input_name - }.
Defaulting to first face." - ) + }.
Defaulting to first face.") return faces[0] return matching_face @@ -553,7 +550,7 @@ def color_indicator(self) -> str: @cached_property def symbol_code(self) -> str: """Code used to match a symbol to this card's set. Provided by hexproof.io.""" - forced_symbol = self.file['kwargs'].get('sym', None) + forced_symbol = self.file["kwargs"].get("sym", None) if forced_symbol: return forced_symbol.upper() if self.config.symbol_force_default: @@ -1788,9 +1785,11 @@ def oracle_texts(self) -> list[str]: """Both side oracle texts.""" text: list[str] = [] for t in [ - c.printed_text or c.oracle_text or "" - if self.is_alt_lang - else c.oracle_text or "" + ( + c.printed_text or c.oracle_text or "" + if self.is_alt_lang + else c.oracle_text or "" + ) for c in self.cards ]: if "Fuse" in self.keywords: @@ -1816,9 +1815,11 @@ def flavor_texts(self) -> list[str]: def shared_reminder(self) -> str: prev_match: str = "" for oracle_text in [ - c.printed_text or c.oracle_text or "" - if self.is_alt_lang - else c.oracle_text or "" + ( + c.printed_text or c.oracle_text or "" + if self.is_alt_lang + else c.oracle_text or "" + ) for c in self.cards ]: match = CardTextPatterns.TEXT_REMINDER_ENDING.match(oracle_text) From 45f123c0e920cf0f6de9dcc2d3dc0580c51a6ae1 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 4 May 2026 10:03:02 +0300 Subject: [PATCH 136/190] feat: Use Scryfall's `/cards/collection` endpoint to batch fetch (English) cards --- main.py | 3 + src/_loader.py | 13 ++ src/cards.py | 136 ++++++++--- src/enums/mtg.py | 71 ++++-- src/gui/qml/models/batch_rendering_model.py | 58 +++-- src/gui/qml/models/template_list_model.py | 29 ++- src/gui/qml/models/test_renders_model.py | 186 +++++++++------ src/utils/data_structures.py | 7 + src/utils/images.py | 77 ------- src/utils/inputs.py | 150 +++++++++++++ src/utils/scryfall.py | 236 ++++++++++++++------ 11 files changed, 671 insertions(+), 295 deletions(-) create mode 100644 src/utils/inputs.py diff --git a/main.py b/main.py index 2643a2e3..0b4a757f 100644 --- a/main.py +++ b/main.py @@ -77,6 +77,7 @@ def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]) template_library, file_dialog_model, render_message_dialog_content_model, + CFG, ) template_list_model = TemplateListModel( render_queue, @@ -84,6 +85,7 @@ def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]) render_message_dialog_content_model, template_library, test_renders_model, + CFG, ) batch_render_model = BatchRenderingModel( file_dialog_model, @@ -92,6 +94,7 @@ def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]) plugins, template_library, test_renders_model, + CFG, ) settings_tree_model = SettingsTreeModel( app_config=CFG, template_library=template_library diff --git a/src/_loader.py b/src/_loader.py index ce2f8e05..f7e7d57c 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -1559,6 +1559,19 @@ def save_template_versions(self) -> None: if self.initial_versions != self.versions: dump_model(PATH.SRC_DATA_VERSIONS, self.versions) + def get_templates_for_layout_category( + self, layout_category: LayoutCategory + ) -> list[AssembledTemplate]: + templates: list[AssembledTemplate] = [] + for template in self.built_in_templates_by_name.values(): + if template.is_installed(layout_category): + templates.append(template) + for plugin_templates in self.plugin_templates_by_name.values(): + for template in plugin_templates.values(): + if template.is_installed(layout_category): + templates.append(template) + return templates + def _group_templates_by_plugin( self, templates: list[AppTemplate] ) -> tuple[list[AppTemplate], dict[AppPlugin, list[AppTemplate]]]: diff --git a/src/cards.py b/src/cards.py index 61cf6e8e..2437e0f3 100644 --- a/src/cards.py +++ b/src/cards.py @@ -3,6 +3,8 @@ * Handles raw card data fetching and processing """ +from collections.abc import Iterable +from json import dumps from logging import Logger, getLogger from pathlib import Path from typing import TypedDict @@ -11,9 +13,16 @@ from pathvalidate import sanitize_filename from src._config import AppConfig -from src.enums.mtg import CardTextPatterns, TransformIcons, non_italics_abilities +from src.enums.mtg import ( + CardTextPatterns, + LayoutScryfall, + TransformIcons, + non_italics_abilities, +) from src.schema.colors import ColorObject +from src.utils.data_structures import find_item from src.utils.scryfall import ( + CardIdentifier, ScryfallCard, ScryfallCardFace, ScryfallException, @@ -21,13 +30,12 @@ get_card_search, get_card_unique, get_card_via_url, + get_cards_collection, ) _logger = getLogger(__name__) -""" -* Types -""" +# region Types # (Start index, end index) CardItalicString = tuple[int, int] @@ -59,6 +67,10 @@ class FrameDetails(TypedDict): is_hybrid: bool +# endregion Types + +# region Constants + _filename_character_replacements: dict[str, str] = { "<": "<", ">": ">", @@ -82,13 +94,13 @@ class FrameDetails(TypedDict): value: key for key, value in _filename_character_replacements.items() } -""" -* Handling Data Requests -""" +# endregion Constants + +# region Requests def get_card_data( - card: CardDetails, + card: CardDetails | CardIdentifier, cfg: AppConfig, ) -> ScryfallCard | None: """Fetch card data from the Scryfall API. @@ -96,7 +108,6 @@ def get_card_data( Args: card: Card details pulled from the art image filename. cfg: AppConfig object providing search configuration settings. - logger: Console or other logger object used to relay warning messages. Returns: Scryfall 'Card' object data if card was returned, otherwise None. @@ -151,9 +162,46 @@ def get_card_data( _logger.exception("Couldn't retrieve card from Scryfall.") -""" -* Pre-processing Data -""" +def get_batch_of_cards( + batch: Iterable[tuple[CardIdentifier, list[CardDetails]]], +) -> list[tuple[CardDetails, ScryfallCard]]: + if result := get_cards_collection([item[0] for item in batch]): + if result.not_found: + _logger.warning( + f"The following cards were not found from Scryfall:
{ + '
'.join( + [ + f'{dumps(ide)} derived from { + human_readable_card_details(card[1][0]) + if ( + card := find_item( + batch, lambda item: item[0] == ide + ) + ) + else None + }' + for ide in result.not_found + ] + ) + }" + ) + + out: list[tuple[CardDetails, ScryfallCard]] = [] + idx: int = 0 + for identifier, cards in batch: + if identifier in result.not_found: + continue + for card in cards: + out.append((card, result.data[idx])) + idx += 1 + + return out + return [] + + +# endregion Requests + +# region Card data processing def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDetails: @@ -192,29 +240,26 @@ def parse_card_info(file_path: Path, name_override: str | None = None) -> CardDe } -""" -* Post-processing Data -""" - - def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: """Process any additional required data before sending it to the layout object. + This function is expected to be idempotent. Args: - data: Unprocessed scryfall data. + data: Scryfall data. card: Card details processed from art image file name. Returns: - Processed scryfall data. + Processed Scryfall data. """ # Define a normalized name name_normalized = normalize_str(card["name"], no_space=True) # Modify meld card data to fit transform layout if data.layout == "meld": - # Ignore tokens and other objects front: list[ScryfallRelatedCard] = [] back: ScryfallRelatedCard | None = None + + # Ignore tokens and other objects for part in data.all_parts if data.all_parts else []: if part.component == "meld_part": front.append(part) @@ -236,12 +281,12 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: try: if is_back and back: data = get_card_via_url(str(back.uri)) - data.layout = "normal" + data.layout = LayoutScryfall.Normal else: # Pull JSON data for each face and set object to card_face data.card_faces = [] for face in faces: - if face: + if face and not isinstance(face, ScryfallCardFace): face_data = get_card_via_url(str(face.uri)) face_data_dict = face_data.model_dump() face_data_dict["object"] = "card_face" @@ -252,7 +297,7 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: [bool(n in TransformIcons) for n in data.frame_effects] ): data.frame_effects = ["meld"] - data.layout = "transform" + data.layout = LayoutScryfall.Transform except ScryfallException: _logger.exception( "Couldn't retrieve additional card details for a meld card." @@ -277,39 +322,39 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: if card_face.type_line: if "Planeswalker" in card_face.type_line: data.layout = ( - "planeswalker_tf" - if data.layout == "transform" - else "planeswalker_mdfc" + LayoutScryfall.PlaneswalkerTransform + if data.layout == LayoutScryfall.Transform + else LayoutScryfall.PlaneswalkerMDFC ) # Transform Saga layout elif "Saga" in card_face.type_line: - data.layout = "saga" + data.layout = LayoutScryfall.Saga # Battle layout elif "Battle" in card_face.type_line: - data.layout = "battle" + data.layout = LayoutScryfall.Battle return data # Add Mutate layout if "Mutate" in data.keywords: - data.layout = "mutate" + data.layout = LayoutScryfall.Mutate return data type_line = data.type_line # Add Planeswalker layout if "Planeswalker" in type_line: - data.layout = "planeswalker" + data.layout = LayoutScryfall.Planeswalker return data # Check for Saga Creature layout if "Saga" in type_line and "Creature" in type_line: - data.layout = "saga" + data.layout = LayoutScryfall.Saga return data # Check for Station layout if data.keywords and "Station" in data.keywords: - data.layout = "station" + data.layout = LayoutScryfall.Station return data # Return updated data @@ -322,9 +367,21 @@ def sanitize_card_filename(name: str) -> str: return sanitize_filename(name) -""" -* Card Text Utilities -""" +def card_details_to_scryfall_identifier(card: CardDetails) -> CardIdentifier: + if card["set"] and card["number"]: + return { + "set": card["set"], + "collector_number": card["number"], + } + elif card["set"]: + return {"name": card["name"], "set": card["set"]} + else: + return {"name": card["name"]} + + +# endregion Card data processing + +# region Card text processing def locate_symbols( @@ -480,3 +537,12 @@ def strip_reminder_text(text: str) -> str: # Remove any extra whitespace return CardTextPatterns.EXTRA_SPACE.sub("", text_stripped).strip() + + +def human_readable_card_details(details: CardDetails) -> str: + return f"{details['name']} ({details['artist']}) [{details['set']}] {{{ + details['number'] + }}} |{details['file']}|" + + +# endregion Card text processing diff --git a/src/enums/mtg.py b/src/enums/mtg.py index e71e6c08..606d85e5 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -71,31 +71,31 @@ class LayoutType(StrEnum): class LayoutScryfall(StrEnum): """Card layout type, according to Scryfall data.""" - Normal = "normal" - Split = "split" + Adventure = "adventure" + ArtSeries = "art_series" + Augment = "augment" + Battle = "battle" + Case = "case" + Class = "class" + DoubleFacedToken = "double_faced_token" + Emblem = "emblem" Flip = "flip" - Transform = "transform" + Host = "host" + Leveler = "leveler" MDFC = "modal_dfc" Meld = "meld" - Leveler = "leveler" - Case = "case" - Class = "class" - Saga = "saga" - Adventure = "adventure" - Prepare = "prepare" Mutate = "mutate" - Prototype = "prototype" - Battle = "battle" + Normal = "normal" Planar = "planar" + Prepare = "prepare" + Prototype = "prototype" + ReversibleCard = "reversible_card" + Saga = "saga" Scheme = "scheme" - Vanguard = "vanguard" + Split = "split" Token = "token" - DoubleFacedToken = "double_faced_token" - Emblem = "emblem" - Augment = "augment" - Host = "host" - ArtSeries = "art_series" - ReversibleCard = "reversible_card" + Transform = "transform" + Vanguard = "vanguard" # Definitions added to Scryfall built-ins Planeswalker = "planeswalker" @@ -179,6 +179,41 @@ class LayoutScryfall(StrEnum): } +scryfall_layout_category_map: dict[LayoutScryfall, LayoutCategory] = { + LayoutScryfall.Adventure: LayoutCategory.Adventure, + LayoutScryfall.ArtSeries: LayoutCategory.Normal, + LayoutScryfall.Augment: LayoutCategory.Normal, + LayoutScryfall.Battle: LayoutCategory.Battle, + LayoutScryfall.Case: LayoutCategory.Case, + LayoutScryfall.Class: LayoutCategory.Class, + LayoutScryfall.DoubleFacedToken: LayoutCategory.Transform, + LayoutScryfall.Emblem: LayoutCategory.Normal, + LayoutScryfall.Flip: LayoutCategory.Transform, + LayoutScryfall.Host: LayoutCategory.Normal, + LayoutScryfall.Leveler: LayoutCategory.Leveler, + LayoutScryfall.MDFC: LayoutCategory.MDFC, + # Meld might be Normal or Transform, so this case should not be retrieved from the map + # LayoutScryfall.Meld: LayoutCategory.Transform, + LayoutScryfall.Mutate: LayoutCategory.Mutate, + LayoutScryfall.Normal: LayoutCategory.Normal, + LayoutScryfall.Planar: LayoutCategory.Planar, + LayoutScryfall.Prepare: LayoutCategory.Prepare, + LayoutScryfall.Prototype: LayoutCategory.Prototype, + LayoutScryfall.ReversibleCard: LayoutCategory.Transform, + LayoutScryfall.Saga: LayoutCategory.Saga, + LayoutScryfall.Scheme: LayoutCategory.Normal, + LayoutScryfall.Split: LayoutCategory.Split, + LayoutScryfall.Token: LayoutCategory.Normal, + LayoutScryfall.Transform: LayoutCategory.Transform, + LayoutScryfall.Vanguard: LayoutCategory.Normal, + # Definitions added to Scryfall built-ins + LayoutScryfall.Planeswalker: LayoutCategory.Planeswalker, + LayoutScryfall.PlaneswalkerMDFC: LayoutCategory.PlaneswalkerMDFC, + LayoutScryfall.PlaneswalkerTransform: LayoutCategory.PlaneswalkerTransform, + LayoutScryfall.Station: LayoutCategory.Station, +} + + """ * Card Types """ diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index 068a326b..cbee7f57 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -1,4 +1,5 @@ -from asyncio import Task, create_task, ensure_future, gather, to_thread +from asyncio import ensure_future, gather, to_thread +from collections.abc import Iterable, Sequence from functools import cached_property from logging import getLogger from pathlib import Path @@ -8,11 +9,11 @@ from pydantic import BaseModel, RootModel, ValidationError from PySide6.QtCore import QModelIndex, QObject, QPersistentModelIndex, QUrl, Slot +from src._config import AppConfig from src._loader import ( AppPlugin, AssembledTemplate, AssembledTemplateInstalledArgs, - RenderableTemplate, TemplateLibrary, ) from src.cards import CardDetails @@ -23,7 +24,7 @@ from src.gui.qml.models.test_renders_model import TestRendersModel from src.render.render_queue import RenderQueue, cancel_with_render from src.render.setup import prepare_render_operations -from src.utils.images import match_images_with_data_files +from src.utils.inputs import get_cards_from_inputs from src.utils.scryfall import ScryfallCard _logger = getLogger(__name__) @@ -64,6 +65,7 @@ def __init__( plugins: dict[str, AppPlugin], template_library: TemplateLibrary, test_renders_model: TestRendersModel, + app_config: AppConfig, parent: QObject | None = None, selected_index: int = -1, ) -> None: @@ -72,6 +74,7 @@ def __init__( self._render_queue = render_queue self._test_renders_model = test_renders_model self._template_library = template_library + self._app_config = app_config self.built_in_templates_by_layout: dict[ LayoutCategory, dict[str, AssembledTemplate] @@ -154,8 +157,8 @@ def columnCount(self, parent: QModelIndex | QPersistentModelIndex) -> int: return 2 @property - def template_choices(self) -> dict[LayoutCategory, RenderableTemplate]: - template_choices: dict[LayoutCategory, RenderableTemplate] = {} + def template_choices(self) -> dict[LayoutCategory, AssembledTemplate]: + template_choices: dict[LayoutCategory, AssembledTemplate] = {} for item in self.items: if item.selected < 0: continue @@ -191,9 +194,8 @@ async def render_files(self, paths: list[Path]) -> None: _logger.info( f"Queueing { len(paths) - } batch mode entries for render. Do note that the actual amount of renders might be lower if you selected JSON files or art for split cards." + } batch mode inputs for render. Do note that the actual amount of renders might be different if you selected JSON or YAML files or art for split cards." ) - matched_inputs = match_images_with_data_files(paths) def add_render( input: CardDetails | tuple[CardDetails, ScryfallCard], @@ -207,7 +209,10 @@ def add_render( ): self._render_queue.enqueue(render_operations[0]) - await gather(*[to_thread(add_render, input) for input in matched_inputs]) + async def add_renders(cards: Sequence[tuple[CardDetails, ScryfallCard]]): + await gather(*[to_thread(add_render, card) for card in cards]) + + await get_cards_from_inputs(paths, add_renders, self._app_config) @Slot() def render_selections(self) -> None: @@ -242,26 +247,39 @@ def test_render(self, layout: str | None = None, quick: bool = False) -> None: async def action() -> None: layout_category = LayoutCategory(layout) if layout else None - preparation_routines: list[Task[None]] = [] - for category, template in self.template_choices.items(): - if layout_category and category != layout_category: - continue - - preparation_routines.append( - create_task( - self._test_renders_model.test_render(template, category, quick) - ) + if layout_category and not ( + (template := self.template_choices.get(layout_category)) + and template.is_installed(layout_category) + ): + _logger.info( + f"A template hasn't been chosen/installed for {layout_category} so there are no tests to queue." ) + return - if layout_category: - break + if layout_category: + conf: dict[LayoutCategory, Iterable[AssembledTemplate] | None] = { + layout_category: (self.template_choices[layout_category],) + } + else: + conf = { + cat: (template,) + for cat, template in self.template_choices.items() + if template.is_installed(cat) + } + + if not conf: + _logger.info( + "No installed templates have been chosen so there are no tests to queue." + ) + return _logger.info( f"Queueing { layout_category.value + ' ' if layout_category else '' }tests for batch mode selections." ) - await gather(*preparation_routines) + + await self._test_renders_model.test_renders(conf, quick) cancel_with_render(ensure_future(action()), self._render_queue) diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 5ab8f37e..3cab1fb0 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -1,4 +1,5 @@ from asyncio import ensure_future, gather, to_thread +from collections.abc import Sequence from functools import cached_property from logging import getLogger from pathlib import Path @@ -7,6 +8,7 @@ from pydantic import BaseModel from PySide6.QtCore import QObject, QUrl, Slot +from src._config import AppConfig from src._loader import ( AssembledTemplate, AssembledTemplateConfigChangedArgs, @@ -23,7 +25,7 @@ from src.render.render_queue import RenderQueue, cancel_with_render from src.render.setup import prepare_render_operations from src.utils.data_structures import first -from src.utils.images import match_images_with_data_files +from src.utils.inputs import get_cards_from_inputs from src.utils.scryfall import ScryfallCard _logger = getLogger(__name__) @@ -58,6 +60,7 @@ def __init__( message_dialog_model: MessageDialogContentModel, template_library: TemplateLibrary, test_renders_model: TestRendersModel, + app_config: AppConfig, parent: QObject | None = None, selected_index: int = 0, ) -> None: @@ -67,7 +70,8 @@ def __init__( self._template_library = template_library self._test_renders_model = test_renders_model self._template_library = template_library - + self._app_config = app_config + self.built_in_templates = template_library.built_in_templates_by_name self.plugin_templates = template_library.plugin_templates_by_name @@ -140,7 +144,7 @@ async def render_files(self, paths: list[Path]) -> None: _logger.info( f"Queueing { len(paths) - } entries for render. Do note that the actual amount of renders might be lower if you selected JSON files or art for split cards." + } inputs for render. Do note that the actual amount of renders might be different if you selected JSON or YAML files or art for split cards." ) selected_template_entry = self.items[self._selected_index] @@ -151,8 +155,6 @@ async def render_files(self, paths: list[Path]) -> None: else: template = self.built_in_templates[selected_template_entry.name] - matched_inputs = match_images_with_data_files(paths) - def add_render( input: CardDetails | tuple[CardDetails, ScryfallCard], ) -> None: @@ -165,7 +167,10 @@ def add_render( ): self._render_queue.enqueue(render_operations[0]) - await gather(*[to_thread(add_render, input) for input in matched_inputs]) + async def add_renders(cards: Sequence[tuple[CardDetails, ScryfallCard]]): + await gather(*[to_thread(add_render, card) for card in cards]) + + await get_cards_from_inputs(paths, add_renders, self._app_config) @Slot() def render_selections(self) -> None: @@ -208,16 +213,18 @@ async def action() -> None: else: template = self.built_in_templates[selected_template_entry.name] - preparation_routines = self._test_renders_model.prepare_test_renders( - (template,), layout_category, quick - ) - _logger.info( f"Queueing { layout_category.value + ' ' if layout_category else '' }tests for template {template.name}." ) - await gather(*preparation_routines) + + await self._test_renders_model.test_renders( + {layout_category: (template,)} + if layout_category + else {category: (template,) for category in template.layout_categories}, + quick, + ) cancel_with_render(ensure_future(action()), self._render_queue) diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index 28270a07..fd7a9ee0 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -1,18 +1,26 @@ -from asyncio import Task, create_task, ensure_future, gather, to_thread -from collections.abc import Iterable +from asyncio import ensure_future, gather, to_thread +from collections.abc import Awaitable, Callable, Iterable, Sequence from functools import cached_property from logging import getLogger from PySide6.QtCore import Property, QObject, Signal, Slot -from src._loader import AssembledTemplate, RenderableTemplate, TemplateLibrary +from src._config import AppConfig +from src._loader import AssembledTemplate, TemplateLibrary from src._state import PATH -from src.cards import parse_card_info -from src.enums.mtg import LayoutCategory, LayoutType, layout_map_category +from src.cards import CardDetails, parse_card_info, process_card_data +from src.enums.mtg import ( + LayoutCategory, + LayoutType, + layout_map_category, + scryfall_layout_category_map, +) from src.gui.qml.models.file_dialog_model import FileDialogModel from src.gui.qml.models.message_dialog_content_model import MessageDialogContentModel from src.render.render_queue import RenderQueue, cancel_with_render from src.render.setup import prepare_render_operations +from src.utils.inputs import get_cards_from_details +from src.utils.scryfall import ScryfallCard from src.utils.tests import get_template_render_test_cases, prepare_test_render _logger = getLogger(__name__) @@ -25,6 +33,7 @@ def __init__( template_library: TemplateLibrary, file_dialog_model: FileDialogModel, message_dialog_model: MessageDialogContentModel, + app_config: AppConfig, /, parent: QObject | None = None, *, @@ -35,6 +44,7 @@ def __init__( self._template_library = template_library self._file_dialog_model = file_dialog_model self._message_dialog_model = message_dialog_model + self._app_config = app_config self._layout_categories = list(LayoutCategory) @cached_property @@ -47,79 +57,123 @@ def template_render_test_cases(self) -> dict[LayoutType, dict[str, str]]: def layout_categories(self) -> list[LayoutCategory]: return self._layout_categories - async def test_render( - self, template: RenderableTemplate, layout_category: LayoutCategory, quick: bool - ) -> None: - layout_types = layout_map_category[layout_category] - for layout_type in layout_types: - if test_cases := self.template_render_test_cases.get(layout_type, None): - for idx, test_case in enumerate(test_cases): - if quick and idx > 0: - break - - if render_operations := await to_thread( - prepare_render_operations, - template, - self._template_library, - (parse_card_info(PATH.SRC_IMG_TEST, name_override=test_case),), - file_dialog=self._file_dialog_model, - message_dialog=self._message_dialog_model, - ): - for op in render_operations: - op.before_render_callback = prepare_test_render - - self._render_queue.enqueue(*render_operations) - - def prepare_test_renders( + async def _run_action_per_layout_test_case( self, - templates: Iterable[AssembledTemplate], - layout_category: LayoutCategory | None = None, - quick: bool = False, - ) -> list[Task[None]]: - preparation_routines: list[Task[None]] = [] + callback: Callable[ + [Sequence[tuple[CardDetails, ScryfallCard]]], Awaitable[None] + ], + layout_categories: Iterable[LayoutCategory], + quick: bool, + ) -> None: + cards: list[CardDetails] = [] + for layout_category in layout_categories: + layout_types = layout_map_category[layout_category] + for layout_type in layout_types: + if test_cases := self.template_render_test_cases.get(layout_type, None): + for idx, test_case in enumerate(test_cases): + if quick and idx > 0: + break + cards.append( + parse_card_info(PATH.SRC_IMG_TEST, name_override=test_case) + ) + await get_cards_from_details(cards, callback, self._app_config) + + def _collect_categories( + self, templates: Iterable[AssembledTemplate] + ) -> set[LayoutCategory]: + layout_categories: set[LayoutCategory] = set() for template in templates: for category in template.layout_categories: - if not template.is_installed(category) or ( - layout_category and not category == layout_category - ): - continue - - preparation_routines.append( - create_task(self.test_render(template, category, quick)) - ) - - if layout_category: - break + layout_categories.add(category) + return layout_categories - return preparation_routines - - async def test_all_renders( - self, layout_category: LayoutCategory | None = None, quick: bool = False + async def test_renders( + self, + templates: dict[LayoutCategory, Iterable[AssembledTemplate] | None] + | None = None, + quick: bool = False, ) -> None: - preparation_routines: list[Task[None]] = self.prepare_test_renders( - self._template_library.built_in_templates_by_name.values(), - layout_category, - quick, - ) - for ( - plugin_templates - ) in self._template_library.plugin_templates_by_name.values(): - preparation_routines += self.prepare_test_renders( - plugin_templates.values(), layout_category, quick + """Queues test renders. + + Args: + templates: The layout categories and templates to queue tests for. Falsy value means that all tests should be queued. A falsy list of templates means that all applicable templates should be tested. + quick: Queue only the first test for each layout category and template combination.""" + if templates: + layout_categories_to_test: Iterable[LayoutCategory] = templates.keys() + for layout_category, test_templates in templates.items(): + if not test_templates: + templates[layout_category] = ( + self._template_library.get_templates_for_layout_category( + layout_category + ) + ) + else: + templates_to_test: list[AssembledTemplate] = list( + self._template_library.built_in_templates_by_name.values() ) - - _logger.info( - f"Queueing all { - layout_category.value + ' ' if layout_category else '' - }tests for render." + for ( + plugin_templates + ) in self._template_library.plugin_templates_by_name.values(): + templates_to_test.extend(plugin_templates.values()) + + layout_categories_to_test = self._collect_categories(templates_to_test) + + # Test all templates when a more specific configuration isn't provided + templates = { + layout_category: [ + template + for template in templates_to_test + if template.is_installed(layout_category) + ] + for layout_category in layout_categories_to_test + } + + def process_test_render( + card: tuple[CardDetails, ScryfallCard], template: AssembledTemplate + ) -> None: + render_operations = prepare_render_operations( + template, + self._template_library, + (card,), + self._file_dialog_model, + self._message_dialog_model, + ) + for op in render_operations: + op.before_render_callback = prepare_test_render + self._render_queue.enqueue(op) + + async def queue_test_render( + cards: Sequence[tuple[CardDetails, ScryfallCard]], + ) -> None: + operations: list[Awaitable[None]] = [] + for card in cards: + processed_scryfall_data = process_card_data(card[1], card[0]) + layout_category = scryfall_layout_category_map[ + processed_scryfall_data.layout + ] + if test_templates := templates.get(layout_category): + for template in test_templates: + operations.append( + to_thread(process_test_render, card, template) + ) + await gather(*operations) + + await self._run_action_per_layout_test_case( + queue_test_render, layout_categories_to_test, quick ) - await gather(*preparation_routines) @Slot(str, bool) def test_all(self, layout: str | None = None, quick: bool = False) -> None: + _logger.info( + f"Queueing {'quick' if quick else 'all'} test renders{ + f' for layout {layout}' if layout else '' + }" + ) cancel_with_render( ensure_future( - self.test_all_renders(LayoutCategory(layout) if layout else None, quick) + self.test_renders( + {LayoutCategory(layout): None} if layout else None, quick + ) ), self._render_queue, ) diff --git a/src/utils/data_structures.py b/src/utils/data_structures.py index 792a7ad2..02759224 100644 --- a/src/utils/data_structures.py +++ b/src/utils/data_structures.py @@ -10,6 +10,13 @@ def first[T](iterable: Iterable[T]) -> T: return next(iter(iterable)) +def find_item[T](iterable: Iterable[T], condition: Callable[[T], bool]) -> T | None: + for item in iterable: + if condition(item): + return item + return None + + def find_index[T](iterable: Iterable[T], condition: Callable[[T], bool]) -> int: for idx, item in enumerate(iterable): if condition(item): diff --git a/src/utils/images.py b/src/utils/images.py index dd16e3be..74239c4d 100644 --- a/src/utils/images.py +++ b/src/utils/images.py @@ -1,18 +1,8 @@ -from dataclasses import dataclass -from logging import getLogger from os import PathLike from pathlib import Path from typing import IO from PIL import Image -from pydantic import ValidationError - -from src.cards import CardDetails, parse_card_info -from src.render_spec import parse_render_spec -from src.utils.data_structures import find_index -from src.utils.scryfall import ScryfallCard - -_logger = getLogger(__name__) IMAGE_ENCODING_TO_SUFFIX_MAPPING: dict[str, str] = { "PNG": ".png", @@ -49,70 +39,3 @@ def save_scaled_card_image( else: save_kwargs = {} f.save(output_path, format=image_format, **save_kwargs) - - -def match_images_with_data_files( - paths: list[Path], -) -> list[CardDetails | tuple[CardDetails, ScryfallCard]]: - """ - Pairs data files (.json) with image files that share the same name. - - Raises: - Pydantic.ValidationError: if some of the data files don't conform to the data model - """ - data_files = [pth for pth in paths if pth.suffix == ".json"] - render_specs = [pth for pth in paths if pth.suffix in (".yaml", ".yml")] - image_files = [pth for pth in paths if pth.suffix not in (".json", ".yaml", ".yml")] - - results: list[CardDetails | tuple[CardDetails, ScryfallCard]] = [] - - @dataclass - class _ValidationError(ValidationError): - file: Path - - def add_card(card: CardDetails) -> None: - card_name = card["name"] - - idx = find_index(data_files, lambda item: item.stem == card_name) - if idx > -1: - data_file = data_files.pop(idx) - try: - results.append( - ( - card, - ScryfallCard.model_validate_json(data_file.read_bytes()), - ) - ) - except ValidationError as e: - raise _ValidationError(data_file) from e - else: - results.append(card) - - try: - for path in render_specs: - try: - cards = parse_render_spec(path).cards - except ValidationError as e: - raise _ValidationError(path) from e - - for card in cards: - add_card(card) - - for path in image_files: - card = parse_card_info(path) - add_card(card) - except _ValidationError as e: - _logger.exception( - f"Data file {e.file} failed to validate. Please correct the reported errors in the data and then try again. Since the file selection was invalid nothing will be added to the render queue." - ) - return [] - - if data_files: - _logger.warning( - f"Couldn't find a matching image file for files:
{ - '
'.join([str(pth) for pth in data_files]) - }When selecting JSON files for rendering make sure to also select an image whose card name part matches the JSON file's name, e.g. my_custom_card (artist).png and my_custom_card.json. Since the file selection was invalid nothing will be added to the render queue." - ) - return [] - - return results diff --git a/src/utils/inputs.py b/src/utils/inputs.py new file mode 100644 index 00000000..1f849ee2 --- /dev/null +++ b/src/utils/inputs.py @@ -0,0 +1,150 @@ +from asyncio import gather, to_thread +from collections.abc import Awaitable, Callable, Iterable, Sequence +from itertools import batched +from logging import getLogger +from pathlib import Path + +from pydantic import ValidationError + +from src._config import AppConfig +from src.cards import ( + CardDetails, + card_details_to_scryfall_identifier, + get_batch_of_cards, + get_card_data, + parse_card_info, +) +from src.render_spec import parse_render_spec +from src.utils.data_structures import find_index, find_item +from src.utils.scryfall import CardIdentifier, ScryfallCard + +_logger = getLogger(__name__) + + +def match_images_with_data_files( + paths: Iterable[Path], +) -> list[CardDetails | tuple[CardDetails, ScryfallCard]]: + """ + Pairs data files (.json) with image files that share the same name. + + Raises: + Pydantic.ValidationError: if some of the data files don't conform to the data model + """ + data_files = [pth for pth in paths if pth.suffix == ".json"] + render_specs = [pth for pth in paths if pth.suffix in (".yaml", ".yml")] + image_files = [pth for pth in paths if pth.suffix not in (".json", ".yaml", ".yml")] + + results: list[CardDetails | tuple[CardDetails, ScryfallCard]] = [] + + def log_data_exception(file: Path) -> None: + _logger.exception( + f"Data file {file} failed to validate. Please correct the reported errors in the data and then try again. Since the file selection was invalid nothing will be added to the render queue." + ) + + def add_card(card: CardDetails) -> None: + card_name = card["name"] + + idx = find_index(data_files, lambda item: item.stem == card_name) + if idx > -1: + data_file = data_files.pop(idx) + try: + results.append( + ( + card, + ScryfallCard.model_validate_json(data_file.read_bytes()), + ) + ) + except ValidationError: + log_data_exception(data_file) + raise + else: + results.append(card) + + try: + for path in render_specs: + try: + cards = parse_render_spec(path).cards + except ValidationError: + log_data_exception(path) + raise + + for card in cards: + add_card(card) + + for path in image_files: + card = parse_card_info(path) + add_card(card) + except ValidationError: + return [] + + if data_files: + _logger.warning( + f"Couldn't find a matching image file for files:
{ + '
'.join([str(pth) for pth in data_files]) + }When selecting JSON files for rendering make sure to also select an image whose card name part matches the JSON file's name, e.g. my_custom_card (artist).png and my_custom_card.json. Since the file selection was invalid nothing will be added to the render queue." + ) + return [] + + return results + + +async def get_cards_from_details( + inputs: Iterable[CardDetails | tuple[CardDetails, ScryfallCard]], + callback: Callable[[Sequence[tuple[CardDetails, ScryfallCard]]], Awaitable[None]], + config: AppConfig, +) -> None: + """Calls the callback on fetched Scryfall cards as the API requests finish. + + The collection endpoint doesn't allow looking up cards by language, so when using + a language other than english we have to fall back to individual requests per card.""" + + async def batch_fetch_and_queue( + batch: Iterable[tuple[CardIdentifier, list[CardDetails]]], + ) -> None: + cards = await to_thread(get_batch_of_cards, batch) + await callback(cards) + + async def single_fetch_and_queue( + card: tuple[CardIdentifier, list[CardDetails]], + ) -> None: + if scryfall_card := await to_thread(get_card_data, card[0], config): + for crd in card[1]: + await callback(((crd, scryfall_card),)) + + cards_to_batch_fetch: list[tuple[CardIdentifier, list[CardDetails]]] = [] + cards_to_fetch_individually: list[tuple[CardIdentifier, list[CardDetails]]] = [] + ready_cards: list[tuple[CardDetails, ScryfallCard]] = [] + + active_list = ( + cards_to_batch_fetch if config.lang == "en" else cards_to_fetch_individually + ) + + for input in inputs: + if isinstance(input, dict): + identifier = card_details_to_scryfall_identifier(input) + if existing := find_item(active_list, lambda item: item[0] == identifier): + existing[1].append(input) + else: + active_list.append((identifier, [input])) + else: + ready_cards.append(input) + + await gather( + callback(ready_cards), + *[batch_fetch_and_queue(batch) for batch in batched(cards_to_batch_fetch, 75)], + *[single_fetch_and_queue(card) for card in cards_to_fetch_individually], + ) + + +async def get_cards_from_inputs( + inputs: Iterable[Path], + callback: Callable[[Sequence[tuple[CardDetails, ScryfallCard]]], Awaitable[None]], + config: AppConfig, +) -> None: + """Calls the callback on fetched Scryfall cards as the API requests finish. + + The collection endpoint doesn't allow looking up cards by language, so when using + a language other than english we have to fall back to individual requests per card.""" + return await get_cards_from_details( + match_images_with_data_files(inputs), callback, config + ) diff --git a/src/utils/scryfall.py b/src/utils/scryfall.py index ab1fc123..45758705 100644 --- a/src/utils/scryfall.py +++ b/src/utils/scryfall.py @@ -2,17 +2,17 @@ * Scryfall API Module """ -from collections.abc import Callable, Sequence +from collections.abc import Callable, Iterable, Sequence from datetime import date +from json import dumps from logging import getLogger from pathlib import Path from shutil import copyfileobj -from typing import Literal, NotRequired, ParamSpec, SupportsInt, TypedDict, TypeVar +from typing import TYPE_CHECKING, Literal, NotRequired, SupportsInt, TypedDict +from urllib.parse import quote_plus, urlencode from uuid import UUID import requests -import yarl -from hexproof.scryfall.enums import ScryURL from limits import RateLimitItemPerSecond from limits.storage import MemoryStorage from limits.strategies import MovingWindowRateLimiter @@ -22,49 +22,22 @@ from requests.exceptions import RequestException from src._state import PATH +from src.enums.mtg import LayoutScryfall from src.utils.download import HEADERS +if TYPE_CHECKING: + from urllib.parse import ( + _QueryType, # pyright: ignore[reportPrivateUsage] + _QuoteVia, # pyright: ignore[reportPrivateUsage] + ) + _logger = getLogger() """ * Types """ -T = TypeVar("T") -P = ParamSpec("P") - -LayoutType = Literal[ - "adventure", - "art_series", - "augment", - "battle", - "case", - "class", - "double_faced_token", - "emblem", - "flip", - "host", - "leveler", - "meld", - "modal_dfc", - "mutate", - "normal", - "planar", - "prepare", - "prototype", - "reversible_card", - "saga", - "scheme", - "split", - "token", - "transform", - "vanguard", - # Non-official extensions - "planeswalker", - "planeswalker_tf", - "planeswalker_mdfc", - "station", -] +QueryVar = object | Sequence[object] MagicColor = Literal["W", "U", "B", "R", "G"] @@ -188,7 +161,7 @@ class ScryfallCardFace(BaseModel): flavor_text: str | None = None illustration_id: UUID | None = None image_uris: ScryfallImageUris | None = None - layout: LayoutType | None = None + layout: LayoutScryfall | None = None loyalty: str | None = None mana_cost: str | None = None name: str @@ -216,7 +189,7 @@ class ScryfallCard(BaseModel): tcgplayer_etched_id: int | None = None cardmarket_id: int | None = None object: Literal["card"] - layout: LayoutType + layout: LayoutScryfall oracle_id: UUID | None = None prints_search_uri: HttpUrl rulings_uri: HttpUrl @@ -303,7 +276,7 @@ class ScryfallCard(BaseModel): class ScryfallList[T](BaseModel): object: Literal["list"] data: list[T] - has_more: bool + has_more: bool = False next_page: HttpUrl | None = None total_cards: int | None = None warnings: list[str] | None = None @@ -352,6 +325,42 @@ class ScryfallError(BaseModel): warnings: list[str] | None = None +# region URLs + +_base_scryfall_url = "https://api.scryfall.com" +_cards_url = f"{_base_scryfall_url}/cards" +_cards_collection_url = f"{_cards_url}/collection" + + +def _cards_single_url(code: str, number: str | int, lang: str | None = None) -> str: + """/cards/:code/:number(/:lang)""" + return f"{_cards_url}/{code}/{number}{f'/{lang}' if lang else ''}" + + +def _cards_search_url( + query: _QueryType | None = None, + doseq: bool = False, + safe: str | bytes = "", + encoding: str | None = None, + errors: str | None = None, + quote_via: _QuoteVia = quote_plus, +) -> str: + """/cards/search(?:query) + Query is encoded with `urllib.parse.urlencode`""" + return f"{_cards_url}/search{ + f'?{urlencode(query, doseq, safe, encoding, errors, quote_via)}' + if query + else '' + }" + + +def _sets_url(code: str | UUID | None = None) -> str: + return f"{_base_scryfall_url}/sets{f'/{code}' if code else ''}" + + +# endregion URLs + + """ * Scryfall Objects """ @@ -422,8 +431,29 @@ def __init__( response = exception.response super().__init__(msg, request=request, response=response) + def __str__(self) -> str: + """Return a detailed error message with response debug information.""" + msg = super().__str__() + + if self.response is not None and not self.response.ok: + # Add HTTP status information + msg += f"\nHTTP Status: {self.response.status_code}" + if self.response.reason: + msg += f" ({self.response.reason})" + + # Add response content if available + if self.response.text: + # Show a concise version of the response + response_text = self.response.text.strip() + if len(response_text) > 200: + msg += f"\nResponse: {response_text[:200]}..." + else: + msg += f"\nResponse: {response_text}" + + return msg + -def scryfall_request_wrapper() -> Callable[[Callable[P, T]], Callable[P, T]]: +def scryfall_request_wrapper[**P, T]() -> Callable[[Callable[P, T]], Callable[P, T]]: """Wrapper for a Scryfall request function to handle retries, rate limits, and a final exception catch. Returns: @@ -520,11 +550,12 @@ def get_card_unique(card_set: str, card_number: str, lang: str = "en") -> Scryfa ScryfallException: if the request fails """ # Establish API pathing - url = ScryURL.API.Cards.Main / card_set.lower() / card_number - url = url / lang if lang != "en" else url + url = _cards_single_url( + card_set.lower(), card_number, lang if lang != "en" else None + ) # Request the data - res = requests.get(url=str(url), headers=scryfall_http_header) + res = requests.get(url=url, headers=scryfall_http_header) try: card = ScryfallCard.model_validate_json(res.content) @@ -594,15 +625,13 @@ def get_card_search( """ # Query Scryfall res = requests.get( - url=str( - ScryURL.API.Cards.Search.with_query( - { - "q": f'!"{card_name}"' - f" lang:{lang}" - f"{f' set:{card_set.lower()}' if card_set else ''}", - **kwargs, - } - ) + url=_cards_search_url( + { + "q": f'!"{card_name}" lang:{lang} { + f" set:{card_set.lower()}" if card_set else "" + }', + **kwargs, + } ), headers=scryfall_http_header, ) @@ -645,9 +674,9 @@ def get_card_search( @scryfall_request_wrapper() @return_on_exception([]) def get_cards_paged( - url: yarl.URL | None = None, + url: str | None = None, all_pages: bool = True, - **kwargs: yarl.QueryVariable, + **kwargs: QueryVar, ) -> list[ScryfallCard]: """Grab paginated card list from a Scryfall API endpoint. @@ -657,19 +686,21 @@ def get_cards_paged( **kwargs: Optional parameters to pass to API endpoint. """ # Configure URL object - url = url or ScryURL.API.Cards.Search + url = ( + f"{url}{f'?{urlencode(kwargs)}' if kwargs else None}" + if url + else _cards_search_url(kwargs) + ) # Query Scryfall - res = requests.get(url=str(url.with_query(kwargs)), headers=scryfall_http_header) + res = requests.get(url=str(_cards_search_url(kwargs)), headers=scryfall_http_header) try: list_data = ScryfallCardList.model_validate_json(res.content) cards = list_data.data if all_pages and list_data.has_more and list_data.next_page: cards.extend( - get_cards_paged( - url=yarl.URL(str(list_data.next_page)), all_pages=all_pages - ) + get_cards_paged(url=str(list_data.next_page), all_pages=all_pages) ) return cards except ValidationError as exc: @@ -688,9 +719,9 @@ def get_cards_paged( @scryfall_request_wrapper() @return_on_exception(None) def get_cards_oracle( - oracle_id: str, + oracle_id: str | UUID, all_pages: bool = False, - **kwargs: yarl.QueryVariable, + **kwargs: QueryVar, ) -> list[ScryfallCard]: """Grab paginated card list from a Scryfall API endpoint using the Oracle ID of the card. @@ -703,7 +734,7 @@ def get_cards_oracle( A list of card objects. """ return get_cards_paged( - url=ScryURL.API.Cards.Search, + url=None, all_pages=all_pages, q=f"oracleid:{oracle_id}", dir=kwargs.pop("dir", "asc"), @@ -713,6 +744,77 @@ def get_cards_oracle( ) +class CardIdentifierBase(TypedDict): + id: NotRequired[str | UUID] + mtgo_id: NotRequired[int] + multiverse_id: NotRequired[int] + oracle_id: NotRequired[str | UUID] + illustration_id: NotRequired[str | UUID] + + +class CardIdentifierWithName(CardIdentifierBase): + name: NotRequired[str] + + +class CardIdentifierWithNameAndSet(CardIdentifierBase): + name: str + set: str + + +class CardIdentifierWithCollectorNumber(CardIdentifierBase): + collector_number: str + set: str + + +CardIdentifier = ( + CardIdentifierWithCollectorNumber + | CardIdentifierWithNameAndSet + | CardIdentifierWithName +) + + +class ScryfallCollectionList(ScryfallList[ScryfallCard]): + not_found: list[CardIdentifier] = [] + + +@scryfall_request_wrapper() +@return_on_exception(None) +def get_cards_collection( + identifiers: Iterable[CardIdentifier], +) -> ScryfallCollectionList: + """Get cards using POST /cards/collection Scryfall API endpoint. + A maximum of 75 identifiers may be submitted per request. + + Notes: + https://scryfall.com/docs/api/cards/collection + + Raises: + ScryfallException: if the request fails + """ + res = requests.post( + url=_cards_collection_url, + json={"identifiers": identifiers}, + headers=scryfall_http_header, + ) + + try: + return ScryfallCollectionList.model_validate_json(res.content) + except ValidationError as exc: + _logger.exception("Validation exc") + try: + err = ScryfallError.model_validate_json(res.content) + scry_exc = _get_scryfall_exception(error=err, response=res) + except ValidationError: + scry_exc = ScryfallException(exception=exc) + _logger.error( + f"Couldn't retrieve the following cards from Scryfall collection endpoint:
{ + dumps(identifiers, indent=2) + }", + exc_info=scry_exc, + ) + raise scry_exc from exc + + """ * Scryfall Requests: Sets """ @@ -730,9 +832,7 @@ def get_set(card_set: str) -> ScryfallSet | None: Scryfall set dict or empty dict. """ # Make the request - res = requests.get( - str(ScryURL.API.Sets.All / card_set.upper()), headers=scryfall_http_header - ) + res = requests.get(_sets_url(card_set.upper()), headers=scryfall_http_header) try: return ScryfallSet.model_validate_json(res.content) From 42751b77ee33d3b695e56ad9df52d90ce5da8da8 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 6 May 2026 12:44:42 +0300 Subject: [PATCH 137/190] test: Separate tests that aren't aimed at plugin authors from the app's code --- src/commands/test/__init__.py | 80 ----- src/commands/test/frame_logic.py | 204 ------------ src/commands/test/scryfall.py | 54 ---- src/commands/test/text_logic.py | 75 ----- src/data/tests/frame_data.toml | 295 ------------------ .../utility.py => helpers/miscellaneous.py} | 164 +++++----- tests/__init__.py | 0 tests/api/__init__.py | 0 tests/api/test_scryfall.py | 65 ++++ tests/cards/__init__.py | 0 tests/cards/test_layouts.py | 98 ++++++ tests/cards/test_text.py | 30 ++ {src/commands/test => tests}/compression.py | 31 +- tests/data/layout_data.toml | 295 ++++++++++++++++++ .../tests => tests/data}/text_italic.toml | 0 tests/utils.py | 8 + 16 files changed, 582 insertions(+), 817 deletions(-) delete mode 100644 src/commands/test/__init__.py delete mode 100644 src/commands/test/frame_logic.py delete mode 100644 src/commands/test/scryfall.py delete mode 100644 src/commands/test/text_logic.py delete mode 100644 src/data/tests/frame_data.toml rename src/{commands/test/utility.py => helpers/miscellaneous.py} (86%) create mode 100644 tests/__init__.py create mode 100644 tests/api/__init__.py create mode 100644 tests/api/test_scryfall.py create mode 100644 tests/cards/__init__.py create mode 100644 tests/cards/test_layouts.py create mode 100644 tests/cards/test_text.py rename {src/commands/test => tests}/compression.py (85%) create mode 100644 tests/data/layout_data.toml rename {src/data/tests => tests/data}/text_italic.toml (100%) create mode 100644 tests/utils.py diff --git a/src/commands/test/__init__.py b/src/commands/test/__init__.py deleted file mode 100644 index 1fd8a60e..00000000 --- a/src/commands/test/__init__.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -* CLI Commands: Testing -""" - -from logging import getLogger - -import click - -from src._state import PATH -from src.commands.test import frame_logic, text_logic - -_logger = getLogger(__name__) - -""" -* Commands -""" - - -@click.command( - short_help="Test Scryfall data frame logic analysis across a variety of card cases.", - help="Test Scryfall data frame logic analysis across a variety of card cases. Frame analysis " - "is used to determine what colors and textures should be used on a given card.", -) -@click.option( - "-T", "--target", is_flag=True, default=False, help="Evaluate a specific test case." -) -def test_frame_logic(target: bool = False): - """Run Frame Logic test on all cases.""" - _logger.info(f"Test Utility: Frame Logic ({PATH.CWD})") - cases = frame_logic.get_frame_logic_cases() - - # Was this a targeted case? - if not target: - return frame_logic.test_all_cases() - - # Choose test case - options = {str(i): title for i, title in enumerate(cases.keys())} - while True: - choice = input( - "Enter the number for a test case to evaluate:\n" - + "\n".join([f"[{i}] {n}" for i, n in options.items()]) - ) - if choice not in options: - print("Choice provided was invalid.") - break - - # Run the test - case = options[choice] - _logger.info(f"CASE: {case}") - frame_logic.test_target_case(cases[case]) - - -@click.command( - short_help="Test Scryfall data text logic analysis across a variety of card cases.", - help="Test Scryfall data text logic analysis across a variety of card cases. Text analysis is used to " - "decide what text should be italicized when rendering the card.", -) -def test_text_logic(): - """Run Text Logic test on all cases.""" - text_logic.test_all_cases() - - -""" -* Command Groups -""" - - -@click.group( - name="test", - chain=True, - help="Commands that test app functionality.", - commands={"logic.frame": test_frame_logic, "logic.text": test_text_logic}, -) -def test_cli(): - """Cli interface for test funcs.""" - pass - - -# Export CLI -__all__ = ["test_cli"] diff --git a/src/commands/test/frame_logic.py b/src/commands/test/frame_logic.py deleted file mode 100644 index 3cc582e8..00000000 --- a/src/commands/test/frame_logic.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -* Tests: Frame Logic -* Credit to Chilli: https://tinyurl.com/chilli-frame-logic-tests -""" - -from concurrent.futures import Future, as_completed -from concurrent.futures import ThreadPoolExecutor as Pool -from logging import getLogger -from multiprocessing import cpu_count -from pathlib import Path - -from colorama import Fore, Style -from omnitils.files import load_data_file -from tqdm import tqdm - -from src import CFG -from src._state import PATH -from src.cards import CardDetails, get_card_data, process_card_data -from src.console import LogColors -from src.enums.mtg import CardTextPatterns -from src.layouts import NormalLayout, layout_map - -_logger = getLogger(__name__) - -""" -* TYPES -""" - -FrameData = list[str] - -""" -* UTIL FUNCS -""" - - -def get_frame_logic_cases() -> dict[str, dict[str, FrameData]]: - """Return frame logic test cases from TOML data file.""" - return load_data_file(Path(PATH.SRC_DATA_TESTS, "frame_data.toml")) - - -def format_result(layout: NormalLayout) -> FrameData: - """Format frame logic test result for comparison. - - Args: - layout: Test result card layout data. - - Returns: - Formatted frame logic test result data. - """ - return [ - str(layout.__class__.__name__), - str(layout.background), - str(layout.pinlines), - str(layout.twins), - str(layout.is_nyx), - str(layout.is_colorless), - ] - - -""" -* TEST FUNCS -""" - - -def test_case( - card_name: str, card_data: FrameData -) -> tuple[str, FrameData, FrameData] | None: - """Test frame logic for a target test case. - - Args: - card_name: Card name for test case. - card_data: Card data for test case. - - Returns: - Tuple containing (card name, actual data, correct data) if the test failed, otherwise None. - """ - try: - # Check if a set code was provided - set_code = None - if all([n in card_name for n in ["[", "]"]]): - if set_match := CardTextPatterns.PATH_SET.search(card_name): - set_code = set_match.group(1) - card_name = card_name.replace(f"[{set_code}]", "").strip() - - # Create a fake card details object - details: CardDetails = { - "name": card_name, - "set": set_code or "", - "number": "", - "creator": "", - "file": Path(), - "artist": "", - "kwargs": {}, - } - - # Pull Scryfall data - scryfall = get_card_data(card=details, cfg=CFG) - if not scryfall: - raise OSError("Did not return valid data from Scryfall.") - # Process the Scryfall data - scryfall = process_card_data(scryfall, details) - except Exception as e: - # Exception occurred during Scryfall lookup - return _logger.error( - f"Scryfall error occurred at card: '{card_name}'", exc_info=e - ) - - # Pull layout data for the card - try: - result_data: FrameData = format_result( - layout_map[scryfall.layout]( - scryfall=scryfall, - file={ - "name": card_name, - "artist": scryfall.artist or "", - "set": scryfall.set, - "number": scryfall.collector_number, - "creator": "", - "file": Path(), - "kwargs": {}, - }, - config=CFG, - ) - ) - except Exception as e: - # Exception occurred during layout generation - return _logger.error( - f"Layout error occurred at card: '{card_name}'", exc_info=e - ) - - # Compare the results - if not result_data == card_data: - return card_name, result_data, card_data - return - - -def test_target_case(cards: dict[str, FrameData]) -> None: - """Test a known Frame Logic test case. - - Args: - cards: Individual card frame cases to test. - - Returns: - True if tests succeeded, otherwise False. - """ - # Submit tests to a pool - with Pool(max_workers=cpu_count()) as executor: - tests_submitted: list[Future[tuple[str, FrameData, FrameData] | None]] = [] - tests_failed: list[tuple[str, FrameData, FrameData]] = [] - - # Submit tasks to executor - for card_name, data in cards.items(): - tests_submitted.append(executor.submit(test_case, card_name, data)) - - # Create a progress bar - pbar = tqdm( - total=len(tests_submitted), - bar_format=f"{LogColors.BLUE}{{l_bar}}{{bar}}{{r_bar}}{LogColors.RESET}", - ) - - # Iterate over completed tasks, update progress bar, add failed tasks - for task in as_completed(tests_submitted): - pbar.update(1) - if result := task.result(): - tests_failed.append(result) - - # Set the progress bar result - if tests_failed: - pbar.set_postfix( - { - "Status": (Fore.RED + "FAILED" + Style.RESET_ALL) - if tests_failed - else (Fore.GREEN + "SUCCESS" + Style.RESET_ALL) - } - ) - - # Close progress bar and return failures - pbar.close() - - # Log failed results - if tests_failed: - _logger.error("=" * 40) - for name, actual, correct in tests_failed: - _logger.warning(f"NAME: {name}") - _logger.warning( - f"RESULT [Actual / Expected]:\n" - f"{LogColors.RESET}{LogColors.WHITE}{actual}\n{correct}" - ) - _logger.error("=" * 40) - _logger.error("SOME TESTS FAILED!") - return - - # All tests successful - _logger.info("ALL TESTS SUCCESSFUL!") - - -def test_all_cases() -> None: - """Test all Frame Logic cases.""" - cases = get_frame_logic_cases() - - # Submit tests to a pool - for case, cards in cases.items(): - _logger.info(f"CASE: {case}") - test_target_case(cards) diff --git a/src/commands/test/scryfall.py b/src/commands/test/scryfall.py deleted file mode 100644 index e179cfb1..00000000 --- a/src/commands/test/scryfall.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -* Tests: Scryfall -""" - -from src.utils.scryfall import get_card_search, get_card_unique, get_set - -""" -* Test Funcs -""" - - -def test_scryfall_unique(): - """Test the request function for Scryfall's '/cards/set/num' endpoint.""" - try: - obj = get_card_unique(card_set="TSR", card_number="50", lang="en") - print("SUCCESS:", obj["name"], obj["set"], obj["collector_number"]) - return obj - except Exception as e: - print(e) - return {} - - -def test_scryfall_search(): - """Test the request function for Scryfall's '/cards/search' endpoint.""" - try: - obj = get_card_search(card_name="Damnation", card_set="TSR", lang="en") - print("SUCCESS:", obj["name"], obj["set"], obj["collector_number"]) - return obj - except Exception as e: - print(e) - return {} - - -def test_scryfall_set(): - """Test the request function for Scryfall's '/sets/code' endpoint.""" - try: - obj = get_set(card_set="TSR") - print("SUCCESS:", obj["name"], obj["code"]) - return obj - except Exception as e: - print(e) - return {} - - -""" -* Cli Entrypoints -""" - - -def test_all_cases() -> None: - """Test all Scryfall request cases.""" - test_scryfall_unique() - test_scryfall_search() - test_scryfall_set() diff --git a/src/commands/test/text_logic.py b/src/commands/test/text_logic.py deleted file mode 100644 index f3fbff49..00000000 --- a/src/commands/test/text_logic.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -* Tests: Card Text Logic -""" - -from logging import getLogger -from pathlib import Path -from typing import TypedDict - -from omnitils.files import load_data_file - -from src._state import PATH -from src.cards import generate_italics - -_logger = getLogger(__name__) - -""" -* Types -""" - - -class TestCaseTextItalic(TypedDict): - """Test cases for validating the generate italics function.""" - - result: list[str] - scenario: str - text: str - - -""" -* Test Funcs -""" - - -def test_all_cases() -> bool: - """Test all Text Logic cases.""" - - # Load our test cases - success = True - test_file: Path = Path(PATH.SRC_DATA_TESTS, "text_italic.toml") - test_cases: dict[str, TestCaseTextItalic] = load_data_file(test_file) - _logger.info(f"Testing > Card Text Logic ({test_file.name})") - - # Check each test case for success - for name, case in test_cases.items(): - # Compare actual test results VS expected test results - result_actual, result_expected = ( - generate_italics(case.get("text", "")), - case.get("result", []), - ) - if not sorted(result_actual) == sorted(result_expected): - success = False - msg_actual = "".join( - f"\n {i}. {n}" for i, n in enumerate(result_actual, start=1) - ) - msg_expected = "".join( - f"\n {i}. {n}" for i, n in enumerate(result_expected, start=1) - ) - _logger.error(f"Case: {name} ({case.get('scenario', '')})") - - # Log what we expect - if not result_expected: - _logger.warning("This card doesn't have italic text!") - else: - _logger.warning(f"Italic strings expected: {msg_expected}") - - # Log what we found - if not result_actual: - _logger.warning("No italic strings were found!") - else: - _logger.warning(f"Italic strings found: {msg_actual}") - - # Did any tests fail? - if success: - _logger.info("All tests successful!") - return success diff --git a/src/data/tests/frame_data.toml b/src/data/tests/frame_data.toml deleted file mode 100644 index 34faf061..00000000 --- a/src/data/tests/frame_data.toml +++ /dev/null @@ -1,295 +0,0 @@ -# TEST CASES: Frame Logic - -# * TEST CASE -["Mono Color, Normal Frame"] -"Healing Salve" = ["NormalLayout", "W", "W", "W", "False", "False"] -"Ancestral Recall" = ["NormalLayout", "U", "U", "U", "False", "False"] -"Dark Ritual" = ["NormalLayout", "B", "B", "B", "False", "False"] -"Lightning Bolt" = ["NormalLayout", "R", "R", "R", "False", "False"] -"Giant Growth" = ["NormalLayout", "G", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, 2/C in Mana Cost"] -"Spectral Procession" = ["NormalLayout", "W", "W", "W", "False", "False"] -"Advice from the Fae" = ["NormalLayout", "U", "U", "U", "False", "False"] -"Beseech the Queen" = ["NormalLayout", "B", "B", "B", "False", "False"] -"Flame Javelin" = ["NormalLayout", "R", "R", "R", "False", "False"] -"Tower Above" = ["NormalLayout", "G", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, 0 Cost Pacts"] -"Intervention Pact" = ["NormalLayout", "W", "W", "W", "False", "False"] -"Pact of Negation" = ["NormalLayout", "U", "U", "U", "False", "False"] -"Slaughter Pact" = ["NormalLayout", "B", "B", "B", "False", "False"] -"Pact of the Titan" = ["NormalLayout", "R", "R", "R", "False", "False"] -"Summoner's Pact" = ["NormalLayout", "G", "G", "G", "False", "False"] - -# * TEST CASE -["Nyxtouched Enchantment Creatures"] -"Heliod, God of the Sun [THS]" = ["NormalLayout", "W", "W", "W", "True", "False"] -"Thassa, God of the Sea" = ["NormalLayout", "U", "U", "U", "True", "False"] -"Erebos, God of the Dead" = ["NormalLayout", "B", "B", "B", "True", "False"] -"Purphoros, God of the Forge [THS]" = ["NormalLayout", "R", "R", "R", "True", "False"] -"Nylea, God of the Hunt" = ["NormalLayout", "G", "G", "G", "True", "False"] - -# * TEST CASE -["No Mana Cost, Suspend"] -"Restore Balance" = ["NormalLayout", "W", "W", "W", "False", "False"] -"Ancestral Vision" = ["NormalLayout", "U", "U", "U", "False", "False"] -"Living End" = ["NormalLayout", "B", "B", "B", "False", "False"] -"Wheel of Fate" = ["NormalLayout", "R", "R", "R", "False", "False"] -"Hypergenesis" = ["NormalLayout", "G", "G", "G", "False", "False"] -"Lotus Bloom" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] - -# * TEST CASE -["Dual Color, Normal Frame"] -"Azorius Charm" = ["NormalLayout", "Gold", "WU", "Gold", "False", "False"] -"Dimir Charm" = ["NormalLayout", "Gold", "UB", "Gold", "False", "False"] -"Rakdos Charm" = ["NormalLayout", "Gold", "BR", "Gold", "False", "False"] -"Gruul Charm" = ["NormalLayout", "Gold", "RG", "Gold", "False", "False"] -"Selesnya Charm" = ["NormalLayout", "Gold", "GW", "Gold", "False", "False"] -"Orzhov Charm" = ["NormalLayout", "Gold", "WB", "Gold", "False", "False"] -"Golgari Charm" = ["NormalLayout", "Gold", "BG", "Gold", "False", "False"] -"Simic Charm" = ["NormalLayout", "Gold", "GU", "Gold", "False", "False"] -"Izzet Charm" = ["NormalLayout", "Gold", "UR", "Gold", "False", "False"] -"Boros Charm" = ["NormalLayout", "Gold", "RW", "Gold", "False", "False"] - -# * TEST CASE -["Dual Color, Hybrid Frame"] -"Godhead of Awe" = ["NormalLayout", "WU", "WU", "Land", "False", "False"] -"Ghastlord of Fugue" = ["NormalLayout", "UB", "UB", "Land", "False", "False"] -"Demigod of Revenge" = ["NormalLayout", "BR", "BR", "Land", "False", "False"] -"Deus of Calamity" = ["NormalLayout", "RG", "RG", "Land", "False", "False"] -"Oversoul of Dusk" = ["NormalLayout", "GW", "GW", "Land", "False", "False"] -"Divinity of Pride" = ["NormalLayout", "WB", "WB", "Land", "False", "False"] -"Deity of Scars" = ["NormalLayout", "BG", "BG", "Land", "False", "False"] -"Overbeing of Myth" = ["NormalLayout", "GU", "GU", "Land", "False", "False"] -"Dominus of Fealty" = ["NormalLayout", "UR", "UR", "Land", "False", "False"] -"Nobilis of War" = ["NormalLayout", "RW", "RW", "Land", "False", "False"] - -# * TEST CASE -["No Mana Cost, Hybrid Frame"] -"Asmoranomardicadaistinaculdacar" = ["NormalLayout", "BR", "BR", "Land", "False", "False"] - -# * TEST CASE -["Dual Color, Gold Frame, Hybrid Mana"] -"Maelstrom Muse" = ["NormalLayout", "Gold", "UR", "Gold", "False", "False"] -"Ajani, Sleeper Agent" = ["PlaneswalkerLayout", "Gold", "GW", "Gold", "False", "False"] -"Tamiyo, Compleated Sage" = ["PlaneswalkerLayout", "Gold", "GU", "Gold", "False", "False"] - -# * TEST CASE -["Tri Color, Gold Frame, Hybrid Mana"] -"Messenger Falcons" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["Double Faced Cards"] -"Insectile Aberration" = ["TransformLayout", "U", "U", "U", "False", "False"] -"Ravager of the Fells" = ["TransformLayout", "Gold", "RG", "Gold", "False", "False"] -"Brisela, Voice of Nightmares" = ["TransformLayout", "Colorless", "Colorless", "Colorless", "False", "True"] -"Urza, Planeswalker" = ["PlaneswalkerTransformLayout", "Gold", "WU", "Gold", "False", "False"] -"Archangel Avacyn" = ["TransformLayout", "W", "W", "W", "False", "False"] -"Avacyn, the Purifier" = ["TransformLayout", "R", "R", "R", "False", "False"] -"Curious Homunculus" = ["TransformLayout", "U", "U", "U", "False", "False"] -"Voracious Reader" = ["TransformLayout", "Colorless", "Colorless", "Colorless", "False", "True"] -"Barkchannel Pathway" = ["ModalDoubleFacedLayout", "Land", "G", "G", "False", "False"] -"Tidechannel Pathway" = ["ModalDoubleFacedLayout", "Land", "U", "U", "False", "False"] -"Blex, Vexing Pest" = ["ModalDoubleFacedLayout", "G", "G", "G", "False", "False"] -"Search for Blex" = ["ModalDoubleFacedLayout", "B", "B", "B", "False", "False"] -"Extus, Oriq Overlord" = ["ModalDoubleFacedLayout", "Gold", "WB", "Gold", "False", "False"] -"Awaken the Blood Avatar" = ["ModalDoubleFacedLayout", "Gold", "BR", "Gold", "False", "False"] - -# * TEST CASE -["Tri Color, Normal Frame"] -"Esper Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Grixis Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Jund Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Naya Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Bant Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Abzan Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Jeskai Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Sultai Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Mardu Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] -"Temur Charm" = ["NormalLayout", "Gold", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["Colorless, Non-Artifact"] -"Emrakul, the Aeons Torn" = ["NormalLayout", "Colorless", "Colorless", "Colorless", "False", "True"] -"Scion of Ugin" = ["NormalLayout", "Colorless", "Colorless", "Colorless", "False", "True"] - -# * TEST CASE -["Colorless, Artifact"] -"Herald's Horn" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Black Lotus" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Mox Pearl" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Mox Sapphire" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Mox Jet" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Mox Ruby" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] -"Mox Emerald" = ["NormalLayout", "Artifact", "Artifact", "Artifact", "False", "False"] - -# * TEST CASE -["Mono Color, Artifact"] -"The Circle of Loyalty" = ["NormalLayout", "Artifact", "W", "W", "False", "False"] -"The Magic Mirror" = ["NormalLayout", "Artifact", "U", "U", "False", "False"] -"The Cauldron of Eternity" = ["NormalLayout", "Artifact", "B", "B", "False", "False"] -"Embercleave" = ["NormalLayout", "Artifact", "R", "R", "False", "False"] -"The Great Henge" = ["NormalLayout", "Artifact", "G", "G", "False", "False"] - -# * TEST CASE -["Dual Color, Artifact"] -"Filigree Angel" = ["NormalLayout", "Artifact", "WU", "Gold", "False", "False"] -"Time Sieve" = ["NormalLayout", "Artifact", "UB", "Gold", "False", "False"] -"Demonspine Whip" = ["NormalLayout", "Artifact", "BR", "Gold", "False", "False"] -"Mage Slayer" = ["NormalLayout", "Artifact", "RG", "Gold", "False", "False"] -"Behemoth Sledge" = ["NormalLayout", "Artifact", "GW", "Gold", "False", "False"] -"Tainted Sigil" = ["NormalLayout", "Artifact", "WB", "Gold", "False", "False"] -"Shardless Agent" = ["NormalLayout", "Artifact", "GU", "Gold", "False", "False"] -"Etherium-Horn Sorcerer" = ["NormalLayout", "Artifact", "UR", "Gold", "False", "False"] - -# * TEST CASE -["Tri Color, Artifact"] -"Sphinx of the Steel Wind" = ["NormalLayout", "Artifact", "Gold", "Gold", "False", "False"] -"Thopter Foundry" = ["NormalLayout", "Artifact", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["WUBRG, Artifact"] -"Sphinx of the Guildpact" = ["NormalLayout", "Artifact", "Gold", "Gold", "False", "False"] -"Reaper King" = ["NormalLayout", "Artifact", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["Non-Colored, Utility Land"] -"Vesuva" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Evolving Wilds" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Karn's Bastion" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Hall of Heliod's Generosity" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Academy Ruins" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Volrath's Stronghold" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Gemstone Caverns" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Glacial Chasm" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Ash Barrens" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Crumbling Vestige" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Blighted Steppe" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Blighted Cataract" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Blighted Fen" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Blighted Gorge" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Blighted Woodland" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Maze's End" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Inventors' Fair" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Myriad Landscape" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Crystal Quarry" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Demolition Field" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] - -# * TEST CASE -["Non-Colored, Panorama"] -"Esper Panorama" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Grixis Panorama" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Jund Panorama" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Naya Panorama" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] -"Bant Panorama" = ["NormalLayout", "Land", "Land", "Land", "False", "False"] - -# * TEST CASE -["Mono Color, Land, Add {C}"] -"Castle Ardenvale" = ["NormalLayout", "Land", "W", "W", "False", "False"] -"Castle Vantress" = ["NormalLayout", "Land", "U", "U", "False", "False"] -"Castle Locthwain" = ["NormalLayout", "Land", "B", "B", "False", "False"] -"Castle Embereth" = ["NormalLayout", "Land", "R", "R", "False", "False"] -"Castle Garenbrig" = ["NormalLayout", "Land", "G", "G", "False", "False"] -"Serra's Sanctum" = ["NormalLayout", "Land", "W", "W", "False", "False"] -"Tolarian Academy" = ["NormalLayout", "Land", "U", "U", "False", "False"] -"Cabal Coffers" = ["NormalLayout", "Land", "B", "B", "False", "False"] -"Gaea's Cradle" = ["NormalLayout", "Land", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, Land, Basic Land Type"] -"Idyllic Grange" = ["NormalLayout", "Land", "W", "W", "False", "False"] -"Mystic Sanctuary" = ["NormalLayout", "Land", "U", "U", "False", "False"] -"Witch's Cottage" = ["NormalLayout", "Land", "B", "B", "False", "False"] -"Dwarven Mine" = ["NormalLayout", "Land", "R", "R", "False", "False"] -"Gingerbread Cabin" = ["NormalLayout", "Land", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, Land, Multicolor Activated Ability"] -"Axgard Armory" = ["NormalLayout", "Land", "W", "W", "False", "False"] -"Surtland Frostpyre" = ["NormalLayout", "Land", "R", "R", "False", "False"] -"Skemfar Elderhall" = ["NormalLayout", "Land", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, Land, All Lands are X"] -"Urborg, Tomb of Yawgmoth" = ["NormalLayout", "Land", "B", "B", "False", "False"] -"Yavimaya, Cradle of Growth" = ["NormalLayout", "Land", "G", "G", "False", "False"] - -# * TEST CASE -["Mono Color, Vivid Land"] -"Vivid Meadow" = ["NormalLayout", "Land", "W", "W", "False", "False"] -"Vivid Creek" = ["NormalLayout", "Land", "U", "U", "False", "False"] -"Vivid Marsh" = ["NormalLayout", "Land", "B", "B", "False", "False"] -"Vivid Crag" = ["NormalLayout", "Land", "R", "R", "False", "False"] -"Vivid Grove" = ["NormalLayout", "Land", "G", "G", "False", "False"] - -# * TEST CASE -["Dual Color, Land, Add {C} or {C}"] -"Celestial Colonnade" = ["NormalLayout", "Land", "WU", "Land", "False", "False"] -"Creeping Tar Pit" = ["NormalLayout", "Land", "UB", "Land", "False", "False"] -"Lavaclaw Reaches" = ["NormalLayout", "Land", "BR", "Land", "False", "False"] -"Raging Ravine" = ["NormalLayout", "Land", "RG", "Land", "False", "False"] -"Stirring Wildwood" = ["NormalLayout", "Land", "GW", "Land", "False", "False"] -"Shambling Vent" = ["NormalLayout", "Land", "WB", "Land", "False", "False"] -"Hissing Quagmire" = ["NormalLayout", "Land", "BG", "Land", "False", "False"] -"Lumbering Falls" = ["NormalLayout", "Land", "GU", "Land", "False", "False"] -"Wandering Fumarole" = ["NormalLayout", "Land", "UR", "Land", "False", "False"] -"Needle Spires" = ["NormalLayout", "Land", "RW", "Land", "False", "False"] - -# * TEST CASE -["Dual Color, Land, Basic Land Types"] -"Hallowed Fountain" = ["NormalLayout", "Land", "WU", "Land", "False", "False"] -"Watery Grave" = ["NormalLayout", "Land", "UB", "Land", "False", "False"] -"Blood Crypt" = ["NormalLayout", "Land", "BR", "Land", "False", "False"] -"Stomping Ground" = ["NormalLayout", "Land", "RG", "Land", "False", "False"] -"Temple Garden" = ["NormalLayout", "Land", "GW", "Land", "False", "False"] -"Godless Shrine" = ["NormalLayout", "Land", "WB", "Land", "False", "False"] -"Overgrown Tomb" = ["NormalLayout", "Land", "BG", "Land", "False", "False"] -"Breeding Pool" = ["NormalLayout", "Land", "GU", "Land", "False", "False"] -"Steam Vents" = ["NormalLayout", "Land", "UR", "Land", "False", "False"] -"Sacred Foundry" = ["NormalLayout", "Land", "RW", "Land", "False", "False"] - -# * TEST CASE -["Dual Color, Fetch Land"] -"Flooded Strand" = ["NormalLayout", "Land", "WU", "Land", "False", "False"] -"Polluted Delta" = ["NormalLayout", "Land", "UB", "Land", "False", "False"] -"Bloodstained Mire" = ["NormalLayout", "Land", "BR", "Land", "False", "False"] -"Wooded Foothills" = ["NormalLayout", "Land", "RG", "Land", "False", "False"] -"Windswept Heath" = ["NormalLayout", "Land", "GW", "Land", "False", "False"] -"Marsh Flats" = ["NormalLayout", "Land", "WB", "Land", "False", "False"] -"Verdant Catacombs" = ["NormalLayout", "Land", "BG", "Land", "False", "False"] -"Misty Rainforest" = ["NormalLayout", "Land", "GU", "Land", "False", "False"] -"Scalding Tarn" = ["NormalLayout", "Land", "UR", "Land", "False", "False"] -"Arid Mesa" = ["NormalLayout", "Land", "RW", "Land", "False", "False"] - -# * TEST CASE -["Tri Color, Land"] -"Arcane Sanctum" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Crumbling Necropolis" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Savage Lands" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Jungle Shrine" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Seaside Citadel" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Sandsteppe Citadel" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Mystic Monastery" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Opulent Palace" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Nomad Outpost" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Frontier Bivouac" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["Gold Color, Land"] -"Prismatic Vista" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Fabled Passage" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Aether Hub" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"City of Brass" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Mana Confluence" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Ally Encampment" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Command Tower" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] -"Thran Portal" = ["NormalLayout", "Land", "Gold", "Gold", "False", "False"] - -# * TEST CASE -["Land, Edge Cases"] -"Krosan Verge" = ["NormalLayout", "Land", "GW", "Land", "False", "False"] -"Murmuring Bosk" = ["NormalLayout", "Land", "Gold", "G", "False", "False"] -"Dryad Arbor" = ["NormalLayout", "Land", "G", "G", "False", "False"] diff --git a/src/commands/test/utility.py b/src/helpers/miscellaneous.py similarity index 86% rename from src/commands/test/utility.py rename to src/helpers/miscellaneous.py index e348d112..fc8e77e7 100644 --- a/src/commands/test/utility.py +++ b/src/helpers/miscellaneous.py @@ -1,6 +1,5 @@ """ -* General Testing Utility -* For contributors and plugin development. +* Miscellaneous utils that are awaiting refinement/relocation. """ import csv @@ -9,11 +8,15 @@ import warnings import xml.etree.ElementTree as ET from _ctypes import COMError +from collections.abc import Iterable, Sequence from contextlib import suppress +from os import PathLike +from typing import Any from xml.dom import minidom from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api.enumerations import DialogModes, ElementPlacement, LayerKind from psd_tools import PSDImage @@ -21,9 +24,9 @@ from psd_tools.psd.image_resources import ImageResource import src.helpers as psd -from src import APP, TEMPLATES +from src import APP +from src._loader import AppTemplate from src.schema.colors import ColorObject -from src.utils.adobe import LayerContainer # Reference Box colors ORANGE = [255, 172, 64] @@ -60,10 +63,11 @@ def test_new_color( groups.remove(r) for g in groups: # Enable new color - psd.getLayer(new, g).visible = True + if new_layer := psd.getLayer(new, g): + new_layer.visible = True # Disable old color - if old: - psd.getLayer(old, g).visible = False + if old and (old_layer := psd.getLayer(old, g)): + old_layer.visible = False def make_duals( @@ -84,30 +88,32 @@ def make_duals( # Loop through each dual for dual in duals: - # Change layer visibility - top = psd.getLayer(dual[0], group).duplicate(ref, ElementPlacement.PlaceBefore) - bottom = psd.getLayer(dual[1], group).duplicate( - top, ElementPlacement.PlaceAfter - ) - top.visible = True - bottom.visible = True + if (top := psd.getLayer(dual[0], group)) and ( + bottom := psd.getLayer(dual[1], group) + ): + top = top.duplicate(ref, ElementPlacement.PlaceBefore) + bottom = bottom.duplicate(top, ElementPlacement.PlaceAfter) + + # Change layer visibility + top.visible = True + bottom.visible = True - # Enable masks - if mask_top: - psd.copy_layer_mask(mask_top, top) - if mask_bottom: - psd.copy_layer_mask(mask_bottom, bottom) + # Enable masks + if mask_top: + psd.copy_layer_mask(mask_top, top) + if mask_bottom: + psd.copy_layer_mask(mask_bottom, bottom) - # Merge the layers and rename - new_layer = psd.merge_layers([top, bottom]) - new_layer.name = dual + # Merge the layers and rename + new_layer = psd.merge_layers([top, bottom]) + new_layer.name = dual def create_blended_layer( colors: str | list[str], group: LayerSet, - masks: None | ArtLayer | list[ArtLayer] = None, -): + masks: Sequence[ArtLayer] | None = None, +) -> None: """Create a multicolor layer using a gradient mask. Args: @@ -115,26 +121,22 @@ def create_blended_layer( group: Group to look for the color layers within. masks: Layers containing a gradient mask. """ - if not masks: - # No mask provided - masks = [psd.getLayer("Mask")] - elif isinstance(masks, ArtLayer): - # Single layer provided - masks = [masks] layers: list[ArtLayer] = [] # Enable each layer color for i, color in enumerate(colors): - layer = psd.getLayer(color, group) - layer.visible = True + if layer := psd.getLayer(color, group): + layer.visible = True - # Position the new layer and add a mask to previous, if previous layer exists - if layers: - layer.move(layers[i - 1], ElementPlacement.PlaceAfter) - psd.copy_layer_mask(masks[i - 1], layers[i - 1]) + # Position the new layer and add a mask to previous, if previous layer exists + if layers: + prev_idx = i - 1 + layer.move(layers[prev_idx], ElementPlacement.PlaceAfter) + if masks and prev_idx < len(masks): + psd.copy_layer_mask(masks[i - 1], layers[i - 1]) - # Add to the layer list - layers.append(layer) + # Add to the layer list + layers.append(layer) """ @@ -142,7 +144,7 @@ def create_blended_layer( """ -def check_if_needed(key, keys_stored): +def check_if_needed(key: str, keys_stored: Iterable[str]): """Check if double key has been locked, skip int keys if it is. Args: @@ -158,7 +160,7 @@ def check_if_needed(key, keys_stored): return True -def try_all_getters(desc: ActionDescriptor, type_id) -> dict: +def try_all_getters(desc: ActionDescriptor, type_id: int) -> dict[str, Any]: """Try all possible getter functions for this Descriptor and Type ID. Args: @@ -168,7 +170,7 @@ def try_all_getters(desc: ActionDescriptor, type_id) -> dict: Returns: All values returned from our getters. """ - values = {} + values: dict[str, Any] = {} getters = { "bool": "getBoolean", "class": "getClass", @@ -202,7 +204,7 @@ def try_all_getters(desc: ActionDescriptor, type_id) -> dict: return values -def get_action_items(desc) -> dict: +def get_action_items(desc: ActionDescriptor) -> dict[str, Any]: """Try to pull objects and getters from each action key in a descriptor. Args: @@ -211,7 +213,7 @@ def get_action_items(desc) -> dict: Returns: Recursive dict of all objects and getters matched to each key. """ - items = {} + items: dict[str, Any] = {} try: count = desc.count except COMError: @@ -234,7 +236,7 @@ def get_action_items(desc) -> dict: return items -def dump_layer_action_descriptors(layer: ArtLayer, path: str) -> dict: +def dump_layer_action_descriptors(layer: ArtLayer, path: str) -> dict[str, Any]: """Combs through all available descriptor keys for a layer, dumps it to JSOn, and returns as a dict. Args: @@ -258,30 +260,6 @@ def dump_layer_action_descriptors(layer: ArtLayer, path: str) -> dict: return actions -""" -* Dict Utilities -""" - - -def get_differing_dict(d1: dict, d2: dict): - """Recursively generates a new dictionary comprised of differing values in two dicts. - - Args: - d1: First dictionary. - d2: Second dictionary. - - Returns: - A dictionary comprised of differing key value pairs. - """ - new_dict = {} - for key, val in d1.items(): - if isinstance(val, dict): - new_dict[key] = get_differing_dict(d1[key], d2[key]) - elif val != d2[key]: - new_dict[key] = [val, d2[key]] - return new_dict - - """ * Text Utilities """ @@ -307,7 +285,7 @@ def apply_single_line_composer(layer: ArtLayer) -> None: ) -def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str | None = " "): +def combine_text_items(from_layer: ArtLayer, to_layer: ArtLayer, sep: str = " "): """Append the "from_layer" contents to the end of "to_layer" contents with optional separator. Preserves the complete style range formatting of both text contents. @@ -474,7 +452,12 @@ def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: layer.remove() docref.activeLayer.visible = False - return docref.activeLayer + + created_layer = docref.activeLayer + if not isinstance(created_layer, ArtLayer): + raise ValueError("Created color shape layer isn't the active layer as expected") + + return created_layer """ @@ -482,34 +465,38 @@ def create_color_shape(layer: ArtLayer, color: ColorObject) -> ArtLayer: """ -def log_all_template_fonts() -> dict: +def log_all_template_fonts(templates: Iterable[AppTemplate]) -> dict[str, list[str]]: """Create a log of every font found for each PSD template.""" # Ignore warnings from the psd_tools module logging.getLogger("psd_tools").setLevel(logging.FATAL) warnings.filterwarnings("ignore", module="psd_tools") - def _get_fonts_from_psd(doc_path: str) -> set[str]: + def _get_fonts_from_psd(doc_path: str | PathLike[str]) -> set[str]: """ Get a set of every font found in a given Photoshop document. @param doc_path: Path to the Photoshop document. @return: Set of font names found in the document. """ - file, fonts = PSDImage.open(doc_path), set() + file = PSDImage.open(doc_path) # pyright: ignore[reportUnknownMemberType] + fonts: set[str] = set() for layer in [n for n in file.descendants() if n.kind == "type"]: - for style in layer.engine_dict["StyleRun"]["RunArray"]: - font_key = style["StyleSheet"]["StyleSheetData"]["Font"] - fonts.add(layer.resource_dict["FontSet"][font_key]["Name"]) + for style in layer.engine_dict["StyleRun"]["RunArray"]: # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType, reportAttributeAccessIssue] + font_key = style["StyleSheet"]["StyleSheetData"]["Font"] # pyright: ignore[reportUnknownVariableType] + fonts.add(layer.resource_dict["FontSet"][font_key]["Name"]) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType, reportAttributeAccessIssue] return fonts # PSD documents to test docs = { t.path_psd: f"{t.name} ({t.plugin.name if t.plugin else 'BASE'})" - for t in TEMPLATES + for t in templates } # Track progress - doc_fonts, master, current, total = {}, {}, 1, len(docs) + doc_fonts: dict[str, set[str]] = {} + master: dict[str, list[str]] = {} + current = 1 + total = len(docs) # Check each document logging.basicConfig(level=logging.INFO) @@ -527,9 +514,7 @@ def _get_fonts_from_psd(doc_path: str) -> set[str]: master.setdefault(str(f), []).append(doc) # Log a sorted master list - master: dict[str, list] = { - k: v for k, v in sorted(master.items(), key=lambda item: len(item[1])) - } + master = {k: v for k, v in sorted(master.items(), key=lambda item: len(item[1]))} with open("logs/FONTS.json", "w", encoding="utf-8") as f: json.dump(master, f, indent=2) return master @@ -560,7 +545,7 @@ def insert_data_set_variables( warnings.filterwarnings("ignore", module="psd_tools") # Open the PSD file - f = PSDImage.open(from_path) + f = PSDImage.open(from_path) # pyright: ignore[reportUnknownMemberType] # Replace the image variables data new_resource = ImageResource( @@ -572,7 +557,7 @@ def insert_data_set_variables( f.image_resources[Resource.IMAGE_READY_VARIABLES] = new_resource # Save the PSD file - f.save(to_path) + f.save(to_path) # pyright: ignore[reportUnknownMemberType] def print_data_set_variables(path: str) -> None: @@ -581,7 +566,7 @@ def print_data_set_variables(path: str) -> None: Args: path: Path to a PSD document. """ - data = PSDImage.open(path).image_resources.get_data(Resource.IMAGE_READY_DATA_SETS) + data = PSDImage.open(path).image_resources.get_data(Resource.IMAGE_READY_DATA_SETS) # pyright: ignore[reportUnknownMemberType] pretty_xml = minidom.parseString(data).toprettyxml() # Print without excess newlines @@ -601,7 +586,7 @@ def format_data_set_variable_name(text: str) -> str: def get_data_set_variables( - group: LayerContainer | None = None, tree: str | None = None + group: LayerSet | Document | None = None, tree: str | None = None ) -> list[dict[str, str]]: """Get data set variables for all ArtLayer and LayerSet objects in document or LayerSet. @@ -745,12 +730,3 @@ def apply_data_set(data_set_name: str) -> None: APP.instance.executeAction( APP.instance.sID("apply"), desc, DialogModes.DisplayNoDialogs ) - - -""" -* Testing Stuff -""" - -if __name__ == "__main__": - """Insert any test actions here.""" - pass diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/test_scryfall.py b/tests/api/test_scryfall.py new file mode 100644 index 00000000..0ec0abbc --- /dev/null +++ b/tests/api/test_scryfall.py @@ -0,0 +1,65 @@ +from itertools import batched + +from pytest import fixture + +from src._state import PATH +from src.cards import card_details_to_scryfall_identifier, parse_card_info +from src.utils.scryfall import ( + get_card_search, + get_card_unique, + get_cards_collection, + get_set, +) +from src.utils.tests import get_template_render_test_cases + + +class TestScryfall: + @fixture(autouse=True) + def setup(self) -> None: + self.test_cards_by_layout = get_template_render_test_cases() + self.test_card_filenames: list[str] = [] + for test_cases in self.test_cards_by_layout.values(): + self.test_card_filenames += test_cases.keys() + self.test_cards = [ + parse_card_info(PATH.SRC_IMG_TEST, name) + for name in self.test_card_filenames + ] + + def test_scryfall_unique(self) -> None: + """Test the request function for Scryfall's '/cards/set/num' endpoint.""" + card_set = "tsr" + card_number = "50" + card_lang = "en" + card = get_card_unique( + card_set=card_set, card_number=card_number, lang=card_lang + ) + assert card.set == card_set + assert card.collector_number == card_number + assert card.lang == card_lang + + def test_scryfall_search(self) -> None: + """Test the request function for Scryfall's '/cards/search' endpoint.""" + card_name = "Damnation" + card_set = "tsr" + card_lang = "en" + card = get_card_search(card_name=card_name, card_set=card_set, lang=card_lang) + assert card.name == card_name + assert card.set == card_set + assert card.lang == card_lang + + def test_cards_collection(self) -> None: + """Test the request function for Scryfall's '/cards/collection' endpoint.""" + identifiers = [ + card_details_to_scryfall_identifier(card) for card in self.test_cards + ] + for batch in batched(identifiers, 75): + result = get_cards_collection(batch) + assert result + assert not result.not_found + + def test_scryfall_set(self): + """Test the request function for Scryfall's '/sets/code' endpoint.""" + card_set = "tsr" + scry_set = get_set(card_set=card_set) + assert scry_set + assert scry_set.code == card_set diff --git a/tests/cards/__init__.py b/tests/cards/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/cards/test_layouts.py b/tests/cards/test_layouts.py new file mode 100644 index 00000000..f9f6ed7d --- /dev/null +++ b/tests/cards/test_layouts.py @@ -0,0 +1,98 @@ +from collections.abc import Iterable +from concurrent.futures import Future, ThreadPoolExecutor, as_completed +from itertools import batched +from multiprocessing import cpu_count +from pathlib import Path + +from pydantic import RootModel +from pytest import fixture + +from src import CFG +from src.cards import ( + CardDetails, + card_details_to_scryfall_identifier, + parse_card_info, + process_card_data, +) +from src.layouts import NormalLayout, layout_map +from src.utils.data_structures import parse_model +from src.utils.scryfall import CardIdentifier, ScryfallCard, get_cards_collection +from tests.utils import TestDataPaths + +LayoutTestCases = RootModel[dict[str, dict[str, tuple[str, str, str, str, bool, bool]]]] + + +class TestLayouts: + @fixture(autouse=True) + def setup(self) -> None: + test_data = parse_model(TestDataPaths.LAYOUT_TEST_DATA, LayoutTestCases).root + self.layout_test_cases: list[ + tuple[str, tuple[str, str, str, str, bool, bool]] + ] = [] + for cases in test_data.values(): + for card_name, data in cases.items(): + self.layout_test_cases.append((card_name, data)) + + def test_layout_assignments(self) -> None: + with ThreadPoolExecutor(max_workers=min(4, cpu_count())) as executor: + tasks: list[Future[None]] = [] + + for batch in batched(self.layout_test_cases, 75): + tasks.append(executor.submit(_test_batch_of_layout_test_cards, batch)) + + for _ in as_completed(tasks): + pass + + +def _test_batch_of_layout_test_cards( + cases: Iterable[tuple[str, tuple[str, str, str, str, bool, bool]]], +) -> None: + card_details = [parse_card_info(Path(name)) for name, _ in cases] + identifiers: list[CardIdentifier] = [ + card_details_to_scryfall_identifier(card) for card in card_details + ] + scryfall_cards = get_cards_collection(identifiers) + assert scryfall_cards + assert not scryfall_cards.not_found + assert len(identifiers) == len(scryfall_cards.data) + for card, scryfall_card, (_, expected_result) in zip( + card_details, scryfall_cards.data, cases, strict=True + ): + _test_case(card, scryfall_card, expected_result) + + +def _test_case( + card_details: CardDetails, + scryfall_card: ScryfallCard, + expected_result: tuple[str, str, str, str, bool, bool], +) -> None: + scryfall_card = process_card_data(scryfall_card, card_details) + + result = _format_result( + layout_map[scryfall_card.layout]( + scryfall=scryfall_card, + file=card_details, + config=CFG, + ) + ) + + assert expected_result == result + + +def _format_result(layout: NormalLayout) -> tuple[str, str, str, str, bool, bool]: + """Format layout test result for comparison. + + Args: + layout: Test result card layout data. + + Returns: + Formatted frame logic test result data. + """ + return ( + layout.__class__.__name__, + layout.background, + layout.pinlines, + layout.twins, + layout.is_nyx, + layout.is_colorless, + ) diff --git a/tests/cards/test_text.py b/tests/cards/test_text.py new file mode 100644 index 00000000..81950da6 --- /dev/null +++ b/tests/cards/test_text.py @@ -0,0 +1,30 @@ +from typing import TypedDict + +from pydantic import RootModel + +from src.cards import generate_italics +from src.utils.data_structures import parse_model +from tests.utils import TestDataPaths + + +class TestCaseTextItalic(TypedDict): + result: list[str] + scenario: str + text: str + + +ItalicTestCasesModel = RootModel[dict[str, TestCaseTextItalic]] + + +class TestText: + def test_italicization(self) -> None: + test_cases = parse_model( + TestDataPaths.ITALIC_TEST_DATA, ItalicTestCasesModel + ).root + + for case in test_cases.values(): + result_actual, result_expected = ( + generate_italics(case["text"]), + case["result"], + ) + assert sorted(result_actual) == sorted(result_expected) diff --git a/src/commands/test/compression.py b/tests/compression.py similarity index 85% rename from src/commands/test/compression.py rename to tests/compression.py index fddbed7c..fdf8a18b 100644 --- a/src/commands/test/compression.py +++ b/tests/compression.py @@ -1,10 +1,7 @@ -""" -* Tests: Compression -""" - from logging import getLogger from pathlib import Path from time import perf_counter +from typing import TypedDict import matplotlib.pyplot as plt from omnitils.files.archive import DictionarySize, WordSize, compress_7z @@ -13,12 +10,13 @@ _logger = getLogger(__name__) -""" -* Test Funcs -""" + +class CompressionTestResult(TypedDict): + time: float + size: float -def test_7z_compression(path: Path) -> dict: +def test_7z_compression(path: Path) -> dict[str, CompressionTestResult]: """Test all compression settings for a given file and generates a plot of time and compression efficiency. @@ -55,12 +53,15 @@ def test_7z_compression(path: Path) -> dict: # For each Word and Dictionary size run a test total, current = len(word_sizes) * len(dict_sizes), 0 - x, y, z = [], [], [] + x: list[float] = [] + y: list[float] = [] + z: list[str] = [] for ws in word_sizes: for ds in dict_sizes: current += 1 s = perf_counter() path_out = compress_7z(path, word_size=ws, dict_size=ds) + assert path_out size = path_out.stat().st_size time = round(perf_counter() - s, 3) x.append(time) @@ -70,7 +71,7 @@ def test_7z_compression(path: Path) -> dict: # Format a 600 DPI plot at 12" x 8" plt.rcParams["lines.markersize"] = 5 - fig, ax = plt.subplots() + fig, ax = plt.subplots() # pyright: ignore[reportUnknownMemberType] fig.set_dpi(600) fig.set_size_inches(12, 8) @@ -79,16 +80,16 @@ def test_7z_compression(path: Path) -> dict: ax.invert_yaxis() # Set axis labels and plot our points - ax.set_xlabel("Seconds") - ax.set_ylabel("Megabytes") - ax.scatter(x, y, s=1) + ax.set_xlabel("Seconds") # pyright: ignore[reportUnknownMemberType] + ax.set_ylabel("Megabytes") # pyright: ignore[reportUnknownMemberType] + ax.scatter(x, y, s=1) # pyright: ignore[reportUnknownMemberType] # Annotate each point with its label (z) for i, txt in enumerate(z): - ax.annotate(txt, (x[i], y[i]), rotation=45, fontsize=5) + ax.annotate(txt, (x[i], y[i]), rotation=45, fontsize=5) # pyright: ignore[reportUnknownMemberType] # Show the plot and return raw data - plt.show() + plt.show() # pyright: ignore[reportUnknownMemberType] return {name: {"time": x[i], "size": y[i]} for i, name in enumerate(z)} diff --git a/tests/data/layout_data.toml b/tests/data/layout_data.toml new file mode 100644 index 00000000..e0e8db28 --- /dev/null +++ b/tests/data/layout_data.toml @@ -0,0 +1,295 @@ +# TEST CASES: Layout assignment + +# * TEST CASE +["Mono Color, Normal Frame"] +"Healing Salve" = ["NormalLayout", "W", "W", "W", false, false] +"Ancestral Recall" = ["NormalLayout", "U", "U", "U", false, false] +"Dark Ritual" = ["NormalLayout", "B", "B", "B", false, false] +"Lightning Bolt" = ["NormalLayout", "R", "R", "R", false, false] +"Giant Growth" = ["NormalLayout", "G", "G", "G", false, false] + +# * TEST CASE +["Mono Color, 2/C in Mana Cost"] +"Spectral Procession" = ["NormalLayout", "W", "W", "W", false, false] +"Advice from the Fae" = ["NormalLayout", "U", "U", "U", false, false] +"Beseech the Queen" = ["NormalLayout", "B", "B", "B", false, false] +"Flame Javelin" = ["NormalLayout", "R", "R", "R", false, false] +"Tower Above" = ["NormalLayout", "G", "G", "G", false, false] + +# * TEST CASE +["Mono Color, 0 Cost Pacts"] +"Intervention Pact" = ["NormalLayout", "W", "W", "W", false, false] +"Pact of Negation" = ["NormalLayout", "U", "U", "U", false, false] +"Slaughter Pact" = ["NormalLayout", "B", "B", "B", false, false] +"Pact of the Titan" = ["NormalLayout", "R", "R", "R", false, false] +"Summoner's Pact" = ["NormalLayout", "G", "G", "G", false, false] + +# * TEST CASE +["Nyxtouched Enchantment Creatures"] +"Heliod, God of the Sun [THS]" = ["NormalLayout", "W", "W", "W", true, false] +"Thassa, God of the Sea" = ["NormalLayout", "U", "U", "U", true, false] +"Erebos, God of the Dead" = ["NormalLayout", "B", "B", "B", true, false] +"Purphoros, God of the Forge [THS]" = ["NormalLayout", "R", "R", "R", true, false] +"Nylea, God of the Hunt" = ["NormalLayout", "G", "G", "G", true, false] + +# * TEST CASE +["No Mana Cost, Suspend"] +"Restore Balance" = ["NormalLayout", "W", "W", "W", false, false] +"Ancestral Vision" = ["NormalLayout", "U", "U", "U", false, false] +"Living End" = ["NormalLayout", "B", "B", "B", false, false] +"Wheel of Fate" = ["NormalLayout", "R", "R", "R", false, false] +"Hypergenesis" = ["NormalLayout", "G", "G", "G", false, false] +"Lotus Bloom" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] + +# * TEST CASE +["Dual Color, Normal Frame"] +"Azorius Charm" = ["NormalLayout", "Gold", "WU", "Gold", false, false] +"Dimir Charm" = ["NormalLayout", "Gold", "UB", "Gold", false, false] +"Rakdos Charm" = ["NormalLayout", "Gold", "BR", "Gold", false, false] +"Gruul Charm" = ["NormalLayout", "Gold", "RG", "Gold", false, false] +"Selesnya Charm" = ["NormalLayout", "Gold", "GW", "Gold", false, false] +"Orzhov Charm" = ["NormalLayout", "Gold", "WB", "Gold", false, false] +"Golgari Charm" = ["NormalLayout", "Gold", "BG", "Gold", false, false] +"Simic Charm" = ["NormalLayout", "Gold", "GU", "Gold", false, false] +"Izzet Charm" = ["NormalLayout", "Gold", "UR", "Gold", false, false] +"Boros Charm" = ["NormalLayout", "Gold", "RW", "Gold", false, false] + +# * TEST CASE +["Dual Color, Hybrid Frame"] +"Godhead of Awe" = ["NormalLayout", "WU", "WU", "Land", false, false] +"Ghastlord of Fugue" = ["NormalLayout", "UB", "UB", "Land", false, false] +"Demigod of Revenge" = ["NormalLayout", "BR", "BR", "Land", false, false] +"Deus of Calamity" = ["NormalLayout", "RG", "RG", "Land", false, false] +"Oversoul of Dusk" = ["NormalLayout", "GW", "GW", "Land", false, false] +"Divinity of Pride" = ["NormalLayout", "WB", "WB", "Land", false, false] +"Deity of Scars" = ["NormalLayout", "BG", "BG", "Land", false, false] +"Overbeing of Myth" = ["NormalLayout", "GU", "GU", "Land", false, false] +"Dominus of Fealty" = ["NormalLayout", "UR", "UR", "Land", false, false] +"Nobilis of War" = ["NormalLayout", "RW", "RW", "Land", false, false] + +# * TEST CASE +["No Mana Cost, Hybrid Frame"] +"Asmoranomardicadaistinaculdacar" = ["NormalLayout", "BR", "BR", "Land", false, false] + +# * TEST CASE +["Dual Color, Gold Frame, Hybrid Mana"] +"Maelstrom Muse" = ["NormalLayout", "Gold", "UR", "Gold", false, false] +"Ajani, Sleeper Agent" = ["PlaneswalkerLayout", "Gold", "GW", "Gold", false, false] +"Tamiyo, Compleated Sage" = ["PlaneswalkerLayout", "Gold", "GU", "Gold", false, false] + +# * TEST CASE +["Tri Color, Gold Frame, Hybrid Mana"] +"Messenger Falcons" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] + +# * TEST CASE +["Double Faced Cards"] +"Insectile Aberration" = ["TransformLayout", "U", "U", "U", false, false] +"Ravager of the Fells" = ["TransformLayout", "Gold", "RG", "Gold", false, false] +"Brisela, Voice of Nightmares" = ["TransformLayout", "Colorless", "Colorless", "Colorless", false, true] +"Urza, Planeswalker" = ["PlaneswalkerTransformLayout", "Gold", "WU", "Gold", false, false] +"Archangel Avacyn" = ["TransformLayout", "W", "W", "W", false, false] +"Avacyn, the Purifier" = ["TransformLayout", "R", "R", "R", false, false] +"Curious Homunculus" = ["TransformLayout", "U", "U", "U", false, false] +"Voracious Reader" = ["TransformLayout", "Colorless", "Colorless", "Colorless", false, true] +"Barkchannel Pathway" = ["ModalDoubleFacedLayout", "Land", "G", "G", false, false] +"Tidechannel Pathway" = ["ModalDoubleFacedLayout", "Land", "U", "U", false, false] +"Blex, Vexing Pest" = ["ModalDoubleFacedLayout", "G", "G", "G", false, false] +"Search for Blex" = ["ModalDoubleFacedLayout", "B", "B", "B", false, false] +"Extus, Oriq Overlord" = ["ModalDoubleFacedLayout", "Gold", "WB", "Gold", false, false] +"Awaken the Blood Avatar" = ["ModalDoubleFacedLayout", "Gold", "BR", "Gold", false, false] + +# * TEST CASE +["Tri Color, Normal Frame"] +"Esper Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Grixis Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Jund Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Naya Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Bant Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Abzan Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Jeskai Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Sultai Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Mardu Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] +"Temur Charm" = ["NormalLayout", "Gold", "Gold", "Gold", false, false] + +# * TEST CASE +["Colorless, Non-Artifact"] +"Emrakul, the Aeons Torn" = ["NormalLayout", "Colorless", "Colorless", "Colorless", false, true] +"Scion of Ugin" = ["NormalLayout", "Colorless", "Colorless", "Colorless", false, true] + +# * TEST CASE +["Colorless, Artifact"] +"Herald's Horn" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Black Lotus" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Mox Pearl" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Mox Sapphire" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Mox Jet" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Mox Ruby" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] +"Mox Emerald" = ["NormalLayout", "Artifact", "Artifact", "Artifact", false, false] + +# * TEST CASE +["Mono Color, Artifact"] +"The Circle of Loyalty" = ["NormalLayout", "Artifact", "W", "W", false, false] +"The Magic Mirror" = ["NormalLayout", "Artifact", "U", "U", false, false] +"The Cauldron of Eternity" = ["NormalLayout", "Artifact", "B", "B", false, false] +"Embercleave" = ["NormalLayout", "Artifact", "R", "R", false, false] +"The Great Henge" = ["NormalLayout", "Artifact", "G", "G", false, false] + +# * TEST CASE +["Dual Color, Artifact"] +"Filigree Angel" = ["NormalLayout", "Artifact", "WU", "Gold", false, false] +"Time Sieve" = ["NormalLayout", "Artifact", "UB", "Gold", false, false] +"Demonspine Whip" = ["NormalLayout", "Artifact", "BR", "Gold", false, false] +"Mage Slayer" = ["NormalLayout", "Artifact", "RG", "Gold", false, false] +"Behemoth Sledge" = ["NormalLayout", "Artifact", "GW", "Gold", false, false] +"Tainted Sigil" = ["NormalLayout", "Artifact", "WB", "Gold", false, false] +"Shardless Agent" = ["NormalLayout", "Artifact", "GU", "Gold", false, false] +"Etherium-Horn Sorcerer" = ["NormalLayout", "Artifact", "UR", "Gold", false, false] + +# * TEST CASE +["Tri Color, Artifact"] +"Sphinx of the Steel Wind" = ["NormalLayout", "Artifact", "Gold", "Gold", false, false] +"Thopter Foundry" = ["NormalLayout", "Artifact", "Gold", "Gold", false, false] + +# * TEST CASE +["WUBRG, Artifact"] +"Sphinx of the Guildpact" = ["NormalLayout", "Artifact", "Gold", "Gold", false, false] +"Reaper King" = ["NormalLayout", "Artifact", "Gold", "Gold", false, false] + +# * TEST CASE +["Non-Colored, Utility Land"] +"Vesuva" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Evolving Wilds" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Karn's Bastion" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Hall of Heliod's Generosity" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Academy Ruins" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Volrath's Stronghold" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Gemstone Caverns" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Glacial Chasm" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Ash Barrens" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Crumbling Vestige" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Blighted Steppe" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Blighted Cataract" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Blighted Fen" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Blighted Gorge" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Blighted Woodland" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Maze's End" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Inventors' Fair" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Myriad Landscape" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Crystal Quarry" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Demolition Field" = ["NormalLayout", "Land", "Land", "Land", false, false] + +# * TEST CASE +["Non-Colored, Panorama"] +"Esper Panorama" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Grixis Panorama" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Jund Panorama" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Naya Panorama" = ["NormalLayout", "Land", "Land", "Land", false, false] +"Bant Panorama" = ["NormalLayout", "Land", "Land", "Land", false, false] + +# * TEST CASE +["Mono Color, Land, Add {C}"] +"Castle Ardenvale" = ["NormalLayout", "Land", "W", "W", false, false] +"Castle Vantress" = ["NormalLayout", "Land", "U", "U", false, false] +"Castle Locthwain" = ["NormalLayout", "Land", "B", "B", false, false] +"Castle Embereth" = ["NormalLayout", "Land", "R", "R", false, false] +"Castle Garenbrig" = ["NormalLayout", "Land", "G", "G", false, false] +"Serra's Sanctum" = ["NormalLayout", "Land", "W", "W", false, false] +"Tolarian Academy" = ["NormalLayout", "Land", "U", "U", false, false] +"Cabal Coffers" = ["NormalLayout", "Land", "B", "B", false, false] +"Gaea's Cradle" = ["NormalLayout", "Land", "G", "G", false, false] + +# * TEST CASE +["Mono Color, Land, Basic Land Type"] +"Idyllic Grange" = ["NormalLayout", "Land", "W", "W", false, false] +"Mystic Sanctuary" = ["NormalLayout", "Land", "U", "U", false, false] +"Witch's Cottage" = ["NormalLayout", "Land", "B", "B", false, false] +"Dwarven Mine" = ["NormalLayout", "Land", "R", "R", false, false] +"Gingerbread Cabin" = ["NormalLayout", "Land", "G", "G", false, false] + +# * TEST CASE +["Mono Color, Land, Multicolor Activated Ability"] +"Axgard Armory" = ["NormalLayout", "Land", "W", "W", false, false] +"Surtland Frostpyre" = ["NormalLayout", "Land", "R", "R", false, false] +"Skemfar Elderhall" = ["NormalLayout", "Land", "G", "G", false, false] + +# * TEST CASE +["Mono Color, Land, All Lands are X"] +"Urborg, Tomb of Yawgmoth" = ["NormalLayout", "Land", "B", "B", false, false] +"Yavimaya, Cradle of Growth" = ["NormalLayout", "Land", "G", "G", false, false] + +# * TEST CASE +["Mono Color, Vivid Land"] +"Vivid Meadow" = ["NormalLayout", "Land", "W", "W", false, false] +"Vivid Creek" = ["NormalLayout", "Land", "U", "U", false, false] +"Vivid Marsh" = ["NormalLayout", "Land", "B", "B", false, false] +"Vivid Crag" = ["NormalLayout", "Land", "R", "R", false, false] +"Vivid Grove" = ["NormalLayout", "Land", "G", "G", false, false] + +# * TEST CASE +["Dual Color, Land, Add {C} or {C}"] +"Celestial Colonnade" = ["NormalLayout", "Land", "WU", "Land", false, false] +"Creeping Tar Pit" = ["NormalLayout", "Land", "UB", "Land", false, false] +"Lavaclaw Reaches" = ["NormalLayout", "Land", "BR", "Land", false, false] +"Raging Ravine" = ["NormalLayout", "Land", "RG", "Land", false, false] +"Stirring Wildwood" = ["NormalLayout", "Land", "GW", "Land", false, false] +"Shambling Vent" = ["NormalLayout", "Land", "WB", "Land", false, false] +"Hissing Quagmire" = ["NormalLayout", "Land", "BG", "Land", false, false] +"Lumbering Falls" = ["NormalLayout", "Land", "GU", "Land", false, false] +"Wandering Fumarole" = ["NormalLayout", "Land", "UR", "Land", false, false] +"Needle Spires" = ["NormalLayout", "Land", "RW", "Land", false, false] + +# * TEST CASE +["Dual Color, Land, Basic Land Types"] +"Hallowed Fountain" = ["NormalLayout", "Land", "WU", "Land", false, false] +"Watery Grave" = ["NormalLayout", "Land", "UB", "Land", false, false] +"Blood Crypt" = ["NormalLayout", "Land", "BR", "Land", false, false] +"Stomping Ground" = ["NormalLayout", "Land", "RG", "Land", false, false] +"Temple Garden" = ["NormalLayout", "Land", "GW", "Land", false, false] +"Godless Shrine" = ["NormalLayout", "Land", "WB", "Land", false, false] +"Overgrown Tomb" = ["NormalLayout", "Land", "BG", "Land", false, false] +"Breeding Pool" = ["NormalLayout", "Land", "GU", "Land", false, false] +"Steam Vents" = ["NormalLayout", "Land", "UR", "Land", false, false] +"Sacred Foundry" = ["NormalLayout", "Land", "RW", "Land", false, false] + +# * TEST CASE +["Dual Color, Fetch Land"] +"Flooded Strand" = ["NormalLayout", "Land", "WU", "Land", false, false] +"Polluted Delta" = ["NormalLayout", "Land", "UB", "Land", false, false] +"Bloodstained Mire" = ["NormalLayout", "Land", "BR", "Land", false, false] +"Wooded Foothills" = ["NormalLayout", "Land", "RG", "Land", false, false] +"Windswept Heath" = ["NormalLayout", "Land", "GW", "Land", false, false] +"Marsh Flats" = ["NormalLayout", "Land", "WB", "Land", false, false] +"Verdant Catacombs" = ["NormalLayout", "Land", "BG", "Land", false, false] +"Misty Rainforest" = ["NormalLayout", "Land", "GU", "Land", false, false] +"Scalding Tarn" = ["NormalLayout", "Land", "UR", "Land", false, false] +"Arid Mesa" = ["NormalLayout", "Land", "RW", "Land", false, false] + +# * TEST CASE +["Tri Color, Land"] +"Arcane Sanctum" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Crumbling Necropolis" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Savage Lands" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Jungle Shrine" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Seaside Citadel" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Sandsteppe Citadel" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Mystic Monastery" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Opulent Palace" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Nomad Outpost" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Frontier Bivouac" = ["NormalLayout", "Land", "Gold", "Gold", false, false] + +# * TEST CASE +["Gold Color, Land"] +"Prismatic Vista" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Fabled Passage" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Aether Hub" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"City of Brass" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Mana Confluence" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Ally Encampment" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Command Tower" = ["NormalLayout", "Land", "Gold", "Gold", false, false] +"Thran Portal" = ["NormalLayout", "Land", "Gold", "Gold", false, false] + +# * TEST CASE +["Land, Edge Cases"] +"Krosan Verge" = ["NormalLayout", "Land", "GW", "Land", false, false] +"Murmuring Bosk" = ["NormalLayout", "Land", "Gold", "G", false, false] +"Dryad Arbor" = ["NormalLayout", "Land", "G", "G", false, false] diff --git a/src/data/tests/text_italic.toml b/tests/data/text_italic.toml similarity index 100% rename from src/data/tests/text_italic.toml rename to tests/data/text_italic.toml diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..0002ec42 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,8 @@ +from pathlib import Path + +_data_dir_path = Path(__file__).parent / "data" + + +class TestDataPaths: + LAYOUT_TEST_DATA = _data_dir_path / "layout_data.toml" + ITALIC_TEST_DATA = _data_dir_path / "text_italic.toml" From 0eca6405fbda784509f409df5ea1a395f13e49dd Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 6 May 2026 16:04:22 +0300 Subject: [PATCH 138/190] style(render_spec.py): Code formatting --- src/render_spec.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/render_spec.py b/src/render_spec.py index b9cb1142..a970363b 100644 --- a/src/render_spec.py +++ b/src/render_spec.py @@ -5,14 +5,14 @@ from __future__ import annotations +import glob import os import re -import glob +from dataclasses import dataclass from pathlib import Path from typing import Annotated, TypeVar -from dataclasses import dataclass -from pydantic import BaseModel, RootModel, BeforeValidator +from pydantic import BaseModel, BeforeValidator, RootModel from src.cards import CardDetails, parse_card_info from src.utils.data_structures import parse_model @@ -20,21 +20,21 @@ # region Model-Types -def __ensure_list__[T](values: list[T] | T) -> list[T]: +def _ensure_list[T](values: list[T] | T) -> list[T]: if isinstance(values, list): return values # type: ignore return [values] -def __ensure_str__(values: list[str] | str) -> str: +def _ensure_str(values: list[str] | str) -> str: if isinstance(values, list): return " ".join(values) return values T = TypeVar("T") -EnsuredList = Annotated[list[T], BeforeValidator(__ensure_list__)] -EnsuredStr = Annotated[str, BeforeValidator(__ensure_str__)] +EnsuredList = Annotated[list[T], BeforeValidator(_ensure_list)] +EnsuredStr = Annotated[str, BeforeValidator(_ensure_str)] class ConfigModel(BaseModel): From ec5106cf6e80f9dac99a5d2f658f3e9b2ce40360 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 6 May 2026 16:06:59 +0300 Subject: [PATCH 139/190] fix(update_template): Fix download unpacking logic --- src/_loader.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/_loader.py b/src/_loader.py index f7e7d57c..b931d25a 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -8,9 +8,10 @@ from enum import Enum from functools import cached_property from json import load +from logging import getLogger from pathlib import Path from threading import Lock -from traceback import format_exc, print_exc +from traceback import print_exc from types import ModuleType from typing import Annotated, Any, Literal, NotRequired, Protocol, TypedDict, overload @@ -43,6 +44,8 @@ from src.utils.download import download_cloudfront from src.utils.event import SubscribableEvent +_logger = getLogger(__name__) + # region Types @@ -1307,6 +1310,10 @@ def update_template( Returns: True if succeeded, False if failed. """ + + def get_fail_message() -> str: + return f"Failed to update template: {self.name}" + if self.path_download: try: result: bool = False @@ -1323,20 +1330,19 @@ def update_template( ) ) - if self.url_amazon: - # Google Drive failed or isn't an option, download from Amazon S3 - if not result: - result = download_cloudfront( - url=self.url_amazon, - path=self.path_download, - callback=callback, - ) + # If downloaded file is a 7z archive, extract the template from it + if result and self.path_download.suffix == ".7z": + with SevenZipFile(self.path_7z, "r") as archive: + archive.extractall(self.path_templates) + self.path_7z.unlink(missing_ok=True) - # If downloaded file is a 7z archive, extract the template from it - if result and self.path_download.suffix == ".7z": - with SevenZipFile(self.path_7z, "r") as archive: - archive.extractall(self.path_templates) - self.path_7z.unlink() + if self.url_amazon and not result: + # Google Drive failed or isn't an option, download from Amazon S3 + result = download_cloudfront( + url=self.url_amazon, + path=self.path_download, + callback=callback, + ) if self.google_drive_id and self.update_version: self.versions[self.google_drive_id] = self.update_version @@ -1344,13 +1350,16 @@ def update_template( if will_install: self.template_installed.trigger(None) + if not result: + _logger.error(get_fail_message()) + return result # Exception caught while downloading / unpacking except Exception: - print(f"Failed to update template: {self.name}", format_exc()) + _logger.exception(get_fail_message()) else: - print("Template update failed. Download path isn't specified.") + _logger.error("Template update failed. Download path isn't specified.") return False """ From 1e1ceac41dfa78b1b36815ea121c1014173a5c34 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 6 May 2026 16:55:31 +0300 Subject: [PATCH 140/190] docs(README.md): Document how to run tests --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 275011a7..e65e83cd 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,19 @@ Additionally if you want to do UI development the extensions below will help wit After installing the Qt extensions add your absolute path to `./src/gui` to the `qt-qml.qmlls.additionalImportPaths` setting in VS Code. Without it qmllint won't recognize local Qml imports. At the time of writing the qmllint, provided by the Qt Qml extension, doesn't recognize context defined in Python so warnings within the Qml files are expected. +# 🔬 Tests + +## Template renders + +Predefined template test render cases may be run via the _Tests_ menu in the GUI. The currently chosen template or batch mode configuration determines which templates are tested when choosing to run template specific tests. The _Quick_ tests queue only the first test case for each layout. + +## Automatic tests + +Automatic tests are defined under the `/tests` directory. All of them can run with the command: +```bash +pytest ./tests +``` + # 💾 Download Templates Manually If you wish to download the templates manually, visit [this link](https://drive.google.com/drive/u/1/folders/1sgJ3Xu4FabxNgDl0yeI7OjDZ7fqlI4p3). These archives must be extracted to the `/templates` directory. The archives found within the **Investigamer** and **SilvanMTG** drive folders must be extracted to From a09d97ed88019b54ed560748e9d081f678575b2e Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 6 May 2026 17:59:16 +0300 Subject: [PATCH 141/190] fix(update_hexproof_cache): Fix asset timestamp comparison --- src/utils/hexapi.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/utils/hexapi.py b/src/utils/hexapi.py index 4de9e262..9dfda399 100644 --- a/src/utils/hexapi.py +++ b/src/utils/hexapi.py @@ -5,7 +5,7 @@ from asyncio import gather, to_thread from collections.abc import Awaitable, Callable from contextlib import suppress -from datetime import datetime +from datetime import UTC, datetime from functools import cache from io import BytesIO from logging import getLogger @@ -52,14 +52,7 @@ def hexproof_request_wrapper[T, **P]( fallback: T, ) -> Callable[[Callable[P, T]], Callable[P, T]]: - """Wrapper for a Hexproof.io request function to handle retries, rate limits, and a final exception catch. - - Args: - logr: Logger object to output any exception messages. - - Returns: - Wrapped function. - """ + """Wrapper for a Hexproof.io request function to handle retries, rate limits, and a final exception catch.""" def decorator(func: Callable[P, T]): @return_on_exception(fallback) @@ -136,7 +129,10 @@ async def set_amazon_api_key(): return bool(env.API_GOOGLE or env.API_AMAZON) -@hexproof_request_wrapper({}) +_get_metadata_fallback: dict[str, Meta] = {} + + +@hexproof_request_wrapper(_get_metadata_fallback) def get_metadata() -> dict[str, Meta]: """Return a manifest of all resource metadata. @@ -156,7 +152,10 @@ def get_metadata() -> dict[str, Meta]: ) -@hexproof_request_wrapper({}) +_get_sets_fallback: dict[str, HexproofSet] = {} + + +@hexproof_request_wrapper(_get_sets_fallback) def get_sets() -> dict[str, HexproofSet]: """Retrieve the current 'Set' data manifest from https://api.hexproof.io. @@ -234,8 +233,11 @@ def update_hexproof_cache() -> tuple[bool, str | None]: asset_timestamp = datetime.fromisoformat(chosen_asset.updated_at) if not ( current_symbols_manifest := get_symbols_manifest() - ) or asset_timestamp > datetime.fromisoformat( - current_symbols_manifest.meta.date + ) or asset_timestamp > datetime.fromtimestamp( + datetime.fromisoformat( + current_symbols_manifest.meta.date + ).timestamp(), + tz=UTC, ): symbols_dl_url = chosen_asset.browser_download_url else: From 987db9cc91d385091f98748255f6f91d4733bccf Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 10:39:55 +0300 Subject: [PATCH 142/190] feat(masks.py): Add helper function for deleting layer mask from a solid color fill layer and allow mask creation to take selection into account --- src/helpers/masks.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/helpers/masks.py b/src/helpers/masks.py index 9ebc88ff..40fa4887 100644 --- a/src/helpers/masks.py +++ b/src/helpers/masks.py @@ -3,6 +3,7 @@ """ from _ctypes import COMError +from enum import StrEnum from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer @@ -262,7 +263,16 @@ def enter_rgb_channel(layer: ArtLayer | LayerSet | None = None): ) -def create_mask(layer: ArtLayer | LayerSet | None = None): +class MaskSelectionBehaviour(StrEnum): + REVEAL_ALL = "revealAll" + REVEAL_SELECTION = "revealSelection" + HIDE_SELECTION = "hideSelection" + + +def create_mask( + layer: ArtLayer | LayerSet | None = None, + selection_behaviour: MaskSelectionBehaviour = MaskSelectionBehaviour.REVEAL_ALL, +): """Add a mask to provided or active layer. Args: @@ -282,7 +292,7 @@ def create_mask(layer: ArtLayer | LayerSet | None = None): d1.putEnumerated( APP.instance.sID("using"), APP.instance.sID("userMaskEnabled"), - APP.instance.sID("revealAll"), + APP.instance.sID(selection_behaviour), ) APP.instance.executeAction( APP.instance.sID("make"), d1, DialogModes.DisplayNoDialogs @@ -331,7 +341,7 @@ def delete_mask(layer: ArtLayer | LayerSet | None = None) -> None: """Removes a given layer's mask. Args: - layer: ArtLayer ore LayerSet object, use active layer if not provided. + layer: to delete mask from. Uses active layer if not provided. """ if layer: APP.instance.activeDocument.activeLayer = layer @@ -346,3 +356,24 @@ def delete_mask(layer: ArtLayer | LayerSet | None = None) -> None: APP.instance.executeAction( APP.instance.sID("delete"), desc1, DialogModes.DisplayNoDialogs ) + + +def delete_mask_from_solid_color_layer(layer: ArtLayer | None = None) -> None: + """Removes the layer mask from a given solid color fill layer. + + Args: + layer: to delete mask from. Uses active layer if not provided. + """ + if layer: + APP.instance.activeDocument.activeLayer = layer + desc = ActionDescriptor() + ref = ActionReference() + ref.putEnumerated( + APP.instance.sID("channel"), + APP.instance.sID("channel"), + APP.instance.sID("mask"), + ) + desc.putReference(APP.instance.sID("target"), ref) + APP.instance.executeAction( + APP.instance.sID("delete"), desc, DialogModes.DisplayNoDialogs + ) From 0156f68f472347c36f09f993254ca6853c2cbfd6 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 10:41:33 +0300 Subject: [PATCH 143/190] feat(selection.py): Return Selection object from functions that select layer's pixels --- src/helpers/selection.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/selection.py b/src/helpers/selection.py index 80edbdc6..86127591 100644 --- a/src/helpers/selection.py +++ b/src/helpers/selection.py @@ -93,7 +93,7 @@ def select_canvas(docref: Document | None = None, bleed: int = 0): def select_layer_pixels( layer: ArtLayer | None = None, add_to_selection: bool = False -) -> None: +) -> Selection: """Select pixels of the active layer, or a target layer. Args: @@ -119,11 +119,12 @@ def select_layer_pixels( des1, DialogModes.DisplayNoDialogs, ) + return APP.instance.activeDocument.selection def select_vector_layer_pixels( layer: ArtLayer | None = None, add_to_selection: bool = False -) -> None: +) -> Selection: """Select pixels of the active vector layer, or a target layer. Args: @@ -149,6 +150,7 @@ def select_vector_layer_pixels( desc1, DialogModes.DisplayNoDialogs, ) + return APP.instance.activeDocument.selection """ From 2c4745f2da1e8d759ac51ce68e4b2a4d51c07ee3 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 10:43:07 +0300 Subject: [PATCH 144/190] feat: Add stricter typing to `get_option` BREAKING CHANGE: --- src/_config.py | 66 +++++++++++++++++++++++++++-------------- src/templates/normal.py | 16 +++++++--- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/_config.py b/src/_config.py index 330e15aa..194253de 100644 --- a/src/_config.py +++ b/src/_config.py @@ -2,7 +2,7 @@ * Global Settings Module """ -from enum import StrEnum +from enum import Enum from typing import Literal, overload from src._loader import ConfigHandler, CustomConfigParser @@ -12,7 +12,6 @@ CollectorMode, CollectorPromo, FillMode, - HasDefault, NicknameShorten, OutputFileType, ScryfallSorting, @@ -101,7 +100,7 @@ def update_definitions(self): "APP.FILES", "Overwrite.Duplicate", fallback=True ) self.output_file_type = self.get_option( - "APP.FILES", "Output.File.Type", OutputFileType + "APP.FILES", "Output.File.Type", OutputFileType, default=OutputFileType.JPG ) self.output_file_name = self.file.get( section="APP.FILES", @@ -141,10 +140,13 @@ def update_definitions(self): "BASE.TEXT", "No.Reminder.Text", fallback=False ) self.collector_mode = self.get_option( - "BASE.TEXT", "Collector.Mode", CollectorMode + "BASE.TEXT", "Collector.Mode", CollectorMode, default=CollectorMode.Normal ) self.collector_promo = self.get_option( - "BASE.TEXT", "Collector.Promo", CollectorPromo + "BASE.TEXT", + "Collector.Promo", + CollectorPromo, + default=CollectorPromo.Automatic, ) self.nickname_allow = self.file.getboolean( "BASE.TEXT", "Nickname", fallback=True @@ -156,7 +158,10 @@ def update_definitions(self): "BASE.TEXT", "Nickname.In.Oracle", fallback=True ) self.nickname_shorten_in_oracle_text = self.get_option( - "BASE.TEXT", "Nickname.Shorten.In.Oracle", NicknameShorten + "BASE.TEXT", + "Nickname.Shorten.In.Oracle", + NicknameShorten, + default=NicknameShorten.ALL_BUT_FIRST, ) # BASE - SYMBOLS @@ -175,7 +180,10 @@ def update_definitions(self): # BASE - WATERMARKS self.watermark_mode = self.get_option( - "BASE.WATERMARKS", "Watermark.Mode", WatermarkMode + "BASE.WATERMARKS", + "Watermark.Mode", + WatermarkMode, + default=WatermarkMode.Disabled, ) self.watermark_default = self.file.get( "BASE.WATERMARKS", "Default.Watermark", fallback="WOTC" @@ -217,22 +225,39 @@ def update_definitions(self): "BASE.TEMPLATES", "Import.Scryfall.Scan", fallback=False ) self.border_color = self.get_option( - "BASE.TEMPLATES", "Border.Color", BorderColor + "BASE.TEMPLATES", "Border.Color", BorderColor, default=BorderColor.Black ) """ * Setting Utils """ - def get_option( + @overload + def get_option[T: Enum]( self, section: str, key: str, - enum_class: type[StrEnum], - default: str | None = None, - ) -> str: + enum_class: type[T], + default: T, + ) -> T: ... + + @overload + def get_option[T: Enum]( + self, + section: str, + key: str, + enum_class: type[T], + default: T | None = None, + ) -> T | None: ... + + def get_option[T: Enum]( + self, + section: str, + key: str, + enum_class: type[T], + default: T | None = None, + ) -> T | None: """Returns the current value of an "options" setting if that option exists in its StrEnum class. - Otherwise, returns the default value of that StrEnum class. Args: section: Group (section) to access within the config file. @@ -243,16 +268,13 @@ def get_option( Returns: Validated current value, or default value. """ - defa: str = ( - default or str(enum_class.Default) - if isinstance(enum_class, HasDefault) - else "" - ) if self.file.has_section(section): - option = self.file[section].get(key, fallback=defa) - if option in enum_class: - return option - return defa + option = self.file[section].get(key) + try: + return enum_class(option) + except ValueError: + pass + return default @overload def get_setting( diff --git a/src/templates/normal.py b/src/templates/normal.py index 2b3007cf..4169afb4 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -1517,10 +1517,13 @@ def land_colorshift(self) -> bool: ) @cached_property - def artifact_color_mode(self) -> str: + def artifact_color_mode(self) -> BorderlessColorMode: """Setting determining what elements to color for colored artifacts..""" return self.config.get_option( - section="COLORS", key="Artifact.Color.Mode", enum_class=BorderlessColorMode + section="COLORS", + key="Artifact.Color.Mode", + enum_class=BorderlessColorMode, + default=BorderlessColorMode.Twins_And_PT, ) """ @@ -2425,9 +2428,14 @@ def is_content_aware_enabled(self) -> bool: """ @cached_property - def crown_mode(self) -> str: + def crown_mode(self) -> ModernClassicCrown: """Whether to use pinlines when generating the Legendary Crown.""" - return self.config.get_option("FRAME", "Crown.Mode", ModernClassicCrown) + return self.config.get_option( + "FRAME", + "Crown.Mode", + ModernClassicCrown, + default=ModernClassicCrown.Pinlines, + ) """ * References From f9a6a8b17dfe3b33c928cd55006a5f77dddb5c09 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 12:54:43 +0300 Subject: [PATCH 145/190] test(text_file_name.toml): Move file name test data under tests directory --- {src/data/tests => tests/data}/text_file_name.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src/data/tests => tests/data}/text_file_name.toml (100%) diff --git a/src/data/tests/text_file_name.toml b/tests/data/text_file_name.toml similarity index 100% rename from src/data/tests/text_file_name.toml rename to tests/data/text_file_name.toml From aa34a8d8370c8edb4e4bd7da4f3602886f89c360 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 13:03:21 +0300 Subject: [PATCH 146/190] build: Separate commands from the main app code E.g. the build command isn't useful in a built app and so it makes more sense to not have it in the main app code. The render command doesn't have an equivalent for now as it has to be redone mostly from scratch since the way renders are handled has changed so much. --- main.py | 19 ----- scripts/__init__.py | 0 scripts/build.py | 61 +++++++++++++++ scripts/compress.py | 87 +++++++++++++++++++++ {src/data/build => scripts/data}/dist.yml | 0 scripts/utils/__init__.py | 0 {src => scripts}/utils/build.py | 51 ++++++++++--- src/commands/__init__.py | 67 ----------------- src/commands/build.py | 66 ---------------- src/commands/docs.py | 40 ---------- src/commands/files.py | 92 ----------------------- src/commands/render.py | 67 ----------------- 12 files changed, 189 insertions(+), 361 deletions(-) create mode 100644 scripts/__init__.py create mode 100644 scripts/build.py create mode 100644 scripts/compress.py rename {src/data/build => scripts/data}/dist.yml (100%) create mode 100644 scripts/utils/__init__.py rename {src => scripts}/utils/build.py (89%) delete mode 100644 src/commands/__init__.py delete mode 100644 src/commands/build.py delete mode 100644 src/commands/docs.py delete mode 100644 src/commands/files.py delete mode 100644 src/commands/render.py diff --git a/main.py b/main.py index 0b4a757f..20aa72f2 100644 --- a/main.py +++ b/main.py @@ -2,8 +2,6 @@ * Proxyshop Application Launcher """ -import os -import sys from pathlib import Path from PySide6.QtGui import QIcon @@ -18,21 +16,6 @@ from src.startup import run_startup_checks -def launch_cli(): - """Launch the app in CLI mode.""" - - # Enable headless mode - os.environ["PROXYSHOP_HEADLESS"] = "1" - # Remove cli marker - if "cli" in sys.argv: - sys.argv.remove("cli") - - from src.commands import ProxyshopCLI - - # Run the CLI application - ProxyshopCLI.main() - - def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]): """Launch the app in GUI mode.""" @@ -147,6 +130,4 @@ def launch_gui(template_library: TemplateLibrary, plugins: dict[str, AppPlugin]) template_file_versions=versions, ) - if "cli" in sys.argv: - sys.exit(launch_cli()) launch_gui(template_library, plugins) diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/build.py b/scripts/build.py new file mode 100644 index 00000000..37cac113 --- /dev/null +++ b/scripts/build.py @@ -0,0 +1,61 @@ +from typing import Annotated + +from pydantic import BaseModel, Field +from pydantic_settings import CliApp, CliSubCommand + +from scripts.utils.build import ( + build_release, + generate_mkdocs, + generate_nav, + update_mkdocs_yml, +) + + +class BuildApp(BaseModel): + """Build executable app release and distributable zip.""" + + version: Annotated[ + str | None, + Field( + description="Version number to build with, if not provided use latest.", + ), + ] = None + beta: Annotated[ + bool, Field(alias="B", description="Build app as a Beta release.") + ] = False + console: Annotated[ + bool, Field(alias="C", description="Build app with console enabled.") + ] = False + raw: Annotated[ + bool, + Field( + alias="R", + description="Build app without creating zip release archive.", + ), + ] = False + + def cli_cmd(self) -> None: + build_release( + version=self.version, + beta=self.beta, + console=self.console, + zipped=not self.raw, + ) + + +class BuildDocs(BaseModel): + def cli_cmd(self) -> None: + headers = ["Template Classes", "Photoshop Helpers", "App Utilities"] + paths = ["templates", "helpers", "utils"] + [generate_mkdocs(p) for p in paths] + nav = generate_nav(headers, paths) + update_mkdocs_yml(nav) + + +class BuildCli(CliApp): + app: CliSubCommand[BuildApp] + docs: CliSubCommand[BuildDocs] + + +if __name__ == "__main__": + CliApp.run(BuildCli) diff --git a/scripts/compress.py b/scripts/compress.py new file mode 100644 index 00000000..4cc68466 --- /dev/null +++ b/scripts/compress.py @@ -0,0 +1,87 @@ +from pathlib import Path +from typing import Annotated + +from omnitils.files.archive import compress_7z, compress_7z_all +from pydantic import BaseModel, Field +from pydantic_settings import CliApp, CliSubCommand + +from src._state import PATH + + +class CompressTemplate(BaseModel): + """Compress a Photoshop template file (PSD/PSB).""" + + template: Annotated[ + str, + Field(description="Filename of the template, e.g. `normal.psd`"), + ] + plugin: Annotated[ + str | None, + Field( + description="Name of the plugin containing the template if required, e.g. MrTeferi" + ), + ] = None + + def cli_cmd(self) -> None: + path = ( + Path(PATH.PLUGINS, self.plugin, "templates") + if self.plugin + else PATH.TEMPLATES + ) + path = path / self.template + if not path.is_file(): + print( + f"Couldn't find a template named '{self.template}' at path:\n{str(path)}" + ) + return + compress_7z(path) + + +class CompressPlugin(BaseModel): + """Compress all Photoshop template files (PSD/PSB) in a given plugin.""" + + plugin: Annotated[ + str, + Field(description="Name of the plugin, e.g. MrTeferi"), + ] + + def cli_cmd(self) -> None: + path = PATH.PLUGINS / self.plugin / "templates" + if not path.is_dir(): + print(f"Couldn't find a plugin named '{self.plugin}'") + return + compress_7z_all(path) + + +class CompressAll(BaseModel): + """Compress all Photoshop template files (PSD/PSB) in the entire app, plugins optional.""" + + plugins: Annotated[ + bool, + Field( + alias="P", + description="Compress built-in plugins as well.", + ), + ] = False + + def cli_cmd(self) -> None: + # Compress main templates folder + compress_7z_all(PATH.TEMPLATES) + + # Compress plugins if requested + if self.plugins: + plugin_paths = [ + Path(PATH.PLUGINS, p, "templates") + for p in ["Investigamer", "SilvanMTG"] + ] + [compress_7z_all(p) for p in plugin_paths] + + +class CompressCli(BaseModel): + template: CliSubCommand[CompressTemplate] + plugin: CliSubCommand[CompressPlugin] + all: CliSubCommand[CompressAll] + + +if __name__ == "__main__": + CliApp.run(CompressCli) diff --git a/src/data/build/dist.yml b/scripts/data/dist.yml similarity index 100% rename from src/data/build/dist.yml rename to scripts/data/dist.yml diff --git a/scripts/utils/__init__.py b/scripts/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/utils/build.py b/scripts/utils/build.py similarity index 89% rename from src/utils/build.py rename to scripts/utils/build.py index 2397324d..4677c5b6 100644 --- a/src/utils/build.py +++ b/scripts/utils/build.py @@ -10,12 +10,14 @@ from pathlib import Path from shutil import copy2, copytree, move, rmtree from subprocess import run -from typing import NotRequired, TypedDict +from typing import Any, NotRequired, TypedDict -import PyInstaller.__main__ -from omnitils.files import dump_data_file, get_project_version, load_data_file +from pydantic import BaseModel, RootModel +from PyInstaller.__main__ import run as run_pyinstaller +from yaml import dump, safe_load from src._state import PATH +from src.utils.data_structures import parse_model # Directory definitions SRC: Path = PATH.CWD @@ -28,6 +30,14 @@ """ +class ProjectModel(BaseModel): + version: str + + +class PyProjectModel(BaseModel): + project: ProjectModel + + class DistConfigNames(TypedDict): """Maps the recognized names table in 'dist.yml' configuration.""" @@ -67,6 +77,8 @@ class DistConfig(TypedDict): copy: dict[str, DistConfigCopyDir] +DistConfigModel = RootModel[DistConfig] + """ * Handling Build Files """ @@ -219,21 +231,37 @@ def build_release( zipped: Whether to create a zip of this release. """ # Load dist config - dist_config: DistConfig = load_data_file(DIST_CONFIG) + dist_config = parse_model(DIST_CONFIG, DistConfigModel).root # Pre-build steps clear_build_files() make_directories(dist_config) # Use provided version or fallback to project defined - version = version or get_project_version((SRC / "pyproject").with_suffix(".toml")) + version = ( + version or parse_model(SRC / "pyproject.toml", PyProjectModel).project.version + ) generate_version_file(version) # Run Pyinstaller - spec_path: list[str] = ( - dist_config["spec"]["console"] if console else dist_config["spec"]["release"] + run_pyinstaller( + ( + "--distpath", + "./dist", + *(("--console",) if console else []), + "-n", + "Proxyshop", + "--onefile", + "--icon", + "./src/img/favicon.ico", + "--clean", + "--add-data", + "src/gui/qml:src/gui/qml", + "--add-data", + "src/img/favicon.ico:src/img/favicon.ico", + "main.py", + ) ) - PyInstaller.__main__.run([str(Path(SRC, *spec_path)), "--clean"]) # Copy our essential app files copy_app_files(dist_config) @@ -353,7 +381,9 @@ def update_mkdocs_yml(nav: list[dict[str, list[str]]]) -> None: Args: nav: List of nav objects to insert into nav data in mkdocs.yml. """ - mkdocs_yml = load_data_file(Path(SRC, "mkdocs.yml")) + mkdocs_path = SRC / "mkdocs.yml" + with open(mkdocs_path, "rb") as f: + mkdocs_yml: dict[str, Any] = safe_load(f) mkdocs_yml["nav"] = [ {"Home": "index.md"}, {"Changelog": "changelog.md"}, @@ -366,4 +396,5 @@ def update_mkdocs_yml(nav: list[dict[str, list[str]]]) -> None: }, {"License": "license.md"}, ] - dump_data_file(mkdocs_yml, Path(SRC, "mkdocs.yml"), config={"sort_keys": False}) + with open(mkdocs_path, "w", encoding="UTF-8") as f: + dump(mkdocs_yml, stream=f) diff --git a/src/commands/__init__.py b/src/commands/__init__.py deleted file mode 100644 index 2ba5a0f3..00000000 --- a/src/commands/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -* Headless CLI Application -""" - -import os -import sys -from pathlib import Path - -import click - -from src._state import PATH -from src.commands.build import build_cli -from src.commands.docs import docs_cli -from src.commands.files import compress_cli -from src.commands.render import render_cli -from src.commands.test import test_cli - -""" -* CLI Commands -""" - - -@click.command(name="run_headless", help="Launch the Proxyshop CLI application.") -def run_cli(): - """Launch the Proxyshop CLI application.""" - response = input("What would you like to do?\n") - if response == "1": - return print("You gave me 1.") - return print("You did not give me 1.") - - -@click.command(name="run_gui", help="Launch the Proxyshop GUI application.") -def run_gui(): - """Launch the Proxyshop GUI application.""" - exe_path = Path(sys.argv[0]) - if exe_path.suffix not in [".py", ".exe"]: - exe_path = PATH.CWD / "main.py" - os.execv(sys.executable, ["python", exe_path]) - - -""" -* CLI Application -""" - - -@click.group( - commands={ - "build": build_cli, - "compress": compress_cli, - "docs": docs_cli, - "gui": run_gui, - "render": render_cli, - "test": test_cli, - }, - context_settings={"ignore_unknown_options": True}, - invoke_without_command=True, - help="Invoke the CLI without a command to launch an ongoing headless Proxyshop application.", -) -@click.pass_context -def ProxyshopCLI(ctx: click.Context): - if ctx.invoked_subcommand is None: - ctx.invoke(run_cli) - pass - - -# Export CLI -__all__ = ["ProxyshopCLI"] diff --git a/src/commands/build.py b/src/commands/build.py deleted file mode 100644 index c63c2136..00000000 --- a/src/commands/build.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -* CLI Commands: Build -""" - -import click - -from src.utils.build import build_release - -""" -* Commands -""" - - -@click.command(help="Build executable app release and distributable zip.") -@click.argument("version", required=False) -@click.option( - "-B", "--beta", is_flag=True, default=False, help="Build app as a Beta release." -) -@click.option( - "-C", - "--console", - is_flag=True, - default=False, - help="Build app with console enabled.", -) -@click.option( - "-R", - "--raw", - is_flag=True, - default=False, - help="Build app without creating zip release archive.", -) -def build_app( - version: str | None = None, - beta: bool = False, - console: bool = False, - raw: bool = False, -) -> None: - """Build Proxyshop as an executable release. - - Args: - version: Version number to build with, if not provided use latest. - beta: Build as beta release if True. - console: Build with console window if True. - raw: Build app without creating zip if True. - """ - build_release(version=version, beta=beta, console=console, zipped=not raw) - - -""" -* Command Groups -""" - - -@click.group( - name="build", - help="Command utilities for building and managing release files.", - commands={"app": build_app}, -) -def build_cli() -> None: - """App build tools CLI.""" - pass - - -# Export CLI -__all__ = ["build_cli"] diff --git a/src/commands/docs.py b/src/commands/docs.py deleted file mode 100644 index 4e114f1d..00000000 --- a/src/commands/docs.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -* CLI Commands: Build -""" - -import click - -from src.utils.build import generate_mkdocs, generate_nav, update_mkdocs_yml - -""" -* Command Groups -""" - - -@click.group( - name="docs", help="Command utilities for managing the app's documentation." -) -def docs_cli() -> None: - """App docs tools CLI.""" - pass - - -""" -* Commands -""" - - -@docs_cli.command( - name="update", help="Updates MKDocs files for the current app version." -) -def generate_docs() -> None: - """Build the docs.""" - headers = ["Template Classes", "Photoshop Helpers", "App Utilities"] - paths = ["templates", "helpers", "utils"] - [generate_mkdocs(p) for p in paths] - nav = generate_nav(headers, paths) - update_mkdocs_yml(nav) - - -# Export CLI -__all__ = ["docs_cli"] diff --git a/src/commands/files.py b/src/commands/files.py deleted file mode 100644 index 57b0aea9..00000000 --- a/src/commands/files.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -* CLI Commands: Files -""" - -from pathlib import Path - -import click -from omnitils.files.archive import compress_7z, compress_7z_all - -from src._state import PATH - -""" -* Commands: Compression -""" - - -@click.group(name="compress", help="Command utilities for compressing files.") -def compress_cli(): - """File utilities CLI.""" - pass - - -@compress_cli.command( - name="template", help="Compress a Photoshop template file (PSD/PSB)." -) -@click.argument("template") -@click.argument("plugin", required=False) -def compress_template(template: str, plugin: str | None = None) -> None: - """Compress a template by name and optionally plugin name. - - Args: - template: Filename of the template, e.g. `normal.psd` - plugin: Name of the plugin containing the template if required, e.g. MrTeferi - """ - path = Path(PATH.PLUGINS, plugin, "templates") if plugin else PATH.TEMPLATES - path = path / template - if not path.is_file(): - print( - f"I couldn't find a template named '{template}' at this path:\n{str(path)}" - ) - return - compress_7z(path) - - -@compress_cli.command( - name="plugin", - help="Compress all Photoshop template files (PSD/PSB) in a given plugin.", -) -@click.argument("plugin") -def compress_plugin(plugin: str) -> None: - """Compress all templates in a specific plugin. - - Args: - plugin: Name of the plugin, e.g. MrTeferi - """ - path = PATH.PLUGINS / plugin / "templates" - if not path.is_dir(): - print(f"I couldn't find a plugin named '{plugin}'") - return - compress_7z_all(path) - - -@compress_cli.command( - name="all", - help="Compress all Photoshop template files (PSD/PSB) in the entire app, plugins optional.", -) -@click.option( - "-P", - "--plugins", - is_flag=True, - default=False, - help="Compress built-in plugins as well.", -) -def compress_all(plugins: bool = False) -> None: - """Compress all templates. - - Args: - plugins: Compress built-in plugins as well if True, otherwise skip them. - """ - # Compress main templates folder - compress_7z_all(PATH.TEMPLATES) - - # Compress plugins if requested - if plugins: - plugins = [ - Path(PATH.PLUGINS, p, "templates") for p in ["Investigamer", "SilvanMTG"] - ] - [compress_7z_all(p) for p in plugins] - - -# Export CLI -__all__ = ["compress_cli"] diff --git a/src/commands/render.py b/src/commands/render.py deleted file mode 100644 index f7a02005..00000000 --- a/src/commands/render.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -* CLI Commands: Rendering -""" - -from pathlib import Path - -import click -from omnitils.files import load_data_file - -from src import CON, TEMPLATE_DEFAULTS -from src._loader import TemplateDetails -from src.cards import CardDetails, parse_card_info -from src.layouts import layout_map - -""" -* Commands: Render -* Add a render command -* This is merely an example, will be replaced in the future. -""" - - -@click.group(name="render", help="Commands for rendering card images.") -def render_cli(): - """App render CLI.""" - pass - - -@render_cli.command(name="target", help="Render one or more target cards.") -@click.argument("data_file", required=True) -def render_target(data_file: str = None): - """Render a single card using JSON data.""" - - # Find your art image file - # Todo: Use target selection in Photoshop, support optional filepath argument - art_path = Path(CON.cwd, "art", data_file) - for suf in [".jpg", ".png", ".webp"]: - art_file = art_path.with_suffix(suf) - if art_file.is_file(): - break - - # Check if proper art file was found - if not art_file.is_file(): - raise OSError(f"No art file matching name '{data_file}' was found!") - - # Load card data from a json file and create fake card "details" dict - # Todo: Make custom card data an optional filepath argument - data_file = Path(CON.cwd, "customs", data_file).with_suffix(".json") - card = load_data_file(data_file) - file_details: CardDetails = parse_card_info(art_path) - - # Get appropriate layout class and initialize it - # Todo: Use the appropriate layout class provided - layout = layout_map.get(card.get("layout", "normal")) - layout(card, file_details) - - # Get appropriate template for this layout - # Todo: Use default, support optional template name argument or custom defined - template: TemplateDetails = TEMPLATE_DEFAULTS.get(layout.type) - template_class = template["object"].get_template_class(template["class_name"]) - layout.template_file = template["object"].path_psd - - # Load the template class, create an instance, execute it - template_class(layout).execute() - - -# Export CLI -__all__ = ["render_cli"] From 5acae7093d66f22ca00895069f0cd069e4bd49f0 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 13:30:41 +0300 Subject: [PATCH 147/190] build: Use uv instead of Poetry for dependency management --- poetry.lock | 4144 ------------------------------------------------ pyproject.toml | 102 +- uv.lock | 2052 ++++++++++++++++++++++++ 3 files changed, 2098 insertions(+), 4200 deletions(-) delete mode 100644 poetry.lock create mode 100644 uv.lock diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 5efb130e..00000000 --- a/poetry.lock +++ /dev/null @@ -1,4144 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "altgraph" -version = "0.17.5" -description = "Python graph (network) package" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597"}, - {file = "altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7"}, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "argcomplete" -version = "3.6.3" -description = "Bash tab completion for argparse" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce"}, - {file = "argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c"}, -] - -[package.extras] -test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] - -[[package]] -name = "attrs" -version = "26.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, - {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, -] - -[[package]] -name = "babel" -version = "2.18.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, - {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "backrefs" -version = "6.2" -description = "A wrapper around re and regex that adds additional back references." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8"}, - {file = "backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be"}, - {file = "backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90"}, - {file = "backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b"}, - {file = "backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7"}, - {file = "backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7"}, - {file = "backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49"}, -] - -[package.extras] -extras = ["regex"] - -[[package]] -name = "beautifulsoup4" -version = "4.14.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.7.0" -groups = ["main"] -files = [ - {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, - {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, -] - -[package.dependencies] -soupsieve = ">=1.6.1" -typing-extensions = ">=4.0.0" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "bracex" -version = "2.6" -description = "Bash style brace expander." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952"}, - {file = "bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7"}, -] - -[[package]] -name = "brotli" -version = "1.2.0" -description = "Python bindings for the Brotli compression library" -optional = false -python-versions = "*" -groups = ["main"] -markers = "platform_python_implementation == \"CPython\"" -files = [ - {file = "brotli-1.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:99cfa69813d79492f0e5d52a20fd18395bc82e671d5d40bd5a91d13e75e468e8"}, - {file = "brotli-1.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3ebe801e0f4e56d17cd386ca6600573e3706ce1845376307f5d2cbd32149b69a"}, - {file = "brotli-1.2.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a387225a67f619bf16bd504c37655930f910eb03675730fc2ad69d3d8b5e7e92"}, - {file = "brotli-1.2.0-cp27-cp27m-win32.whl", hash = "sha256:b908d1a7b28bc72dfb743be0d4d3f8931f8309f810af66c906ae6cd4127c93cb"}, - {file = "brotli-1.2.0-cp27-cp27m-win_amd64.whl", hash = "sha256:d206a36b4140fbb5373bf1eb73fb9de589bb06afd0d22376de23c5e91d0ab35f"}, - {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7e9053f5fb4e0dfab89243079b3e217f2aea4085e4d58c5c06115fc34823707f"}, - {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4735a10f738cb5516905a121f32b24ce196ab82cfc1e4ba2e3ad1b371085fd46"}, - {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e"}, - {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984"}, - {file = "brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de"}, - {file = "brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947"}, - {file = "brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2"}, - {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84"}, - {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d"}, - {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1"}, - {file = "brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997"}, - {file = "brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196"}, - {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744"}, - {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f"}, - {file = "brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd"}, - {file = "brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe"}, - {file = "brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a"}, - {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b"}, - {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3"}, - {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae"}, - {file = "brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03"}, - {file = "brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24"}, - {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84"}, - {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b"}, - {file = "brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d"}, - {file = "brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca"}, - {file = "brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f"}, - {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28"}, - {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7"}, - {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036"}, - {file = "brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161"}, - {file = "brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44"}, - {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab"}, - {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c"}, - {file = "brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f"}, - {file = "brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6"}, - {file = "brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c"}, - {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48"}, - {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18"}, - {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5"}, - {file = "brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a"}, - {file = "brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8"}, - {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21"}, - {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac"}, - {file = "brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e"}, - {file = "brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7"}, - {file = "brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63"}, - {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b"}, - {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361"}, - {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888"}, - {file = "brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d"}, - {file = "brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3"}, - {file = "brotli-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82676c2781ecf0ab23833796062786db04648b7aae8be139f6b8065e5e7b1518"}, - {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c16ab1ef7bb55651f5836e8e62db1f711d55b82ea08c3b8083ff037157171a69"}, - {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e85190da223337a6b7431d92c799fca3e2982abd44e7b8dec69938dcc81c8e9e"}, - {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d8c05b1dfb61af28ef37624385b0029df902ca896a639881f594060b30ffc9a7"}, - {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:465a0d012b3d3e4f1d6146ea019b5c11e3e87f03d1676da1cc3833462e672fb0"}, - {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:96fbe82a58cdb2f872fa5d87dedc8477a12993626c446de794ea025bbda625ea"}, - {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:1b71754d5b6eda54d16fbbed7fce2d8bc6c052a1b91a35c320247946ee103502"}, - {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:66c02c187ad250513c2f4fce973ef402d22f80e0adce734ee4e4efd657b6cb64"}, - {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:ba76177fd318ab7b3b9bf6522be5e84c2ae798754b6cc028665490f6e66b5533"}, - {file = "brotli-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:c1702888c9f3383cc2f09eb3e88b8babf5965a54afb79649458ec7c3c7a63e96"}, - {file = "brotli-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f8d635cafbbb0c61327f942df2e3f474dde1cff16c3cd0580564774eaba1ee13"}, - {file = "brotli-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e80a28f2b150774844c8b454dd288be90d76ba6109670fe33d7ff54d96eb5cb8"}, - {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b1b799f45da91292ffaa21a473ab3a3054fa78560e8ff67082a185274431c8"}, - {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b7e6716ee4ea0c59e3b241f682204105f7da084d6254ec61886508efeb43bc"}, - {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640fe199048f24c474ec6f3eae67c48d286de12911110437a36a87d7c89573a6"}, - {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:92edab1e2fd6cd5ca605f57d4545b6599ced5dea0fd90b2bcdf8b247a12bd190"}, - {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7274942e69b17f9cef76691bcf38f2b2d4c8a5f5dba6ec10958363dcb3308a0a"}, - {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:a56ef534b66a749759ebd091c19c03ef81eb8cd96f0d1d16b59127eaf1b97a12"}, - {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5732eff8973dd995549a18ecbd8acd692ac611c5c0bb3f59fa3541ae27b33be3"}, - {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:598e88c736f63a0efec8363f9eb34e5b5536b7b6b1821e401afcb501d881f59a"}, - {file = "brotli-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:7ad8cec81f34edf44a1c6a7edf28e7b7806dfb8886e371d95dcf789ccd4e4982"}, - {file = "brotli-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:865cedc7c7c303df5fad14a57bc5db1d4f4f9b2b4d0a7523ddd206f00c121a16"}, - {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ac27a70bda257ae3f380ec8310b0a06680236bea547756c277b5dfe55a2452a8"}, - {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e813da3d2d865e9793ef681d3a6b66fa4b7c19244a45b817d0cceda67e615990"}, - {file = "brotli-1.2.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fe11467c42c133f38d42289d0861b6b4f9da31e8087ca2c0d7ebb4543625526"}, - {file = "brotli-1.2.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c0d6770111d1879881432f81c369de5cde6e9467be7c682a983747ec800544e2"}, - {file = "brotli-1.2.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:eda5a6d042c698e28bda2507a89b16555b9aa954ef1d750e1c20473481aff675"}, - {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3173e1e57cebb6d1de186e46b5680afbd82fd4301d7b2465beebe83ed317066d"}, - {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:71a66c1c9be66595d628467401d5976158c97888c2c9379c034e1e2312c5b4f5"}, - {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1e68cdf321ad05797ee41d1d09169e09d40fdf51a725bb148bff892ce04583d7"}, - {file = "brotli-1.2.0-cp38-cp38-win32.whl", hash = "sha256:f16dace5e4d3596eaeb8af334b4d2c820d34b8278da633ce4a00020b2eac981c"}, - {file = "brotli-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:14ef29fc5f310d34fc7696426071067462c9292ed98b5ff5a27ac70a200e5470"}, - {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8d4f47f284bdd28629481c97b5f29ad67544fa258d9091a6ed1fda47c7347cd1"}, - {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2881416badd2a88a7a14d981c103a52a23a276a553a8aacc1346c2ff47c8dc17"}, - {file = "brotli-1.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d39b54b968f4b49b5e845758e202b1035f948b0561ff5e6385e855c96625971"}, - {file = "brotli-1.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95db242754c21a88a79e01504912e537808504465974ebb92931cfca2510469e"}, - {file = "brotli-1.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bba6e7e6cfe1e6cb6eb0b7c2736a6059461de1fa2c0ad26cf845de6c078d16c8"}, - {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:88ef7d55b7bcf3331572634c3fd0ed327d237ceb9be6066810d39020a3ebac7a"}, - {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7fa18d65a213abcfbb2f6cafbb4c58863a8bd6f2103d65203c520ac117d1944b"}, - {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09ac247501d1909e9ee47d309be760c89c990defbb2e0240845c892ea5ff0de4"}, - {file = "brotli-1.2.0-cp39-cp39-win32.whl", hash = "sha256:c25332657dee6052ca470626f18349fc1fe8855a56218e19bd7a8c6ad4952c49"}, - {file = "brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937"}, - {file = "brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a"}, -] - -[[package]] -name = "brotlicffi" -version = "1.2.0.1" -description = "Python CFFI bindings to the Brotli library" -optional = false -python-versions = ">=3.8" -groups = ["main"] -markers = "platform_python_implementation == \"PyPy\"" -files = [ - {file = "brotlicffi-1.2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c85e65913cf2b79c57a3fdd05b98d9731d9255dc0cb696b09376cc091b9cddd"}, - {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:535f2d05d0273408abc13fc0eebb467afac17b0ad85090c8913690d40207dac5"}, - {file = "brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce17eb798ca59ecec67a9bb3fd7a4304e120d1cd02953ce522d959b9a84d58ac"}, - {file = "brotlicffi-1.2.0.1-cp314-cp314t-win32.whl", hash = "sha256:3c9544f83cb715d95d7eab3af4adbbef8b2093ad6382288a83b3a25feb1a57ec"}, - {file = "brotlicffi-1.2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:625f8115d32ae9c0740d01ea51518437c3fbaa3e78d41cb18459f6f7ac326000"}, - {file = "brotlicffi-1.2.0.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:91ba5f0ccc040f6ff8f7efaf839f797723d03ed46acb8ae9408f99ffd2572cf4"}, - {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9a670c6811af30a4bd42d7116dc5895d3b41beaa8ed8a89050447a0181f5ce"}, - {file = "brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3314a3476f59e5443f9f72a6dff16edc0c3463c9b318feaef04ae3e4683f5a"}, - {file = "brotlicffi-1.2.0.1-cp38-abi3-win32.whl", hash = "sha256:82ea52e2b5d3145b6c406ebd3efb0d55db718b7ad996bd70c62cec0439de1187"}, - {file = "brotlicffi-1.2.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:da2e82a08e7778b8bc539d27ca03cdd684113e81394bfaaad8d0dfc6a17ddede"}, - {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e015af99584c6db1490a69a210c765953e473e63adc2d891ac3062a737c9e851"}, - {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37cb587d32bf7168e2218c455e22e409ad1f3157c6c71945879a311f3e6b6abf"}, - {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d6ba65dd528892b4d9960beba2ae011a753620bcfc66cf6fa3cee18d7b0baa4"}, - {file = "brotlicffi-1.2.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2a5575653b0672638ba039b82fda56854934d7a6a24d4b8b5033f73ab43cbc1"}, - {file = "brotlicffi-1.2.0.1.tar.gz", hash = "sha256:c20d5c596278307ad06414a6d95a892377ea274a5c6b790c2548c009385d621c"}, -] - -[package.dependencies] -cffi = {version = ">=1.17.0", markers = "python_version >= \"3.13\""} - -[[package]] -name = "bs4" -version = "0.0.2" -description = "Dummy package for Beautiful Soup (beautifulsoup4)" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, - {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - -[[package]] -name = "certifi" -version = "2026.2.25" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, -] - -[[package]] -name = "cffi" -version = "2.0.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation == \"PyPy\"" -files = [ - {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, - {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, - {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, - {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, - {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, - {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, - {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, - {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, - {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, - {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, - {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, - {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, - {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, - {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, - {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, - {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, - {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, - {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, - {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, - {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, - {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, - {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, - {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, - {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, - {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, - {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, - {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, - {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, - {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, - {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, - {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, - {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, - {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, - {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, - {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, - {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, - {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, - {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, - {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, - {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, - {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, - {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, -] - -[package.dependencies] -pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} - -[[package]] -name = "cfgv" -version = "3.5.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, - {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, - {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, - {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, - {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, - {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, - {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, - {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, - {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, - {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, - {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, - {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, -] - -[[package]] -name = "click" -version = "8.3.2" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d"}, - {file = "click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} - -[[package]] -name = "commitizen" -version = "4.13.9" -description = "Python commitizen client tool" -optional = false -python-versions = "<4.0,>=3.10" -groups = ["dev"] -files = [ - {file = "commitizen-4.13.9-py3-none-any.whl", hash = "sha256:d2af3d6a83cacec9d5200e17768942c5de6266f93d932c955986c60c4285f2db"}, - {file = "commitizen-4.13.9.tar.gz", hash = "sha256:2b4567ed50555e10920e5bd804a6a4e2c42ec70bb74f14a83f2680fe9eaf9727"}, -] - -[package.dependencies] -argcomplete = ">=1.12.1,<3.7" -charset-normalizer = ">=2.1.0,<4" -colorama = ">=0.4.1,<1.0" -decli = ">=0.6.0,<1.0" -deprecated = ">=1.2.13,<2" -jinja2 = ">=2.10.3" -packaging = ">=19" -prompt-toolkit = "!=3.0.52" -pyyaml = ">=3.8" -questionary = ">=2.0,<3.0" -termcolor = ">=1.1.0,<4.0.0" -tomlkit = ">=0.8.0,<1.0.0" - -[[package]] -name = "comtypes" -version = "1.4.16" -description = "Pure Python COM package" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "comtypes-1.4.16-py3-none-any.whl", hash = "sha256:e18d85179ff12955524c5a8c3bc09cb3c0d890f1da4d7123d14244c7b78f84c8"}, - {file = "comtypes-1.4.16.tar.gz", hash = "sha256:cd66d1add01265cface4df51ba1e31cd1657e04463c281c802e737e79e1ba93c"}, -] - -[[package]] -name = "contourpy" -version = "1.3.3" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, - {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, - {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, - {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, - {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, - {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, - {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, - {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, - {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, - {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, - {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, - {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, - {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, - {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, - {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, - {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, - {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, - {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, - {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, - {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, - {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, - {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, - {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, - {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, - {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, - {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, - {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, - {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, - {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, - {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, - {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, - {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, - {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, - {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, - {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, - {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, - {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, - {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, - {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, - {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, - {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, - {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, - {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, - {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, - {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, - {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, - {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, - {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, - {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, - {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, -] - -[package.dependencies] -numpy = ">=1.25" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "csscompressor" -version = "0.9.5" -description = "A python port of YUI CSS Compressor" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05"}, -] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "decli" -version = "0.6.3" -description = "Minimal, easy-to-use, declarative cli tool" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3"}, - {file = "decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656"}, -] - -[[package]] -name = "deprecated" -version = "1.3.1" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f"}, - {file = "deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223"}, -] - -[package.dependencies] -wrapt = ">=1.10,<3" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] - -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - -[[package]] -name = "filelock" -version = "3.25.2" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70"}, - {file = "filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694"}, -] - -[[package]] -name = "fonttools" -version = "4.62.1" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c"}, - {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a"}, - {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3"}, - {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23"}, - {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d"}, - {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae"}, - {file = "fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed"}, - {file = "fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9"}, - {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7"}, - {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14"}, - {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7"}, - {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b"}, - {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1"}, - {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416"}, - {file = "fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53"}, - {file = "fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2"}, - {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974"}, - {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9"}, - {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936"}, - {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392"}, - {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04"}, - {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d"}, - {file = "fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c"}, - {file = "fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42"}, - {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79"}, - {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe"}, - {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68"}, - {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1"}, - {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069"}, - {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9"}, - {file = "fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24"}, - {file = "fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056"}, - {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca"}, - {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca"}, - {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782"}, - {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae"}, - {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7"}, - {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a"}, - {file = "fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800"}, - {file = "fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e"}, - {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82"}, - {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260"}, - {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4"}, - {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b"}, - {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87"}, - {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c"}, - {file = "fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a"}, - {file = "fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e"}, - {file = "fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd"}, - {file = "fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d"}, -] - -[package.extras] -all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] -lxml = ["lxml (>=4.0)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.45.0)"] -symfont = ["sympy"] -type1 = ["xattr ; sys_platform == \"darwin\""] -unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] -woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "gitdb" -version = "4.0.12" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.46" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058"}, - {file = "gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] - -[[package]] -name = "griffelib" -version = "2.0.2" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1"}, - {file = "griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e"}, -] - -[package.extras] -pypi = ["pip (>=24.0)", "platformdirs (>=4.2)", "wheel (>=0.42)"] - -[[package]] -name = "hexproof" -version = "0.3.7" -description = "A comprehensive library of Magic the Gathering API utilities." -optional = false -python-versions = ">=3.10,<4.0" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -bs4 = "^0.0.2" -limits = "^5.6.0" -loguru = "^0.7.3" -omnitils = {git = "https://github.com/pappnu/omnitils.git", branch = "dev"} -pydantic = ">=2.12.3" -PyYAML = ">=6.0.3" -requests = "^2.32.5" -tomli = ">=2.3.0" -tomlkit = ">=0.13.3" - -[package.source] -type = "git" -url = "https://github.com/pappnu/hexproof.git" -reference = "dev" -resolved_reference = "fc281f1d767651322ff271d2ccb72251bd70e19c" - -[[package]] -name = "htmlmin2" -version = "0.1.13" -description = "An HTML Minifier" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2"}, -] - -[[package]] -name = "identify" -version = "2.6.18" -description = "File identification library for Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737"}, - {file = "identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.11" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "inflate64" -version = "1.0.4" -description = "deflate64 compression/decompression library" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "inflate64-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1a47837d4322e0684824f91eb635aa6fd1967584140c478b0a1aca7b11740d6"}, - {file = "inflate64-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8600478542e2354d1ee7b5c57c957006cabacd8b787b4046951f487a2216e5c0"}, - {file = "inflate64-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fb2b5a62579d074f38352a3494c3c6ac1a90516b75c5793c39303547f1fea925"}, - {file = "inflate64-1.0.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dcfafc572a642215894af1ec8d05949fa35eb7cb36d053aa97b11eccf1ae579e"}, - {file = "inflate64-1.0.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb93159cb60aee8cab62541aa70e4c460f13359660a27a1a486518bba0153535"}, - {file = "inflate64-1.0.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:89126ceb4d96e76842f4697017a9a3e750c34e029ddb360b3d8ca79a648d47f6"}, - {file = "inflate64-1.0.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f70e6692617ec82500b203eefac8765302298ce7e73584fcf995bb9e23184530"}, - {file = "inflate64-1.0.4-cp310-cp310-win32.whl", hash = "sha256:d08cdda33341b4f992af60c12dc60e370e9993b80a936c17244a602711eeb727"}, - {file = "inflate64-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:950dd7fe53474df5f4699b8f099980027e812d55fd82d8e167d599822c3d27d6"}, - {file = "inflate64-1.0.4-cp310-cp310-win_arm64.whl", hash = "sha256:bad20de249d6336793f6267880668dbb286ca5c6e6991795aa6344c817588068"}, - {file = "inflate64-1.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bccda9815b27623e805a34ee3ee4f46c93f0cc7ac621f9834d75f033fd79c27a"}, - {file = "inflate64-1.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c11e2a3cb9d9b49620c9b0c806dd0c55daec3b6bb665299b770a68f01bfc5432"}, - {file = "inflate64-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e42def03ace8c58fd50b0df4f40241c45a2314c3876d020cce24acf958323c98"}, - {file = "inflate64-1.0.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7912927a509ca58d1a445ce4ff6e6e9f276dc1d72687386cdf7103bf590e785c"}, - {file = "inflate64-1.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec40c0383cbd84d845dcb785a48ae76eef43246c923f84fda380fdd5ea653d3c"}, - {file = "inflate64-1.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b01539fea372c6078b9707d9121c12cb321e587e193f50e257ce06cf5b15e41"}, - {file = "inflate64-1.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bf4e34e32a37a42e9cf8bd9681f89e3e37b218f97d8b8cc95bd065419bc8db13"}, - {file = "inflate64-1.0.4-cp311-cp311-win32.whl", hash = "sha256:2725ccc14b138f0ad622d0322b769f177f9edfe016ee9ed3404102935d39e7de"}, - {file = "inflate64-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:7056148548c1f25dcb38251f88c19b4635a5f32af4c7bad00621c85509e3d8c0"}, - {file = "inflate64-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:2ea7bdcad65e255b4596f84880f6e0c1756d6336d620e302653257defa407742"}, - {file = "inflate64-1.0.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8009e4a4918ee6c8cbc49e58fe159464895064cfdf0565fed3f49ca81e45272"}, - {file = "inflate64-1.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d173a7a0e865bb7d19685c5b1ad2994712b8361b24136d7e94abeff58505647"}, - {file = "inflate64-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8bad992f2d034f5f7e36208e54502d1b0829ce772c898e5dc59109833420148a"}, - {file = "inflate64-1.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bfcf806912ced77a21394f7363805ecacd626b79f93cba87d505a48e88ede78"}, - {file = "inflate64-1.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62d1aac3aba094ae42e27ce7581b414c90f218248be0953b6aeb11a127225e5d"}, - {file = "inflate64-1.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8065166f355122484f004225b379d403346bdae69ec624786a9334f025580675"}, - {file = "inflate64-1.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a95f32087d223d2e119ff5c7c264109e8d4cb7e421e7a688a899a6fe021b38"}, - {file = "inflate64-1.0.4-cp312-cp312-win32.whl", hash = "sha256:ad4fa490bb7dc2a4640a3adaa2d5950f4a465ba034bbcf184c2103646e58ad97"}, - {file = "inflate64-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:2c6befdf83d088a6e0d10d0873a9d4bfde2ce00ad7a52c8189cf303306f98030"}, - {file = "inflate64-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:2b263c619469f90a75f29c421c53d31b208ad494a078235a8f6db2bc96583fdc"}, - {file = "inflate64-1.0.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3f37540d0e64884a935fd62a7d17e40ab69f05ec63e815483b6513675d01bef"}, - {file = "inflate64-1.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4d24112180c95d12f279cade9a1e21f8be7f4790c4109c293292edf87d061992"}, - {file = "inflate64-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5c098dab17821f466fc6e6a3d78fc6e0295bb51458015f03416b1d58d6a8df4f"}, - {file = "inflate64-1.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a984b9287ff0fb596eb058d66a9e94530556afd2b7c054b44f2e0aeeff894e8f"}, - {file = "inflate64-1.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62a13d0327631778fa2a47c308ae2b07b2659b7bb8564783259ac65949f8c0c"}, - {file = "inflate64-1.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:513201336fb3b0b7e2aee5dbbbe30a9f1b23291738b5ceb80076fc285f2ec2f1"}, - {file = "inflate64-1.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:84ce3a97272ba745fce52b38363855c7201968f6402a794bbade774e64c657b9"}, - {file = "inflate64-1.0.4-cp313-cp313-win32.whl", hash = "sha256:332051a9d7e50579b90a3f555d68f53414b06f636c9ffe82e97c0baae3c8fbcc"}, - {file = "inflate64-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:3983f53b590ff7d0ba243f664ce852aca882482f30f7a8eab33e10d769336d0c"}, - {file = "inflate64-1.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:118d8286f085e99a14341c76ef9fbffd56619ccc80318a9a204aea3dbfa71470"}, - {file = "inflate64-1.0.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4f61925b2d4248eac2ebb15350a80aaa0d1f7f1dc770bd5ebbbb3b0db4a6a416"}, - {file = "inflate64-1.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c1acf18b08b32981a4a11ec5a112b8ad5d7c7a5b15cb5bdbdb5b19201e9aa180"}, - {file = "inflate64-1.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abddae8920b2eaef824254e14b8d4ff54afbe6194a1bbe9816584859f0c1244d"}, - {file = "inflate64-1.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b303132cc562a906543a56f35c4e164e3880da6ff041cb4a7b1df9f9d2b4bb69"}, - {file = "inflate64-1.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f0993214dea0738c557fa56c13cd9083aef0097a201d726c21984ad7f577514"}, - {file = "inflate64-1.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a6baedc3288d7a4ff588951d3a9a97a5391dceed6255ff5b16e42cae7274bfa9"}, - {file = "inflate64-1.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a846ce1f38845b20bef2625af1b512be83416d97824539524c5a34e7a729aec7"}, - {file = "inflate64-1.0.4-cp314-cp314-win32.whl", hash = "sha256:eef87908c780439393d577a155868317f0a275b47b417db9f47d8633ec791745"}, - {file = "inflate64-1.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:fb2fdd63ef3933b67af98b3f2ee2f57e7787278041d7ba4821382fedd729b68a"}, - {file = "inflate64-1.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:2e129669a0243ac7816fd526946ee01c25688fe81623a6d6bc95b3156d80f4fb"}, - {file = "inflate64-1.0.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b17bf665d948dc4edeea0cd17752415d0cd7240c882b9c7e136ad4cc4321e9d4"}, - {file = "inflate64-1.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6751758301936fbb38fa38eb5312e14e27b6a1abf568f83c17557fab2694373d"}, - {file = "inflate64-1.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d6a4136752aa2a544301059d8f13780aeb88c34d60770258436a87dacd3fc304"}, - {file = "inflate64-1.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:938ebc6b28578bfd365d1a9fdb18b7faab08321babeb2198e8025d07d8dc7fb5"}, - {file = "inflate64-1.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61f51f80fa6f367288343c1a2cd20a42af454883087064e9274fd2a8c3a5a200"}, - {file = "inflate64-1.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:172b51da7bbfa66b33f0a5405e944807b9949e92cf4cd9f983c07af8152766df"}, - {file = "inflate64-1.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ca9a2985afd5a14fb48cd126a67e5944ccb7a0a6bdec58c4f796c8c88a84539"}, - {file = "inflate64-1.0.4-cp314-cp314t-win32.whl", hash = "sha256:f8964ceaabea294bc20abc9ef408c6aae978a75c25c83168a76cd87a37c38938"}, - {file = "inflate64-1.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:7d13b04cba65c12d21e65eaa77da9484e265e8e821b26e0761d1455ad3a878d9"}, - {file = "inflate64-1.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:9ae3ee727235a06dc3cd353ee5761fdd8e3b56ad119c711f61680528972a6ced"}, - {file = "inflate64-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:179e39069c56a69c37d3b939505254cf7e495a06fcbc0c4bd5a61fa8fc43c678"}, - {file = "inflate64-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:067af498c34a8b69b2957d20dbce4d72affda23aed37ea231b1ea5ad9aab5731"}, - {file = "inflate64-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e408d3b667fb693d45448327dc8cab8c684c6627d4fd8e71819f212ab1435e81"}, - {file = "inflate64-1.0.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b5aa704141c9bad08b4e57bd722f68dd3a8cff62490b16a1605d2698e268fbd"}, - {file = "inflate64-1.0.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a55295f493a40d7e68929f0ba54e489ab9b623b6aa968dc5d1389ba77a63eff"}, - {file = "inflate64-1.0.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e107d1d6e6d9b94409a68d1ac20522815996ad44269cc05a69c93d0f1c450f95"}, - {file = "inflate64-1.0.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19ffca993315ce7a4439efc388ffebdd395a20dbea2942b2445d87ec15373737"}, - {file = "inflate64-1.0.4-cp39-cp39-win32.whl", hash = "sha256:dd8fdb7350728aa488edabeb9d2afbac5273522b50665e8dc99844a7eb99925b"}, - {file = "inflate64-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4662b2a4bba73bd64f803c97ec5134d6fd19227c3b3d3785e1babb359f1b4c33"}, - {file = "inflate64-1.0.4-cp39-cp39-win_arm64.whl", hash = "sha256:f5bb6c58c642859ecf5e23178b1e7e4724f3ce968fc49090919d59a5e186f3e6"}, - {file = "inflate64-1.0.4.tar.gz", hash = "sha256:b398c686960c029777afc0ed281a86f66adb956cfc3fbf6667cc6453f7b407ce"}, -] - -[package.extras] -check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "flake8-isort", "mypy (>=1.10.0)", "mypy_extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] -docs = ["docutils", "sphinx (>=5.0)", "sphinx_rtd_theme"] -test = ["pytest"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jsmin" -version = "3.0.1" -description = "JavaScript minifier." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"}, -] - -[[package]] -name = "kiwisolver" -version = "1.5.0" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374"}, - {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd"}, - {file = "kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476"}, - {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22"}, - {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b"}, - {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e"}, - {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb"}, - {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537"}, - {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4"}, - {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c"}, - {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede"}, - {file = "kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2"}, - {file = "kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875"}, - {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c"}, - {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb"}, - {file = "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac"}, - {file = "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27"}, - {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398"}, - {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db"}, - {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc"}, - {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679"}, - {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309"}, - {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2"}, - {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c"}, - {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08"}, - {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4"}, - {file = "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b"}, - {file = "kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac"}, - {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9"}, - {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588"}, - {file = "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819"}, - {file = "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f"}, - {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf"}, - {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d"}, - {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083"}, - {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6"}, - {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1"}, - {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0"}, - {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15"}, - {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314"}, - {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9"}, - {file = "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384"}, - {file = "kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7"}, - {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09"}, - {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3"}, - {file = "kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd"}, - {file = "kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3"}, - {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96"}, - {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099"}, - {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8"}, - {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87"}, - {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23"}, - {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859"}, - {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902"}, - {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167"}, - {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0"}, - {file = "kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276"}, - {file = "kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2"}, - {file = "kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53"}, - {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615"}, - {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02"}, - {file = "kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e"}, - {file = "kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac"}, - {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05"}, - {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd"}, - {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a"}, - {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554"}, - {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581"}, - {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303"}, - {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9"}, - {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79"}, - {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796"}, - {file = "kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e"}, - {file = "kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681"}, - {file = "kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57"}, - {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797"}, - {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203"}, - {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7"}, - {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57"}, - {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4"}, - {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca"}, - {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f"}, - {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed"}, - {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc"}, - {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232"}, - {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a"}, - {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737"}, - {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16"}, - {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1"}, - {file = "kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a"}, -] - -[[package]] -name = "limits" -version = "5.8.0" -description = "Rate limiting utilities" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8"}, - {file = "limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da"}, -] - -[package.dependencies] -deprecated = ">=1.2" -packaging = ">=21" -typing-extensions = "*" - -[package.extras] -async-memcached = ["memcachio (>=0.3)"] -async-mongodb = ["motor (>=3,<4)"] -async-redis = ["coredis (>=3.4.0,<6)"] -async-valkey = ["valkey (>=6)"] -memcached = ["pymemcache (>3,<5.0.0)"] -mongodb = ["pymongo (>4.1,<5)"] -redis = ["redis (>3,!=4.5.2,!=4.5.3,<8.0.0)"] -rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] -valkey = ["valkey (>=6)"] - -[[package]] -name = "loguru" -version = "0.7.3" -description = "Python logging made (stupidly) simple" -optional = false -python-versions = "<4.0,>=3.5" -groups = ["main"] -files = [ - {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, - {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] - -[[package]] -name = "macholib" -version = "1.16.4" -description = "Mach-O header analysis and editing" -optional = false -python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"darwin\"" -files = [ - {file = "macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea"}, - {file = "macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362"}, -] - -[package.dependencies] -altgraph = ">=0.17" - -[[package]] -name = "markdown" -version = "3.10.2" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, - {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, -] - -[package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, - {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins (>=0.5.0)"] -profiling = ["gprof2dot"] -rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] - -[[package]] -name = "markupsafe" -version = "3.0.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, - {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, - {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, - {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, - {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, - {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, - {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, - {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, - {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, -] - -[[package]] -name = "matplotlib" -version = "3.10.8" -description = "Python plotting package" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7"}, - {file = "matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656"}, - {file = "matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df"}, - {file = "matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17"}, - {file = "matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933"}, - {file = "matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a"}, - {file = "matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160"}, - {file = "matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78"}, - {file = "matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4"}, - {file = "matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2"}, - {file = "matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6"}, - {file = "matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9"}, - {file = "matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2"}, - {file = "matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a"}, - {file = "matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58"}, - {file = "matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04"}, - {file = "matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f"}, - {file = "matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466"}, - {file = "matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf"}, - {file = "matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b"}, - {file = "matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6"}, - {file = "matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1"}, - {file = "matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486"}, - {file = "matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce"}, - {file = "matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6"}, - {file = "matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149"}, - {file = "matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645"}, - {file = "matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077"}, - {file = "matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22"}, - {file = "matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39"}, - {file = "matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565"}, - {file = "matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a"}, - {file = "matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958"}, - {file = "matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5"}, - {file = "matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f"}, - {file = "matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b"}, - {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d"}, - {file = "matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008"}, - {file = "matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c"}, - {file = "matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11"}, - {file = "matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8"}, - {file = "matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50"}, - {file = "matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908"}, - {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a"}, - {file = "matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1"}, - {file = "matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c"}, - {file = "matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b"}, - {file = "matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f"}, - {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8"}, - {file = "matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7"}, - {file = "matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3"}, - {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1"}, - {file = "matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a"}, - {file = "matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2"}, - {file = "matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.23" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=3" -python-dateutil = ">=2.7" - -[package.extras] -dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "memory-profiler" -version = "0.61.0" -description = "A module for monitoring memory usage of a python program" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"}, - {file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"}, -] - -[package.dependencies] -psutil = "*" - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-autolinks-plugin" -version = "0.7.1" -description = "An MkDocs plugin" -optional = false -python-versions = ">=3.4" -groups = ["dev"] -files = [ - {file = "mkdocs-autolinks-plugin-0.7.1.tar.gz", hash = "sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151"}, - {file = "mkdocs_autolinks_plugin-0.7.1-py3-none-any.whl", hash = "sha256:5c6c17f6649b68e79a9ef0b2648d59f3072e18002b90ee1586a64c505f11ab12"}, -] - -[package.dependencies] -mkdocs = ">=1.2.3" - -[[package]] -name = "mkdocs-autorefs" -version = "1.4.4" -description = "Automatically link across pages in MkDocs." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089"}, - {file = "mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197"}, -] - -[package.dependencies] -Markdown = ">=3.3" -markupsafe = ">=2.0.1" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-gen-files" -version = "0.6.1" -description = "MkDocs plugin to programmatically generate documentation pages during the build" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189"}, - {file = "mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763"}, -] - -[package.dependencies] -mkdocs = ">=1.4.1,<=1.6.1" -properdocs = ">=1.6.5" - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.2" -description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, - {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, -] - -[package.dependencies] -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mkdocs-git-revision-date-plugin" -version = "0.3.2" -description = "MkDocs plugin for setting revision date from git per markdown file." -optional = false -python-versions = ">=3.4" -groups = ["dev"] -files = [ - {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, -] - -[package.dependencies] -GitPython = "*" -jinja2 = "*" -mkdocs = ">=0.17" - -[[package]] -name = "mkdocs-include-markdown-plugin" -version = "7.2.2" -description = "Mkdocs Markdown includer plugin." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_include_markdown_plugin-7.2.2-py3-none-any.whl", hash = "sha256:f2ec4487cf32d3e33ca528f9366f20fb9280ded9c8d1630eb2bbda244962dcd1"}, - {file = "mkdocs_include_markdown_plugin-7.2.2.tar.gz", hash = "sha256:f052ccb741eccf498116b826c1d78a2d761c56747372594709441cee0963fbc9"}, -] - -[package.dependencies] -mkdocs = ">=1.4" -wcmatch = "*" - -[package.extras] -cache = ["platformdirs"] - -[[package]] -name = "mkdocs-material" -version = "9.7.6" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba"}, - {file = "mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69"}, -] - -[package.dependencies] -babel = ">=2.10" -backrefs = ">=5.7.post1" -colorama = ">=0.4" -jinja2 = ">=3.1" -markdown = ">=3.2" -mkdocs = ">=1.6,<2" -mkdocs-material-extensions = ">=1.3" -paginate = ">=0.5" -pygments = ">=2.16" -pymdown-extensions = ">=10.2" -requests = ">=2.30" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4)"] -imaging = ["cairosvg (>=2.6)", "pillow (>=10.2)"] -recommended = ["mkdocs-minify-plugin (>=0.7)", "mkdocs-redirects (>=1.2)", "mkdocs-rss-plugin (>=1.6)"] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, -] - -[[package]] -name = "mkdocs-minify-plugin" -version = "0.8.0" -description = "An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d"}, - {file = "mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6"}, -] - -[package.dependencies] -csscompressor = ">=0.9.5" -htmlmin2 = ">=0.1.13" -jsmin = ">=3.0.1" -mkdocs = ">=1.4.1" - -[[package]] -name = "mkdocs-pymdownx-material-extras" -version = "2.8" -description = "Plugin to extend MkDocs Material theme." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs_pymdownx_material_extras-2.8-py3-none-any.whl", hash = "sha256:81b68789420c51b9b15514180d0f3ab7136d56ee512c830c998d2edb77ca3d77"}, - {file = "mkdocs_pymdownx_material_extras-2.8.tar.gz", hash = "sha256:7b22bb119cd9592f98d6c6d4d269506d9a68d7038355c71525aadc88169ee9fe"}, -] - -[package.dependencies] -mkdocs-material = ">=8.3.3" - -[[package]] -name = "mkdocs-same-dir" -version = "0.1.4" -description = "MkDocs plugin to allow placing mkdocs.yml in the same directory as documentation" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_same_dir-0.1.4-py3-none-any.whl", hash = "sha256:75103f722b2ab86a860c1a850d52ab2867b2e334f6b0c0dbe1c6d09ccd9c9897"}, - {file = "mkdocs_same_dir-0.1.4.tar.gz", hash = "sha256:68e8a6cd023833d1b797563b57c0bc732156170a00f615404f1d0e8544ed7e39"}, -] - -[package.dependencies] -mkdocs = ">=1.2,<=1.6.1" -properdocs = ">=1.6.5" - -[[package]] -name = "mkdocstrings" -version = "1.0.3" -description = "Automatic documentation from sources, for MkDocs." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046"}, - {file = "mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434"}, -] - -[package.dependencies] -Jinja2 = ">=3.1" -Markdown = ">=3.6" -MarkupSafe = ">=1.1" -mkdocs = ">=1.6" -mkdocs-autorefs = ">=1.4" -mkdocstrings-python = {version = ">=1.16.2", optional = true, markers = "extra == \"python\""} -pymdown-extensions = ">=6.3" - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=1.16.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-python" -version = "2.0.3" -description = "A Python handler for mkdocstrings." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12"}, - {file = "mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8"}, -] - -[package.dependencies] -griffelib = ">=2.0" -mkdocs-autorefs = ">=1.4" -mkdocstrings = ">=0.30" - -[[package]] -name = "multidict" -version = "6.7.1" -description = "multidict implementation" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8"}, - {file = "multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190"}, - {file = "multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962"}, - {file = "multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505"}, - {file = "multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122"}, - {file = "multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df"}, - {file = "multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e"}, - {file = "multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0"}, - {file = "multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0"}, - {file = "multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa"}, - {file = "multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a"}, - {file = "multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b"}, - {file = "multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd"}, - {file = "multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a"}, - {file = "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a"}, - {file = "multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba"}, - {file = "multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511"}, - {file = "multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19"}, - {file = "multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2"}, - {file = "multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed"}, - {file = "multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d"}, - {file = "multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33"}, - {file = "multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3"}, - {file = "multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5"}, - {file = "multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963"}, - {file = "multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd"}, - {file = "multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52"}, - {file = "multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108"}, - {file = "multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32"}, - {file = "multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8"}, - {file = "multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, - {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, - {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, - {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, - {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, - {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, - {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, - {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, - {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, - {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, - {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, - {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, - {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358"}, - {file = "multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f"}, - {file = "multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de"}, - {file = "multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5"}, - {file = "multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0"}, - {file = "multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4"}, - {file = "multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9"}, - {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, - {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, -] - -[[package]] -name = "multivolumefile" -version = "0.2.3" -description = "multi volume file wrapper library" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678"}, - {file = "multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6"}, -] - -[package.extras] -check = ["check-manifest", "flake8", "flake8-black", "isort (>=5.0.3)", "pygments", "readme-renderer", "twine"] -test = ["coverage[toml] (>=5.2)", "coveralls (>=2.1.1)", "hypothesis", "pyannotate", "pytest", "pytest-cov"] -type = ["mypy", "mypy-extensions"] - -[[package]] -name = "nodeenv" -version = "1.10.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, - {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, -] - -[[package]] -name = "numpy" -version = "2.4.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["dev"] -files = [ - {file = "numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015"}, - {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40"}, - {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d"}, - {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502"}, - {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd"}, - {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5"}, - {file = "numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e"}, - {file = "numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e"}, - {file = "numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842"}, - {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8"}, - {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121"}, - {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e"}, - {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44"}, - {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d"}, - {file = "numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827"}, - {file = "numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a"}, - {file = "numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af"}, - {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c"}, - {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103"}, - {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83"}, - {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed"}, - {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959"}, - {file = "numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed"}, - {file = "numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf"}, - {file = "numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7"}, - {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93"}, - {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e"}, - {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40"}, - {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e"}, - {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392"}, - {file = "numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008"}, - {file = "numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8"}, - {file = "numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a"}, - {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b"}, - {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a"}, - {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"}, - {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252"}, - {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f"}, - {file = "numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc"}, - {file = "numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74"}, - {file = "numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113"}, - {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d"}, - {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d"}, - {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f"}, - {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0"}, - {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150"}, - {file = "numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871"}, - {file = "numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e"}, - {file = "numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f"}, - {file = "numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119"}, - {file = "numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0"}, -] - -[[package]] -name = "omnitils" -version = "1.4.6" -description = "Universal reusable Python utils for the modern human." -optional = false -python-versions = ">=3.10,<4.0" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -backoff = "^2.2.1" -click = "^8.3.0" -limits = "^5.6.0" -loguru = "^0.7.3" -pathvalidate = "^3.3.1" -pillow = "^12.0.0" -py7zr = "^1.0.0" -pydantic = "^2.12.3" -python-dateutil = "^2.9.0.post0" -PyYAML = "^6.0.3" -requests = "^2.32.5" -tomli = "^2.3.0" -tomlkit = "^0.13.3" -tqdm = "^4.67.1" -yarl = "^1.22.0" - -[package.source] -type = "git" -url = "https://github.com/pappnu/omnitils.git" -reference = "dev" -resolved_reference = "dab468c228d746a1832f421a092d902b4a3f5fb8" - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "paginate" -version = "0.5.7" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, - {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, -] - -[package.extras] -dev = ["pytest", "tox"] -lint = ["black"] - -[[package]] -name = "pathspec" -version = "1.0.4" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, - {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, -] - -[package.extras] -hyperscan = ["hyperscan (>=0.7)"] -optional = ["typing-extensions (>=4)"] -re2 = ["google-re2 (>=1.1)"] -tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] - -[[package]] -name = "pathvalidate" -version = "3.3.1" -description = "pathvalidate is a Python library to sanitize/validate a string such as filenames/file-paths/etc." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f"}, - {file = "pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177"}, -] - -[package.extras] -docs = ["Sphinx (>=2.4)", "sphinx_rtd_theme (>=1.2.2)", "urllib3 (<2)"] -readme = ["path (>=13,<18)", "readmemaker (>=1.2.0)"] -test = ["Faker (>=1.0.8)", "allpairspy (>=2)", "click (>=6.2)", "pytest (>=6.0.1)", "pytest-md-report (>=0.6.2)"] - -[[package]] -name = "pefile" -version = "2024.8.26" -description = "Python PE parsing module" -optional = false -python-versions = ">=3.6.0" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f"}, - {file = "pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632"}, -] - -[[package]] -name = "photoshop-python-api" -version = "0.24.1" -description = "Python API for Photoshop." -optional = false -python-versions = ">=3.10,<4.0" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -comtypes = "^1.4.15" -wheel = "^0.46.3" - -[package.source] -type = "git" -url = "https://github.com/pappnu/photoshop-python-api.git" -reference = "type-annotation" -resolved_reference = "4cd7f34986a17da959295dc698650d5e5b58e3f0" - -[[package]] -name = "pillow" -version = "12.2.0" -description = "Python Imaging Library (fork)" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"}, - {file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"}, - {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"}, - {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"}, - {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"}, - {file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"}, - {file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"}, - {file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"}, - {file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"}, - {file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"}, - {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"}, - {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"}, - {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"}, - {file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"}, - {file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"}, - {file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"}, - {file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"}, - {file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"}, - {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"}, - {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"}, - {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"}, - {file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"}, - {file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"}, - {file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"}, - {file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"}, - {file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"}, - {file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"}, - {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"}, - {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"}, - {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"}, - {file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"}, - {file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"}, - {file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"}, - {file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"}, - {file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"}, - {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"}, - {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"}, - {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"}, - {file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"}, - {file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"}, - {file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"}, - {file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"}, - {file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"}, - {file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"}, - {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"}, - {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"}, - {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"}, - {file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"}, - {file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"}, - {file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"}, - {file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"}, - {file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"}, - {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"}, - {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"}, - {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"}, - {file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"}, - {file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"}, - {file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"}, - {file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"}, - {file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.9.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, - {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "4.5.1" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, - {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "prompt-toolkit" -version = "3.0.51" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, - {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "propcache" -version = "0.4.1" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, - {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, - {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, - {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, - {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, - {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, - {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, - {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, - {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, - {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, - {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, - {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, - {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, - {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, - {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, - {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, - {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, - {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, - {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, - {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, - {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, - {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, - {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, - {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, - {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, - {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, - {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, - {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, - {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, - {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, - {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, - {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, - {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, - {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, - {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, - {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, - {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, - {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, - {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, - {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, - {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, - {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, - {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, - {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, - {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, - {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, - {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, - {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, - {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, - {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, - {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, -] - -[[package]] -name = "properdocs" -version = "1.6.7" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd"}, - {file = "properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -packaging = ">=20.5" -pathspec = ">=0.11.1" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] - -[[package]] -name = "psd-tools" -version = "1.14.2" -description = "Python package for working with Adobe Photoshop PSD files" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "psd_tools-1.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4d84744bf55bcc17bdcb43b87784dd0eb91758d1ff1fda042f4f0f2c5775141"}, - {file = "psd_tools-1.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05816681b0b976052e8c305162310a287eaf5897fbf253e442a9fcac8f4627d2"}, - {file = "psd_tools-1.14.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c815ca9a505f2680cd6edda1c08c05fdd2edf48a60847196d1256cb673175a"}, - {file = "psd_tools-1.14.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a6d9243ee27c4df103df63043290a57b5d39185bb7ada06d51b49e0d0781df6"}, - {file = "psd_tools-1.14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a015b2ab7bbf52bf8ed6bc671201252648e47a54975627cf01cd748f1c0336a2"}, - {file = "psd_tools-1.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e858a613ed0e7a7b95633fe773cb065321617eeb40cbca0cbccbc57a255154"}, - {file = "psd_tools-1.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:23d9a495aaded4caf554df72c51a066a49473c667714b08b9b39e448f32de6a4"}, - {file = "psd_tools-1.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aeaa742816c711a2e30f8896b8c07c9941c7b2d1b25ebcf1e4359f5d578c1ae8"}, - {file = "psd_tools-1.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e17a8d96ad42ed69fcd7f8c06b62158581925ae4070a7087f70c60e268ce39"}, - {file = "psd_tools-1.14.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84ff0ab83a2c9bff36a7e5b040973c3895d09460e6c0d51906e111c0cae78db0"}, - {file = "psd_tools-1.14.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35af03b0618c53c4f41683644f858c60c5fb4b9a323c65ddcf02c4b6b56ef482"}, - {file = "psd_tools-1.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a2321fa8df22535982faa9b2084b6055eb8297a9bc46c92709c46c9e3f7c6c39"}, - {file = "psd_tools-1.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f68ac8798fd2d634eb00e32307d3d0074877ce9bba8c41c4755f752cf0f39355"}, - {file = "psd_tools-1.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:cd78029e09f830765b9d56419db0443abd5b477d7f3a582a5128af78fdb6f211"}, - {file = "psd_tools-1.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:0097f41175efabc1739515cb1fb626c39879a382d07c04803118502802fca66c"}, - {file = "psd_tools-1.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0f40256c6fb985965abbd1eca51d7cbd3421842381e4c5f91c7aeaf8314eb817"}, - {file = "psd_tools-1.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23afcd57ad54f1632c1e9592df7a584a9f4891e5f43df8a9757b5a2c18c4e6a"}, - {file = "psd_tools-1.14.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:087a008a831ec810a0c6709cba4fadb5698fc70aa231a3a87ceda7b746085391"}, - {file = "psd_tools-1.14.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c1b9a79121c0f67656b14ee01b5c2adccdf3523ec242f0991601c1043b965db"}, - {file = "psd_tools-1.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ce526ef1f130a21c382cfc07995b0ecc692a0af0f55d812de1b90025f01ce1e"}, - {file = "psd_tools-1.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23a5b890ef459bd7265c3ff93614142549a1961558944514a231ee90a969e8b1"}, - {file = "psd_tools-1.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:fd77b5762698442c0b2465ab37d218e9bddb9e08ac78f94831b21d410ffaecea"}, - {file = "psd_tools-1.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:29184e35af61d109dab6503378f4aee057e37d5162813b2680247428d9f52460"}, - {file = "psd_tools-1.14.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:17f84d68a6b1aa785cfce884546ac4d86eb5e13d9a26d37c30237e356526de21"}, - {file = "psd_tools-1.14.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:b0031d859f80db5547d13fff7c60f046beaaa8b0463bcc548848117b15c784aa"}, - {file = "psd_tools-1.14.2-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddffe3216cc733dec2995f92e936e059291529d80c4c9c992554429e22c4d71a"}, - {file = "psd_tools-1.14.2-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:868c9607b93d07bc5723ab32733d0658fc187da2efe5e993e2f94e3b6d1f45c0"}, - {file = "psd_tools-1.14.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23ce7f52c6fc71dd06358f1e6ac77f668547e073a75ff9e681cef9ee9deaa90a"}, - {file = "psd_tools-1.14.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c98dc8bfa51e1293fbbfd3c6aa14d1b95b2eea5e87392f3c3c63455c7914e774"}, - {file = "psd_tools-1.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b041214fcf52109db0e11389cd6d66aabf9d9c92f21d21259d0b72062efca3d2"}, - {file = "psd_tools-1.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba45ff7a37ba00e10deb53ff1617a8f1b16baa05eadf40d808c12b3f4e817af7"}, - {file = "psd_tools-1.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:ade664b0c1320ea81f3b1d31c0a50ba2317a9d66ae064d32f384310e95138585"}, - {file = "psd_tools-1.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:1e90a329e0cd72b706e56bb0a5d4eadfbd969097c1a5cc2026d5720860c93e49"}, -] - -[package.dependencies] -attrs = ">=23.0.0" -numpy = "*" -Pillow = ">=10.3.0" -typing-extensions = ">=4.0" - -[package.extras] -composite = ["aggdraw (>=1.3.16) ; sys_platform != \"win32\"", "aggdraw (>=1.3.16,<1.4.1) ; sys_platform == \"win32\" and python_version < \"3.11\"", "aggdraw (>=1.4.1) ; sys_platform == \"win32\" and python_version >= \"3.11\"", "scikit-image", "scipy"] - -[[package]] -name = "psutil" -version = "7.2.2" -description = "Cross-platform lib for process and system monitoring." -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b"}, - {file = "psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea"}, - {file = "psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63"}, - {file = "psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312"}, - {file = "psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b"}, - {file = "psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9"}, - {file = "psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00"}, - {file = "psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9"}, - {file = "psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a"}, - {file = "psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf"}, - {file = "psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1"}, - {file = "psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841"}, - {file = "psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486"}, - {file = "psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979"}, - {file = "psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9"}, - {file = "psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e"}, - {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8"}, - {file = "psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc"}, - {file = "psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988"}, - {file = "psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee"}, - {file = "psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372"}, -] -markers = {main = "sys_platform != \"cygwin\""} - -[package.extras] -dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "psleak", "pylint", "pyperf", "pypinfo", "pyreadline3 ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] -test = ["psleak", "pytest", "pytest-instafail", "pytest-xdist", "pywin32 ; os_name == \"nt\" and implementation_name != \"pypy\"", "setuptools", "wheel ; os_name == \"nt\" and implementation_name != \"pypy\"", "wmi ; os_name == \"nt\" and implementation_name != \"pypy\""] - -[[package]] -name = "py7zr" -version = "1.1.0" -description = "Pure python 7-zip library" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "py7zr-1.1.0-py3-none-any.whl", hash = "sha256:5921bc30fb72b5453aafe3b2183664c08ef508cde2655988d5e9bd6078353ef7"}, - {file = "py7zr-1.1.0.tar.gz", hash = "sha256:087b1a94861ad9eb4d21604f6aaa0a8986a7e00580abd79fedd6f82fecf0592c"}, -] - -[package.dependencies] -brotli = {version = ">=1.2.0", markers = "platform_python_implementation == \"CPython\""} -brotlicffi = {version = ">=1.2.0.0", markers = "platform_python_implementation == \"PyPy\""} -inflate64 = ">=1.0.4" -multivolumefile = ">=0.2.3" -psutil = {version = "*", markers = "sys_platform != \"cygwin\""} -pybcj = ">=1.0.6" -pycryptodomex = ">=3.20.0" -pyppmd = ">=1.3.1" -texttable = "*" - -[package.extras] -check = ["black (>=25.1.0)", "check-manifest", "flake8 (<8)", "flake8-black (>=0.3.6)", "flake8-deprecated", "flake8-isort", "isort (>=7.0.0)", "lxml", "mypy (>=1.17.0)", "mypy_extensions (>=1.1.0)", "pygments", "pylint", "readme-renderer", "twine", "types-psutil"] -debug = ["pytest", "pytest-leaks", "pytest-profiling"] -docs = ["docutils", "sphinx (>=8.0.0)", "sphinx-a4doc", "sphinx-py3doc-enhanced-theme"] -test = ["coverage[toml] (>=7.10.7)", "coveralls (>=4.0.2)", "py-cpuinfo", "pytest", "pytest-benchmark", "pytest-cov", "pytest-httpserver", "pytest-remotedata", "pytest-timeout", "requests"] - -[[package]] -name = "pybcj" -version = "1.0.7" -description = "bcj filter library" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "pybcj-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:618ec7345775c306d83527750e2d0ab3f42ffdc5ad6282f62f88cb53c9b2b679"}, - {file = "pybcj-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7e7faa1b0f7d894685e4567dd41268b93df89cff347ebfdfdc48b4bc0d68cb2"}, - {file = "pybcj-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cd4b2d05272df605d5bdb54b3386985a2b074b4d97072da944736abd639fdee"}, - {file = "pybcj-1.0.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eb5cd6f52df8857a8d9de594ca28a71683169b9de5af7e727c0e510aedb4550"}, - {file = "pybcj-1.0.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9d10760356b7d254b7b04ff38e052d5229c5f5a69a5514c9c31cb1dbb7d7f82"}, - {file = "pybcj-1.0.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1696d9b50971e317f72802bebd18f9b53684892ad3c43e0258f34e0a01738484"}, - {file = "pybcj-1.0.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e3d91b5dfdb0a200545b68145d81dae4edd2c385d89643dc45d6d01291f5c04"}, - {file = "pybcj-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:74df8f4c897f937105e8cd830df3b4ddf65ab5b5ba3e63cd6e3aeb3f4ecb0864"}, - {file = "pybcj-1.0.7-cp310-cp310-win_arm64.whl", hash = "sha256:dc121ecb26fdc1a4173a20b3c7cca5d8cc81494b485d4b44a62ed8448f8c796e"}, - {file = "pybcj-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:906ee707e89302253813a123f90a36d94d1f3c8785a4a1b853b31ac67296857a"}, - {file = "pybcj-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93da8503161fd51e01843aca031444fd46dce83e8a8bb4972f0256d6b3d280d3"}, - {file = "pybcj-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb4a52cd573f4359a89fd3a4a1d82c914f8b758a5c9f16cd5dd13fb8aa24436"}, - {file = "pybcj-1.0.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6d9a26fa9e627eb2fbba0f5b376ab42246bebdaf38cf437e384a6b7e3d78e23"}, - {file = "pybcj-1.0.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47207b69997fdc39e91a66812477506267964284b7d45fed68876dd74323d44f"}, - {file = "pybcj-1.0.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b33d6ef1de94720f4856e198bd2b8eca978015ed685aef4138755ba3910eb963"}, - {file = "pybcj-1.0.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:053c7cda499a8934151d0c915b6efce8e53fa6b47d162434a5b24afef7af5d17"}, - {file = "pybcj-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:555e90270d665d94cd34d2e50b096f68dba6baf7035ae11ac65c2bc126f8cef7"}, - {file = "pybcj-1.0.7-cp311-cp311-win_arm64.whl", hash = "sha256:22bdb390da9a4e38b2191070a62b88ad52edc3f6e12fe7eea278217ccfdbc02c"}, - {file = "pybcj-1.0.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d39787b85678d2ab1c67e2f21dd2e71be851f08e5c9fe619c605877b57dd529d"}, - {file = "pybcj-1.0.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8cd5dd166093a1fb146fb78859aac0f00b45db6c11074705517bc72a940a1c8e"}, - {file = "pybcj-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82152e8641f5ce68638f3504227065f27b6b1efe96479ffbf20d81530c220062"}, - {file = "pybcj-1.0.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2095b45d05f8d19430167b7df52ebd920df854ab8d064bae879df0a4611374b3"}, - {file = "pybcj-1.0.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b400c9f48faed01edb7f0df54b4354270325c886e785f31c866c581a46023b"}, - {file = "pybcj-1.0.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8210e51a2d4e5ccb4fdb75a1e692dd8c121858b589026bb28988ed7ffdb7ed00"}, - {file = "pybcj-1.0.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6c3fe420083186ae2e5f75c23aa6563dcb030b8fc188d00778ce374d1df1984"}, - {file = "pybcj-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:c435062d66364f85674a639541980000e37657b98367a2ce2699514e44b8ab05"}, - {file = "pybcj-1.0.7-cp312-cp312-win_arm64.whl", hash = "sha256:3f74fd70b08092e58b1ee13c67fbf9de63d73eb1c61ab06670a0d7161efeb252"}, - {file = "pybcj-1.0.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d5e0feeeee3a659b30d7afbd89bf41da84e8c8fe13e5b997457e799a70fa550"}, - {file = "pybcj-1.0.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:60baaf9f0da31438515a401145f920f75f2ec7d511165bbf57475467af72a3e6"}, - {file = "pybcj-1.0.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c6e726618c3d43c730df5a4067fc19653b360f89c2f72f4323dae10d324552"}, - {file = "pybcj-1.0.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a5fcd40a4ce8f0c5428032ec5db9f03abb42214b993886cdf558e5644de636e"}, - {file = "pybcj-1.0.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:029112255c22de66e0117bec932c8be341ed20c56dcf6a961c14689f7f0ce772"}, - {file = "pybcj-1.0.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6492bcef5cb6883506b9dce5e48cb81217407305957b0e602c6c689c60097c5e"}, - {file = "pybcj-1.0.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22a7f4a51d36a1abb67a61e93248f997eb2be278f788d681096f5044ae18b4f9"}, - {file = "pybcj-1.0.7-cp313-cp313-win_amd64.whl", hash = "sha256:ebcce9b419fe5d3109150a1fab0fc93a64d5cd812ca44c5ddb7d4f7128ea369f"}, - {file = "pybcj-1.0.7-cp313-cp313-win_arm64.whl", hash = "sha256:bc6acf0320976b4e31bdc0e59b16689083d5c346a6c62ac4f799685d1cc5cf27"}, - {file = "pybcj-1.0.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:293f951eb3877840acab79f0c4dcfc06eab03e087cb9e4c004ec058e093acb1d"}, - {file = "pybcj-1.0.7-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3ae64960904f362d33ffca10715803afd9f9a6a2a592f871dcb335acf82edf29"}, - {file = "pybcj-1.0.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:70aa4476910f982025f878e598c136559a6d78b59fc20ba8b4b592306cde6051"}, - {file = "pybcj-1.0.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79ce3ce9b380b1b75c5e490abc3888ee3b5b2d28c22b59618674bf410b9cee16"}, - {file = "pybcj-1.0.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc73ee1bc064d6f97dfd66051d3859b32e1b6a4cf89b077f5c8ef6c2dccb71af"}, - {file = "pybcj-1.0.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5b0d13f41a9f85b3f95dd5dc7bfaa9539e80f8ae60a96db7f34c07ed732e4a82"}, - {file = "pybcj-1.0.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:597d7e9a8cbb30a6ed54d552fd3436edb32bbb821a7ac2fa8e5c7ebd1f7e0e93"}, - {file = "pybcj-1.0.7-cp314-cp314-win_amd64.whl", hash = "sha256:4603cc41ceb1236abe9169e2ead344140be5d2c3ac01bbc5e44cb1b13078a009"}, - {file = "pybcj-1.0.7-cp314-cp314-win_arm64.whl", hash = "sha256:adf985e816ddd59f3bf6d1066b7fa89de7424a4f19f3725f9976284cabe54e28"}, - {file = "pybcj-1.0.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bbd835873de147481d62c11ba91a75d26a72df1142de3516b384b04e5a1db6d"}, - {file = "pybcj-1.0.7-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b7576b25d7b01a953e2f987e77cef93c001db7b95924a5541d5a55f9195a7e89"}, - {file = "pybcj-1.0.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57b36920498f82ca6a325a98b13e0fbff8fc29bade7aaaddc7d284640bffd87d"}, - {file = "pybcj-1.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac2a46faf41e373939f6d3e6a5aa2121bf09e2446972c14a8e5d1ca3b0f8130"}, - {file = "pybcj-1.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c7d6156ef2b4e8ecd450b62dc4cc3a89e8dda307cb26288b670952ef0df3a37"}, - {file = "pybcj-1.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0fe306213de1e764abae63c06ae5a4e9a83632f62612805f1f883b8d74431901"}, - {file = "pybcj-1.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00448182d535cca37e8f24d892d480fa86f80ff20c79385f6eca75f118efcbb4"}, - {file = "pybcj-1.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:7e94aa712d0fa5fda9875828441755ece7121fc3f8c5cc3bc8ee92d05b853590"}, - {file = "pybcj-1.0.7-cp314-cp314t-win_arm64.whl", hash = "sha256:16fd4e51a5556d1f38d7ba5d1fab588bfb60ae23d2299b5179779bf9900adf71"}, - {file = "pybcj-1.0.7.tar.gz", hash = "sha256:72d64574069ffb0a800020668376b7ebd7adea159adbf4d35f8effc62f0daa67"}, -] - -[package.extras] -check = ["check-manifest", "flake8 (<8)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=1.10.0)", "pygments", "readme-renderer"] -test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "pyclean" -version = "3.6.0" -description = "Pure Python cross-platform pyclean. Clean up your Python bytecode." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pyclean-3.6.0-py3-none-any.whl", hash = "sha256:3e5f9c83f2472df5bdafa78ed533bd38690756e0a7b3b6f11c8b29c9affc728b"}, - {file = "pyclean-3.6.0.tar.gz", hash = "sha256:a46c179be187999d50d2d3362b2c94856e60f77de32ca7ec2db4eeb16bc39199"}, -] - -[[package]] -name = "pycparser" -version = "3.0" -description = "C parser in Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "platform_python_implementation == \"PyPy\" and implementation_name != \"PyPy\"" -files = [ - {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, - {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, -] - -[[package]] -name = "pycryptodomex" -version = "3.23.0" -description = "Cryptographic library for Python" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] -files = [ - {file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"}, - {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"}, - {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"}, - {file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"}, - {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"}, - {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"}, - {file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"}, - {file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"}, - {file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"}, - {file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"}, - {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"}, - {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"}, - {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"}, - {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"}, - {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"}, - {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"}, - {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"}, - {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"}, - {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"}, - {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"}, - {file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"}, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, -] - -[package.dependencies] -typing-extensions = ">=4.14.1" - -[[package]] -name = "pydantic-settings" -version = "2.13.1" -description = "Settings management using Pydantic" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237"}, - {file = "pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025"}, -] - -[package.dependencies] -pydantic = ">=2.7.0" -python-dotenv = ">=0.21.0" -typing-inspection = ">=0.4.0" - -[package.extras] -aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] -azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] -gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] -toml = ["tomli (>=2.0.1)"] -yaml = ["pyyaml (>=6.0.1)"] - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyinstaller" -version = "6.19.0" -description = "PyInstaller bundles a Python application and all its dependencies into a single package." -optional = false -python-versions = "<3.15,>=3.8" -groups = ["dev"] -files = [ - {file = "pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134"}, - {file = "pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11"}, - {file = "pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf"}, - {file = "pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122"}, - {file = "pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63"}, - {file = "pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe"}, - {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83"}, - {file = "pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6"}, - {file = "pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2"}, - {file = "pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33"}, - {file = "pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea"}, - {file = "pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865"}, -] - -[package.dependencies] -altgraph = "*" -macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} -packaging = ">=22.0" -pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""} -pyinstaller-hooks-contrib = ">=2026.0" -pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} -setuptools = ">=42.0.0" - -[package.extras] -completion = ["argcomplete"] -hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] - -[[package]] -name = "pyinstaller-hooks-contrib" -version = "2026.4" -description = "Community maintained hooks for PyInstaller" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pyinstaller_hooks_contrib-2026.4-py3-none-any.whl", hash = "sha256:1de1a5e49a878122010b88c7e295502bc69776c157c4a4dc78741a4e6178b00f"}, - {file = "pyinstaller_hooks_contrib-2026.4.tar.gz", hash = "sha256:766c281acb1ecc32e21c8c667056d7ebf5da0aabd5e30c219f9c2a283620eeaa"}, -] - -[package.dependencies] -packaging = ">=22.0" -setuptools = ">=42.0.0" - -[[package]] -name = "pymdown-extensions" -version = "10.21.2" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638"}, - {file = "pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc"}, -] - -[package.dependencies] -markdown = ">=3.6" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.19.1)"] - -[[package]] -name = "pyparsing" -version = "3.3.2" -description = "pyparsing - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, - {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyppmd" -version = "1.3.1" -description = "PPMd compression/decompression library" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "pyppmd-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:041f46fbeb0a59888c0a94d6b9a557c652935633a104be1c31c12de491b5f448"}, - {file = "pyppmd-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9512a8b39740923559c26eb16266bf8b70d4eab6ad27a9b39cd2465e60e0acfa"}, - {file = "pyppmd-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8966f26b91ba7cdff3cfec5512d39d1f8bf4a8dbb75c44085e33b564566fea66"}, - {file = "pyppmd-1.3.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d1cff657e85655c67426c29c90c78a6210148b207993e643fc351c72c60d188"}, - {file = "pyppmd-1.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9de2cdcc3932e7c23a54beb48dfe1b5ab7b4aedd5ffaae1e4871bd213d630cb3"}, - {file = "pyppmd-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e1985461219c30d4576070b7e2de718dbb6f32637d1e658d25f838dfda2a4bb"}, - {file = "pyppmd-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20d9d1aa4d0f32118c8094c212c66b7af50e55f47e7c6dffa5f35a8ac391faca"}, - {file = "pyppmd-1.3.1-cp310-cp310-win32.whl", hash = "sha256:44d25e7dede2abb614bc023fe87835365fdd5865981c2273b70bfad71b84db29"}, - {file = "pyppmd-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:1e503a28c9a275d31f24af9b735d2cca543b62f438b064e2833e9833e758bdbc"}, - {file = "pyppmd-1.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:3fb3708d7b2b38e2999385a2f02c8e68e0f5a364d94f94e475e2e8b09e9338fc"}, - {file = "pyppmd-1.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdf55aa6ee7aef492f6896464e7a5a528f8615bb9e435f55bc8dff226fcc8292"}, - {file = "pyppmd-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa820aac385ac4ee57160b26d92862c69d31c08f92272dbef05fe8e619cea8d1"}, - {file = "pyppmd-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e16593ba4ca0a85821ae698ef06847a52937662f5ce1b130c39cca2979a4e8cd"}, - {file = "pyppmd-1.3.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:486dde2294ff9b30465ab5bb0f213b20bd5ac0e4adf21be801a1ceb29aa75d9d"}, - {file = "pyppmd-1.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89730cf026416ae2546c92738966ecf117c8176d52c229ad621a61c34643818b"}, - {file = "pyppmd-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e77f5a6770950d464b50da760d53e67bce308a3abc8e3bd51db620b3f8cf1fa8"}, - {file = "pyppmd-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8e3bf8deef44f8e03612689a6067a4a3dd7e50d2ef00af4cf987c59b62ff3006"}, - {file = "pyppmd-1.3.1-cp311-cp311-win32.whl", hash = "sha256:a3509b3f881409ebc5522942438108c48a78f8df88bcf3f9d907b74131b9431c"}, - {file = "pyppmd-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:23b83799f33f9a24577f22e092b0feecda8cd1ea33871ad8610a58629874f7bc"}, - {file = "pyppmd-1.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:234489036a1758670655d1ceafd4caeb93b858bd4c0ca39686837d38aef044c0"}, - {file = "pyppmd-1.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3faa58ab2ebe3b13ec23b1904639d687fb727270d2962fd2d239ca00fd6eb865"}, - {file = "pyppmd-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27703f041ee96912a5410fd3ce31c5cde32f9323bd67f72f100bd960ee67bf13"}, - {file = "pyppmd-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e773d8353b36f7e7973a43526993fb276b98a97839cb5dc8f4e6465ad873f41a"}, - {file = "pyppmd-1.3.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b1883accf840cb0b711785d353f8548853a1401d381da007c0aec362f3ffac"}, - {file = "pyppmd-1.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bd6d179ad39b6191ca0cbe62fb9592f33f49277b4384ad7bc5eb0e6ca27ebee"}, - {file = "pyppmd-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:806cf8d33606e44bf5ff5786c57891f57993f1eef1c763da3c58ea97de3a13c8"}, - {file = "pyppmd-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1826cbce9a2c944aa08df79310a7e6d4a61fd20636b6dff64a77ea4bc43da30f"}, - {file = "pyppmd-1.3.1-cp312-cp312-win32.whl", hash = "sha256:d3ff96671319318d941dd34300d641745048e8a3251b077bddf98652d6ddc513"}, - {file = "pyppmd-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:c8c1ad39e7ebde71bf5a54cf61f489bf4790f1dd0beb70dc2e8f5ad3329d7ca7"}, - {file = "pyppmd-1.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:391b2bf76d7dc45b343781754d0b734dcbf539b92667986a343f5488c4bf9ca0"}, - {file = "pyppmd-1.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4b4edb3e9619fd0bc39c1a07eb03e8731db833a93b23134f36c7ef581a94b37a"}, - {file = "pyppmd-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8b5c813e462c91048b88e2adfbcc0c69f2c905f70097001d32066f86f675bd4"}, - {file = "pyppmd-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e8d372d9fac382183e0371cf0c2d736b494b1857a1befe98d563342b1205265b"}, - {file = "pyppmd-1.3.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b765ae21f7ed2f4ea8f32bfd9e3a4a8d738e73fc8f8dcddec9cbe2c898d60be"}, - {file = "pyppmd-1.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd00522ddfcc292304577386b6c217758c0c10e1fb9ce7877ad7d3b7b821a808"}, - {file = "pyppmd-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3de62099ff2ca876c2d39bc547bcba6f7b878988663abd782a5bad4edac3bb44"}, - {file = "pyppmd-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:011f845de195d60fe973a635a1f4be981b7d80f357a8acb1b2d83bdf5087c808"}, - {file = "pyppmd-1.3.1-cp313-cp313-win32.whl", hash = "sha256:7d61bd01f25289b6ae54832db4254602fb0c6d105f6e6bf0aee39b803b698b98"}, - {file = "pyppmd-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:df8d84ab72381058a964ba66e5e81ed52dbd0b5ad734a5ef8353452983506098"}, - {file = "pyppmd-1.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:124a04aab6936ba011f9ad57067798c7f052fdb1848b0cc4318606eea55475e6"}, - {file = "pyppmd-1.3.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ea53f71ac16e113599b8441a9d8b6dcd71cfdf15cdb33ba5151810b8e656c5ec"}, - {file = "pyppmd-1.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:985c8703b53e5f68fe17f653e96748d60b1f855676c852a6e67cd472eb853671"}, - {file = "pyppmd-1.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33daa996ad5203c665c0b55aff6329817b2cb7fa95f2c33a2e83ed0121b400cb"}, - {file = "pyppmd-1.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b49870c6d7194f6eb80f30335ca03596d153e02fcde2c222e4f1202ac25f7fcf"}, - {file = "pyppmd-1.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:385e92c97c42e8a6f0bfc0e4acfc6c074cb1ba3a2f650f292696dd9f19e2e603"}, - {file = "pyppmd-1.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017a1e2903f1c3147a1046db486990d401e8a25eb52c320b1fc2fb3e7b83cbeb"}, - {file = "pyppmd-1.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f2770a4b777c0c5236b3d9294b7bf4bc15538c95d45b2079eb8ebc1298e62e37"}, - {file = "pyppmd-1.3.1-cp314-cp314-win32.whl", hash = "sha256:b9d54cd59ce97f2ba57be1da91b3d874d129faca21c9565d7afec111f942e6a1"}, - {file = "pyppmd-1.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0e64247618cb150d2909beb0137da3084fef1d3479b4cc73b5b47fda7611abf9"}, - {file = "pyppmd-1.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:d354d6e551d2630b0ac98f27e3ad63e86cdcac9ce2115b5dfe46e2c9d3f4e82c"}, - {file = "pyppmd-1.3.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2d77ed79662c32e2551748d59763cfe3dcd10855bf3495937e3d5e5917507818"}, - {file = "pyppmd-1.3.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6a1f92b94635c23d85270bb26db25cc0db544e436af86efc1cf58302d71d5af1"}, - {file = "pyppmd-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:610f214f2405e27eb5a3dad7fa15f385cfc42141a01cda71995d9c1e0b09fab9"}, - {file = "pyppmd-1.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae81a14895498d9a23429d92114c98da478b74b8e33251527d7cff3e01c09de0"}, - {file = "pyppmd-1.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1683e6d1ba09e377e0ae02de3a518191a3d63ccdb0b6037c74e6ddf577b5644"}, - {file = "pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e4d74fa0f3531e9dadc56e0ace41bce82d3c0babed47b3f224101dc0dbde7287"}, - {file = "pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f8d6375b18b9c79127fee0885cfd52e2e983edb67041464309426571d38dcd4"}, - {file = "pyppmd-1.3.1-cp314-cp314t-win32.whl", hash = "sha256:de87f7acd575fb07a4ff42d41bcc071570fe759a36f345f1f54f574ecfccfc5b"}, - {file = "pyppmd-1.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:76e4800aa67292b4cc80058fd29b39e02a5dded721af9fe5654f356ef24307f4"}, - {file = "pyppmd-1.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e066cbf1d335fe20480cd8ecc10848ba78d99fe6d1e44ea00def48feaf46afdf"}, - {file = "pyppmd-1.3.1.tar.gz", hash = "sha256:ced527f08ade4408c1bfc5264e9f97ffac8d221c9d13eca4f35ec1ec0c7b6b2e"}, -] - -[package.extras] -check = ["check-manifest", "flake8", "flake8-black", "flake8-isort", "mypy (>=1.10.0)", "pygments", "readme-renderer"] -docs = ["sphinx", "sphinx_rtd_theme"] -fuzzer = ["atheris", "hypothesis"] -test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "pyside6" -version = "6.11.0" -description = "Python bindings for the Qt cross-platform application and UI framework" -optional = false -python-versions = "<3.15,>=3.10" -groups = ["main"] -files = [ - {file = "pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f"}, - {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00"}, - {file = "pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e"}, - {file = "pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc"}, - {file = "pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e"}, -] - -[package.dependencies] -PySide6_Addons = "6.11.0" -PySide6_Essentials = "6.11.0" -shiboken6 = "6.11.0" - -[[package]] -name = "pyside6-addons" -version = "6.11.0" -description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" -optional = false -python-versions = "<3.15,>=3.10" -groups = ["main"] -files = [ - {file = "pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182"}, - {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35"}, - {file = "pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753"}, - {file = "pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e"}, - {file = "pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8"}, -] - -[package.dependencies] -PySide6_Essentials = "6.11.0" -shiboken6 = "6.11.0" - -[[package]] -name = "pyside6-essentials" -version = "6.11.0" -description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" -optional = false -python-versions = "<3.15,>=3.10" -groups = ["main"] -files = [ - {file = "pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01"}, - {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849"}, - {file = "pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e"}, - {file = "pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf"}, - {file = "pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187"}, -] - -[package.dependencies] -shiboken6 = "6.11.0" - -[[package]] -name = "pytest" -version = "9.0.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1.0.1" -packaging = ">=22" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-discovery" -version = "1.2.1" -description = "Python interpreter discovery" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "python_discovery-1.2.1-py3-none-any.whl", hash = "sha256:b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502"}, - {file = "python_discovery-1.2.1.tar.gz", hash = "sha256:180c4d114bff1c32462537eac5d6a332b768242b76b69c0259c7d14b1b680c9e"}, -] - -[package.dependencies] -filelock = ">=3.15.4" -platformdirs = ">=4.3.6,<5" - -[package.extras] -docs = ["furo (>=2025.12.19)", "sphinx (>=9.1)", "sphinx-autodoc-typehints (>=3.6.3)", "sphinxcontrib-mermaid (>=2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] - -[[package]] -name = "python-dotenv" -version = "1.2.2" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, - {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pywin32" -version = "311" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, - {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, - {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, - {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, - {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, - {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, - {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, - {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, - {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, - {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, - {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, - {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, - {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, - {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, - {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, - {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, - {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, - {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, - {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, - {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, -] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.3" -description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, - {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, - {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, - {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, - {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, - {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, - {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, - {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, - {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, - {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, - {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, - {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, - {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, - {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -description = "A custom YAML tag for referencing environment variables in YAML files." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, - {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "questionary" -version = "2.1.1" -description = "Python library to build pretty command line user prompts ⭐️" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59"}, - {file = "questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d"}, -] - -[package.dependencies] -prompt_toolkit = ">=2.0,<4.0" - -[[package]] -name = "requests" -version = "2.33.1" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, - {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, -] - -[package.dependencies] -certifi = ">=2023.5.7" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.26,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] - -[[package]] -name = "rich" -version = "14.3.3" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -groups = ["main"] -files = [ - {file = "rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d"}, - {file = "rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "setuptools" -version = "82.0.1" -description = "Most extensible Python build backend with support for C/C++ extension modules" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, - {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] - -[[package]] -name = "shiboken6" -version = "6.11.0" -description = "Python/C++ bindings helper module" -optional = false -python-versions = "<3.15,>=3.10" -groups = ["main"] -files = [ - {file = "shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b"}, - {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053"}, - {file = "shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9"}, - {file = "shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1"}, - {file = "shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4"}, -] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "smmap" -version = "5.0.3" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f"}, - {file = "smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c"}, -] - -[[package]] -name = "soupsieve" -version = "2.8.3" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, - {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, -] - -[[package]] -name = "termcolor" -version = "3.3.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5"}, - {file = "termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - -[[package]] -name = "texttable" -version = "1.7.0" -description = "module to create simple ASCII tables" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, - {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, -] - -[[package]] -name = "tomli" -version = "2.4.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, - {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076"}, - {file = "tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c"}, - {file = "tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc"}, - {file = "tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049"}, - {file = "tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e"}, - {file = "tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a"}, - {file = "tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9"}, - {file = "tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585"}, - {file = "tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1"}, - {file = "tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917"}, - {file = "tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9"}, - {file = "tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54"}, - {file = "tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897"}, - {file = "tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d"}, - {file = "tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5"}, - {file = "tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd"}, - {file = "tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36"}, - {file = "tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf"}, - {file = "tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662"}, - {file = "tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15"}, - {file = "tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba"}, - {file = "tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6"}, - {file = "tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7"}, - {file = "tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4"}, - {file = "tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d"}, - {file = "tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c"}, - {file = "tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f"}, - {file = "tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8"}, - {file = "tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26"}, - {file = "tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396"}, - {file = "tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe"}, - {file = "tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f"}, -] - -[[package]] -name = "tomlkit" -version = "0.13.3" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"}, - {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"}, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf"}, - {file = "tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] -discord = ["requests"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "types-pywin32" -version = "311.0.0.20260402" -description = "Typing stubs for pywin32" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "types_pywin32-311.0.0.20260402-py3-none-any.whl", hash = "sha256:4db644fcf40ee85a3ee2551f110d009e427c01569ed4670bb53cfe999df0929f"}, - {file = "types_pywin32-311.0.0.20260402.tar.gz", hash = "sha256:637f041065f02fb49cbaba530ae8cf2e483b5d2c145a9bf97fd084c3e913c7e3"}, -] - -[[package]] -name = "types-requests" -version = "2.33.0.20260402" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "types_requests-2.33.0.20260402-py3-none-any.whl", hash = "sha256:c98372d7124dd5d10af815ee25c013897592ff92af27b27e22c98984102c3254"}, - {file = "types_requests-2.33.0.20260402.tar.gz", hash = "sha256:1bdd3ada9b869741c5c4b887d2c8b4e38284a1449751823b5ebbccba3eefd9da"}, -] - -[package.dependencies] -urllib3 = ">=2" - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, - {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, -] - -[package.dependencies] -typing-extensions = ">=4.12.0" - -[[package]] -name = "urllib3" -version = "2.6.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, -] - -[package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] - -[[package]] -name = "virtualenv" -version = "21.2.0" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f"}, - {file = "virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = {version = ">=3.24.2,<4", markers = "python_version >= \"3.10\""} -platformdirs = ">=3.9.1,<5" -python-discovery = ">=1" - -[[package]] -name = "watchdog" -version = "6.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "wcmatch" -version = "10.1" -description = "Wildcard/glob file name matcher." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a"}, - {file = "wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af"}, -] - -[package.dependencies] -bracex = ">=2.1.1" - -[[package]] -name = "wcwidth" -version = "0.6.0" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad"}, - {file = "wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159"}, -] - -[[package]] -name = "wheel" -version = "0.46.3" -description = "Command line tool for manipulating wheel files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d"}, - {file = "wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803"}, -] - -[package.dependencies] -packaging = ">=24.0" - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=77)"] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -description = "A small Python utility to set file creation time on Windows" -optional = false -python-versions = ">=3.5" -groups = ["main"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, - {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, -] - -[package.extras] -dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] - -[[package]] -name = "wrapt" -version = "2.1.2" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c"}, - {file = "wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f"}, - {file = "wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb"}, - {file = "wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e"}, - {file = "wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba"}, - {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f"}, - {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394"}, - {file = "wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45"}, - {file = "wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d"}, - {file = "wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71"}, - {file = "wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc"}, - {file = "wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb"}, - {file = "wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d"}, - {file = "wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894"}, - {file = "wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842"}, - {file = "wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8"}, - {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6"}, - {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9"}, - {file = "wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15"}, - {file = "wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b"}, - {file = "wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1"}, - {file = "wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a"}, - {file = "wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9"}, - {file = "wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748"}, - {file = "wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e"}, - {file = "wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8"}, - {file = "wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c"}, - {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c"}, - {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1"}, - {file = "wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2"}, - {file = "wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0"}, - {file = "wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63"}, - {file = "wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf"}, - {file = "wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b"}, - {file = "wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e"}, - {file = "wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb"}, - {file = "wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca"}, - {file = "wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267"}, - {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f"}, - {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8"}, - {file = "wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413"}, - {file = "wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6"}, - {file = "wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1"}, - {file = "wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf"}, - {file = "wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b"}, - {file = "wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18"}, - {file = "wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d"}, - {file = "wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015"}, - {file = "wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92"}, - {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf"}, - {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67"}, - {file = "wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a"}, - {file = "wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd"}, - {file = "wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f"}, - {file = "wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679"}, - {file = "wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9"}, - {file = "wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9"}, - {file = "wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e"}, - {file = "wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c"}, - {file = "wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a"}, - {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90"}, - {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586"}, - {file = "wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19"}, - {file = "wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508"}, - {file = "wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04"}, - {file = "wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575"}, - {file = "wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb"}, - {file = "wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22"}, - {file = "wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596"}, - {file = "wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044"}, - {file = "wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b"}, - {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf"}, - {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2"}, - {file = "wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3"}, - {file = "wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7"}, - {file = "wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5"}, - {file = "wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00"}, - {file = "wrapt-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5e0fa9cc32300daf9eb09a1f5bdc6deb9a79defd70d5356ba453bcd50aef3742"}, - {file = "wrapt-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:710f6e5dfaf6a5d5c397d2d6758a78fecd9649deb21f1b645f5b57a328d63050"}, - {file = "wrapt-2.1.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:305d8a1755116bfdad5dda9e771dcb2138990a1d66e9edd81658816edf51aed1"}, - {file = "wrapt-2.1.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0d8fc30a43b5fe191cf2b1a0c82bab2571dadd38e7c0062ee87d6df858dd06e"}, - {file = "wrapt-2.1.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5d516e22aedb7c9c1d47cba1c63160b1a6f61ec2f3948d127cd38d5cfbb556f"}, - {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:45914e8efbe4b9d5102fcf0e8e2e3258b83a5d5fba9f8f7b6d15681e9d29ffe0"}, - {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:478282ebd3795a089154fb16d3db360e103aa13d3b2ad30f8f6aac0d2207de0e"}, - {file = "wrapt-2.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3756219045f73fb28c5d7662778e4156fbd06cf823c4d2d4b19f97305e52819c"}, - {file = "wrapt-2.1.2-cp39-cp39-win32.whl", hash = "sha256:b8aefb4dbb18d904b96827435a763fa42fc1f08ea096a391710407a60983ced8"}, - {file = "wrapt-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e5aeab8fe15c3dff75cfee94260dcd9cded012d4ff06add036c28fae7718593b"}, - {file = "wrapt-2.1.2-cp39-cp39-win_arm64.whl", hash = "sha256:f069e113743a21a3defac6677f000068ebb931639f789b5b226598e247a4c89e"}, - {file = "wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8"}, - {file = "wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e"}, -] - -[package.extras] -dev = ["pytest", "setuptools"] - -[[package]] -name = "yarl" -version = "1.23.0" -description = "Yet another URL library" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cff6d44cb13d39db2663a22b22305d10855efa0fa8015ddeacc40bc59b9d8107"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c53f8347cd4200f0d70a48ad059cabaf24f5adc6ba08622a23423bc7efa10d"}, - {file = "yarl-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a6940a074fb3c48356ed0158a3ca5699c955ee4185b4d7d619be3c327143e05"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed5f69ce7be7902e5c70ea19eb72d20abf7d725ab5d49777d696e32d4fc1811d"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:389871e65468400d6283c0308e791a640b5ab5c83bcee02a2f51295f95e09748"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dda608c88cf709b1d406bdfcd84d8d63cff7c9e577a403c6108ce8ce9dcc8764"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c4fe09e0780c6c3bf2b7d4af02ee2394439d11a523bbcf095cf4747c2932007"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c9921eb8bd12633b41ad27686bbb0b1a2a9b8452bfdf221e34f311e9942ed4"}, - {file = "yarl-1.23.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5f10fd85e4b75967468af655228fbfd212bdf66db1c0d135065ce288982eda26"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dbf507e9ef5688bada447a24d68b4b58dd389ba93b7afc065a2ba892bea54769"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:85e9beda1f591bc73e77ea1c51965c68e98dafd0fec72cdd745f77d727466716"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1fdaa14ef51366d7757b45bde294e95f6c8c049194e793eedb8387c86d5993"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:75e3026ab649bf48f9a10c0134512638725b521340293f202a69b567518d94e0"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:80e6d33a3d42a7549b409f199857b4fb54e2103fc44fb87605b6663b7a7ff750"}, - {file = "yarl-1.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ec2f42d41ccbd5df0270d7df31618a8ee267bfa50997f5d720ddba86c4a83a6"}, - {file = "yarl-1.23.0-cp310-cp310-win32.whl", hash = "sha256:debe9c4f41c32990771be5c22b56f810659f9ddf3d63f67abfdcaa2c6c9c5c1d"}, - {file = "yarl-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f043cb8a2d71c981c09c510da013bc79fd661f5c60139f00dd3c3cc4f2ffb"}, - {file = "yarl-1.23.0-cp310-cp310-win_arm64.whl", hash = "sha256:263cd4f47159c09b8b685890af949195b51d1aa82ba451c5847ca9bc6413c220"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b35d13d549077713e4414f927cdc388d62e543987c572baee613bf82f11a4b99"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbb0fef01f0c6b38cb0f39b1f78fc90b807e0e3c86a7ff3ce74ad77ce5c7880c"}, - {file = "yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2c6b50c7b0464165472b56b42d4c76a7b864597007d9c085e8b63e185cf4a7a"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aafe5dcfda86c8af00386d7781d4c2181b5011b7be3f2add5e99899ea925df05"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ee33b875f0b390564c1fb7bc528abf18c8ee6073b201c6ae8524aca778e2d83"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c41e021bc6d7affb3364dc1e1e5fa9582b470f283748784bd6ea0558f87f42c"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598"}, - {file = "yarl-1.23.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2af5c81a1f124609d5f33507082fc3f739959d4719b56877ab1ee7e7b3d602b"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6b41389c19b07c760c7e427a3462e8ab83c4bb087d127f0e854c706ce1b9215c"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1dc702e42d0684f42d6519c8d581e49c96cefaaab16691f03566d30658ee8788"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0e40111274f340d32ebcc0a5668d54d2b552a6cca84c9475859d364b380e3222"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4764a6a7588561a9aef92f65bda2c4fb58fe7c675c0883862e6df97559de0bfb"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:03214408cfa590df47728b84c679ae4ef00be2428e11630277be0727eba2d7cc"}, - {file = "yarl-1.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170e26584b060879e29fac213e4228ef063f39128723807a312e5c7fec28eff2"}, - {file = "yarl-1.23.0-cp311-cp311-win32.whl", hash = "sha256:51430653db848d258336cfa0244427b17d12db63d42603a55f0d4546f50f25b5"}, - {file = "yarl-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46"}, - {file = "yarl-1.23.0-cp311-cp311-win_arm64.whl", hash = "sha256:b39cb32a6582750b6cc77bfb3c49c0f8760dc18dc96ec9fb55fbb0f04e08b928"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069"}, - {file = "yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51"}, - {file = "yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86"}, - {file = "yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34"}, - {file = "yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d"}, - {file = "yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e"}, - {file = "yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5"}, - {file = "yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4"}, - {file = "yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a"}, - {file = "yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543"}, - {file = "yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957"}, - {file = "yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3"}, - {file = "yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120"}, - {file = "yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9"}, - {file = "yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6"}, - {file = "yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5"}, - {file = "yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595"}, - {file = "yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090"}, - {file = "yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, - {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, - {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, - {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, - {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, - {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, - {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, - {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, - {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, - {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, - {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, - {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, - {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, - {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, - {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.1" - -[metadata] -lock-version = "2.1" -python-versions = ">=3.14,<3.15" -content-hash = "12247e39605d25d0d586fef8a2e1083974c4e229f194ca608da9fe970921fe06" diff --git a/pyproject.toml b/pyproject.toml index 501ccd25..df5ae29e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,8 +17,53 @@ keywords = [ "playtest", ] requires-python = ">=3.14,<3.15" +dependencies = [ + "photoshop-python-api @ git+https://github.com/pappnu/photoshop-python-api.git@type-annotation", + "requests>=2.33.1,<3.0.0", + "Pillow>=12.2.0,<13.0.0", + "limits>=5.8.0,<6.0.0", + "backoff>=2.2.1,<3.0.0", + "pathvalidate>=3.3.1,<4.0.0", + "fonttools>=4.62.1,<5.0.0", + "pyyaml>=6.0.3,<7.0.0", + "yarl>=1.23.0,<2.0.0", + "pydantic>=2.13.3,<3.0.0", + "pydantic-settings>=2.14.0,<3.0.0", + "omnitils @ git+https://github.com/pappnu/omnitils.git@dev", + "hexproof @ git+https://github.com/pappnu/hexproof.git@dev", + "pywin32>=311.0.0,<312.0.0", + "pyside6>=6.11.0,<7.0.0", +] + +[dependency-groups] +dev = [ + "pytest>=9.0.3,<10.0.0", + "commitizen>=4.13.10,<5.0.0", + "psd-tools>=1.16.0,<2.0.0", + "types-pywin32>=311.0.0.20260402", + "types-requests>=2.33.0.20260402", +] +build = [ + "setuptools>=82.0.1,<83.0.0", + "pyclean>=3.6.0,<4.0.0", + "pyinstaller>=6.20.0,<7.0.0", +] +docs = [ + "mkdocs>=1.6.1,<2.0.0", + "mkdocs-material>=9.7.6,<10.0.0", + "mkdocs-include-markdown-plugin>=7.2.2,<8.0.0", + "mkdocs-pymdownx-material-extras>=2.8.0,<3.0.0", + "mkdocs-minify-plugin>=0.8.0,<1.0.0", + "mkdocstrings-python>=2.0.3,<3.0.0", + "mkdocs-gen-files>=0.6.1,<1.0.0", + "mkdocs-autolinks-plugin>=0.7.1,<1.0.0", + "mkdocs-same-dir>=0.1.5,<1.0.0", + "mkdocs-git-revision-date-plugin>=0.3.2,<1.0.0", + "mkdocstrings[python]>=1.0.4,<2.0.0", +] +tests-extra = ["matplotlib>=3.10.9,<4.0.0", "memory-profiler>=0.61.0,<1.0.0"] -[tool.poetry.urls] +[project.urls] Changelog = "https://github.com/Investigamer/Proxyshop/blob/main/CHANGELOG.md" Discord = "https://discord.gg/magicproxies" Documentation = "https://investigamer.github.io/Proxyshop" @@ -26,61 +71,6 @@ Issues = "https://github.com/Investigamer/Proxyshop/issues" Source = "https://github.com/Investigamer/Proxyshop" Sponsor = "https://patreon.com/mpcfill" -[[tool.poetry.packages]] -include = 'src/../' - -[tool.poetry.dependencies] -photoshop-python-api = { git = "https://github.com/pappnu/photoshop-python-api.git", branch = "type-annotation" } -requests = "^2.33.1" -Pillow = "^12.2.0" -typing-extensions = "^4.15.0" -limits = "^5.8.0" -backoff = "^2.2.1" -pathvalidate = "^3.3.1" -fonttools = "^4.62.1" -pyyaml = "^6.0.3" -tqdm = "^4.67.3" -click = "8.3.2" -yarl = "^1.23.0" -pydantic = "^2.12.5" -pydantic-settings = "^2.13.1" -omnitils = { git = "https://github.com/pappnu/omnitils.git", branch = "dev" } -hexproof = { git = "https://github.com/pappnu/hexproof.git", branch = "dev" } -rich = "^14.3.3" -pywin32 = "^311.0.0" -pyside6 = "^6.11.0" - -[tool.poetry.group.dev.dependencies] -pytest = "^9.0.2" -commitizen = "^4.13.9" -setuptools = "^82.0.1" -matplotlib = "^3.10.8" -psd-tools = "^1.14.2" -pyclean = "^3.6.0" -pyinstaller = "^6.19.0" -pre-commit = "^4.5.1" -mkdocs = "^1.6.1" -mkdocs-material = "^9.7.6" -mkdocs-include-markdown-plugin = "^7.2.2" -mkdocs-pymdownx-material-extras = "^2.8.0" -mkdocs-minify-plugin = "^0.8.0" -mkdocstrings-python = "^2.0.3" -mkdocs-gen-files = "^0.6.1" -mkdocs-autolinks-plugin = "^0.7.1" -mkdocs-same-dir = "^0.1.4" -mkdocs-git-revision-date-plugin = "^0.3.2" -mkdocstrings = { extras = ["python"], version = "^1.0.3" } -memory-profiler = "^0.61.0" -types-pywin32 = "^311.0.0.20260402" -types-requests = "^2.33.0.20260402" - -[tool.poetry.scripts] -proxyshop = 'main:launch_cli' - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - [tool.commitizen] version_provider = "pep621" encoding = "utf-8" diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..c8db8374 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2052 @@ +version = 1 +revision = 3 +requires-python = "==3.14.*" + +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "backrefs" +version = "7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, + { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, +] + +[[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 = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "brotli" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" }, +] + +[[package]] +name = "brotlicffi" +version = "1.2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/b6/017dc5f852ed9b8735af77774509271acbf1de02d238377667145fcee01d/brotlicffi-1.2.0.1.tar.gz", hash = "sha256:c20d5c596278307ad06414a6d95a892377ea274a5c6b790c2548c009385d621c", size = 478156, upload-time = "2026-03-05T19:54:11.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/f9/dfa56316837fa798eac19358351e974de8e1e2ca9475af4cb90293cd6576/brotlicffi-1.2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c85e65913cf2b79c57a3fdd05b98d9731d9255dc0cb696b09376cc091b9cddd", size = 433046, upload-time = "2026-03-05T19:53:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f5/f8f492158c76b0d940388801f04f747028971ad5774287bded5f1e53f08d/brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:535f2d05d0273408abc13fc0eebb467afac17b0ad85090c8913690d40207dac5", size = 1541126, upload-time = "2026-03-05T19:53:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e1/ff87af10ac419600c63e9287a0649c673673ae6b4f2bcf48e96cb2f89f60/brotlicffi-1.2.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce17eb798ca59ecec67a9bb3fd7a4304e120d1cd02953ce522d959b9a84d58ac", size = 1541983, upload-time = "2026-03-05T19:53:50.317Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/80ecd9bd45776109fab14040e478bf63e456967c9ddee2353d8330ed8de1/brotlicffi-1.2.0.1-cp314-cp314t-win32.whl", hash = "sha256:3c9544f83cb715d95d7eab3af4adbbef8b2093ad6382288a83b3a25feb1a57ec", size = 349047, upload-time = "2026-03-05T19:53:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/ab/98/13e5b250236a281b6cd9e92a01ee1ae231029fa78faee932ef3766e1cb24/brotlicffi-1.2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:625f8115d32ae9c0740d01ea51518437c3fbaa3e78d41cb18459f6f7ac326000", size = 385652, upload-time = "2026-03-05T19:53:53.892Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9f/b98dcd4af47994cee97aebac866996a006a2e5fc1fd1e2b82a8ad95cf09c/brotlicffi-1.2.0.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:91ba5f0ccc040f6ff8f7efaf839f797723d03ed46acb8ae9408f99ffd2572cf4", size = 432608, upload-time = "2026-03-05T19:53:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7a/ac4ee56595a061e3718a6d1ea7e921f4df156894acffb28ed88a1fd52022/brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9a670c6811af30a4bd42d7116dc5895d3b41beaa8ed8a89050447a0181f5ce", size = 1534257, upload-time = "2026-03-05T19:53:58.667Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/e7410db7f6f56de57744ea52a115084ceb2735f4d44973f349bb92136586/brotlicffi-1.2.0.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3314a3476f59e5443f9f72a6dff16edc0c3463c9b318feaef04ae3e4683f5a", size = 1536838, upload-time = "2026-03-05T19:54:00.705Z" }, + { url = "https://files.pythonhosted.org/packages/a6/75/6e7977d1935fc3fbb201cbd619be8f2c7aea25d40a096967132854b34708/brotlicffi-1.2.0.1-cp38-abi3-win32.whl", hash = "sha256:82ea52e2b5d3145b6c406ebd3efb0d55db718b7ad996bd70c62cec0439de1187", size = 343337, upload-time = "2026-03-05T19:54:02.446Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ef/e7e485ce5e4ba3843a0a92feb767c7b6098fd6e65ce752918074d175ae71/brotlicffi-1.2.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:da2e82a08e7778b8bc539d27ca03cdd684113e81394bfaaad8d0dfc6a17ddede", size = 379026, upload-time = "2026-03-05T19:54:04.322Z" }, +] + +[[package]] +name = "bs4" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/aa/4acaf814ff901145da37332e05bb510452ebed97bc9602695059dd46ef39/bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925", size = 698, upload-time = "2024-01-17T18:15:47.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/bb/bf7aab772a159614954d84aa832c129624ba6c32faa559dfb200a534e50b/bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc", size = 1189, upload-time = "2024-01-17T18:15:48.613Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +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" } +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" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.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/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]] +name = "click" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "commitizen" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "charset-normalizer" }, + { name = "colorama" }, + { name = "decli" }, + { name = "deprecated" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "prompt-toolkit" }, + { name = "pyyaml" }, + { name = "questionary" }, + { name = "termcolor" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/2a/ac219a23e89210aa3ee9244dc076e39bc532bc3f352024f3d478f3e79b85/commitizen-4.15.1.tar.gz", hash = "sha256:cca192e07b2f9d77734044c631da294b3007ef9aa10cc8f0600290aa662e9fa3", size = 65718, upload-time = "2026-05-06T04:14:45.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/5d/12e74d633a622474cf3af6b27656c7da8b96caf87e1c6672436fcff85138/commitizen-4.15.1-py3-none-any.whl", hash = "sha256:8207ee3e0344b483c5e59103d59ce19b96eb7204ccf28c72b65596fa524b06e2", size = 87876, upload-time = "2026-05-06T04:14:43.754Z" }, +] + +[[package]] +name = "comtypes" +version = "1.4.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/2a/65274c13327f637ec13af8d39f2cf579d9ebe7a0e683696b5f05236d2805/comtypes-1.4.16.tar.gz", hash = "sha256:cd66d1add01265cface4df51ba1e31cd1657e04463c281c802e737e79e1ba93c", size = 260252, upload-time = "2026-03-02T23:11:42.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/7c/0eb685107290b6221c03c46d39214a4e42a124189691cb83ae3228257f46/comtypes-1.4.16-py3-none-any.whl", hash = "sha256:e18d85179ff12955524c5a8c3bc09cb3c0d890f1da4d7123d14244c7b78f84c8", size = 296230, upload-time = "2026-03-02T23:11:41.049Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "csscompressor" +version = "0.9.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8d7ae270bb5bc437b210bb9d6d9e46630c98f4abd20c/csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05", size = 237808, upload-time = "2017-11-26T21:13:08.238Z" } + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "decli" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/59/d4ffff1dee2c8f6f2dd8f87010962e60f7b7847504d765c91ede5a466730/decli-0.6.3.tar.gz", hash = "sha256:87f9d39361adf7f16b9ca6e3b614badf7519da13092f2db3c80ca223c53c7656", size = 7564, upload-time = "2025-06-01T15:23:41.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3", size = 7989, upload-time = "2025-06-01T15:23:40.228Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "fonttools" +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/354ae6491228b5eb40e10d89c4d13c651fe1cf7556e35ebdded50cff57ce/gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc", size = 219798, upload-time = "2026-05-06T04:01:26.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" }, +] + +[[package]] +name = "griffelib" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, +] + +[[package]] +name = "hexproof" +version = "0.3.7" +source = { git = "https://github.com/pappnu/hexproof.git?rev=dev#fc281f1d767651322ff271d2ccb72251bd70e19c" } +dependencies = [ + { name = "bs4" }, + { name = "limits" }, + { name = "loguru" }, + { name = "omnitils" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tomli" }, + { name = "tomlkit" }, +] + +[[package]] +name = "htmlmin2" +version = "0.1.13" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/31/a76f4bfa885f93b8167cb4c85cf32b54d1f64384d0b897d45bc6d19b7b45/htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2", size = 34486, upload-time = "2023-03-14T21:28:30.388Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +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" } +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" }, +] + +[[package]] +name = "inflate64" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/f3/41bb2901543abe7aad0b0b0284ae5854bb75f848cf406bf8a046bf525f67/inflate64-1.0.4.tar.gz", hash = "sha256:b398c686960c029777afc0ed281a86f66adb956cfc3fbf6667cc6453f7b407ce", size = 902542, upload-time = "2025-11-28T10:55:52.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fb/ec9d10f44f2fc7666ad5d70acae2b8a1941e8e08ccae1fad0820f7796be3/inflate64-1.0.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4f61925b2d4248eac2ebb15350a80aaa0d1f7f1dc770bd5ebbbb3b0db4a6a416", size = 58727, upload-time = "2025-11-28T10:55:13.834Z" }, + { url = "https://files.pythonhosted.org/packages/81/80/24ba0d2ee14e07e275e9c5b058e59a8a58f8ef42dd51a78ebbfd7c857ac4/inflate64-1.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c1acf18b08b32981a4a11ec5a112b8ad5d7c7a5b15cb5bdbdb5b19201e9aa180", size = 35945, upload-time = "2025-11-28T10:55:15.225Z" }, + { url = "https://files.pythonhosted.org/packages/70/b8/073a79716e093db973b8823bdfb02e10fbdf65642dbe1fa3cda24832aeb2/inflate64-1.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abddae8920b2eaef824254e14b8d4ff54afbe6194a1bbe9816584859f0c1244d", size = 36060, upload-time = "2025-11-28T10:55:16.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f0/87d3c317ed0acd94f5e9b3b1b9e9000228ea2af0cb4618c62cbfc816da34/inflate64-1.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b303132cc562a906543a56f35c4e164e3880da6ff041cb4a7b1df9f9d2b4bb69", size = 98995, upload-time = "2025-11-28T10:55:17.405Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/0b6035302e9c33f004240a50cb6e2e1fc7bb1f2b415b02d939c551bdd06b/inflate64-1.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f0993214dea0738c557fa56c13cd9083aef0097a201d726c21984ad7f577514", size = 100781, upload-time = "2025-11-28T10:55:18.597Z" }, + { url = "https://files.pythonhosted.org/packages/2b/05/5f383c615ec0f01bcbbc699a71da167623e494083ab7ed0df86b4bddf125/inflate64-1.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a6baedc3288d7a4ff588951d3a9a97a5391dceed6255ff5b16e42cae7274bfa9", size = 97038, upload-time = "2025-11-28T10:55:20.156Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9a/c1482718c717c49c67490c42b4fdced9476e894eaebd52193e488c12e188/inflate64-1.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a846ce1f38845b20bef2625af1b512be83416d97824539524c5a34e7a729aec7", size = 100016, upload-time = "2025-11-28T10:55:21.337Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7f/700ede7474e72a1c0e2e8fe36624cd0225ca8b2875eca33d64aa2de75f4d/inflate64-1.0.4-cp314-cp314-win32.whl", hash = "sha256:eef87908c780439393d577a155868317f0a275b47b417db9f47d8633ec791745", size = 33691, upload-time = "2025-11-28T10:55:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/f2972df8cceecc9bf3afa3353d517ffc7125285198c844588e9aaf98f5d0/inflate64-1.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:fb2fdd63ef3933b67af98b3f2ee2f57e7787278041d7ba4821382fedd729b68a", size = 36304, upload-time = "2025-11-28T10:55:24.005Z" }, + { url = "https://files.pythonhosted.org/packages/29/77/16200aced67215119fb30ec9a5889b48289ee2fa5ce5137623a9ad41b2c4/inflate64-1.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:2e129669a0243ac7816fd526946ee01c25688fe81623a6d6bc95b3156d80f4fb", size = 34491, upload-time = "2025-11-28T10:55:25.068Z" }, + { url = "https://files.pythonhosted.org/packages/c0/79/b466ec7666c40912ea81a305a8a2b75f5998e6cec1d3d75e067d76203731/inflate64-1.0.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b17bf665d948dc4edeea0cd17752415d0cd7240c882b9c7e136ad4cc4321e9d4", size = 59300, upload-time = "2025-11-28T10:55:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/16e1168ac80f39894e6cb18b439eec63fec42cbced239aebfe12081b6ec7/inflate64-1.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6751758301936fbb38fa38eb5312e14e27b6a1abf568f83c17557fab2694373d", size = 36258, upload-time = "2025-11-28T10:55:27.295Z" }, + { url = "https://files.pythonhosted.org/packages/25/42/c463b42fd8a7947b4445fbaf57c265bb7f3114362fb7aee6884ffd8b5341/inflate64-1.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d6a4136752aa2a544301059d8f13780aeb88c34d60770258436a87dacd3fc304", size = 36296, upload-time = "2025-11-28T10:55:28.632Z" }, + { url = "https://files.pythonhosted.org/packages/39/f1/cf6121926e405020e9e7bccb78ec7781fbc87500ec67368e7d9e866758be/inflate64-1.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:938ebc6b28578bfd365d1a9fdb18b7faab08321babeb2198e8025d07d8dc7fb5", size = 106788, upload-time = "2025-11-28T10:55:29.797Z" }, + { url = "https://files.pythonhosted.org/packages/9c/dd/b653f9962497cf4d3520d69272894c37fd76f86a0e04bb3bd9f32827dc2f/inflate64-1.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61f51f80fa6f367288343c1a2cd20a42af454883087064e9274fd2a8c3a5a200", size = 107959, upload-time = "2025-11-28T10:55:31.306Z" }, + { url = "https://files.pythonhosted.org/packages/60/ae/65d88109b63611c9b6c29008201107127cf2603d186ab99beec39d85f38e/inflate64-1.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:172b51da7bbfa66b33f0a5405e944807b9949e92cf4cd9f983c07af8152766df", size = 104213, upload-time = "2025-11-28T10:55:32.506Z" }, + { url = "https://files.pythonhosted.org/packages/f9/94/c17de2f55b9fb1269bca4657f9089efe4ba0f3d4b652f07b34dfc69f69a2/inflate64-1.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ca9a2985afd5a14fb48cd126a67e5944ccb7a0a6bdec58c4f796c8c88a84539", size = 106629, upload-time = "2025-11-28T10:55:33.735Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/8bbc587bb2ad09ab7edf1f9215b2c5faf4fa7ee7c071daefe9ed55e28814/inflate64-1.0.4-cp314-cp314t-win32.whl", hash = "sha256:f8964ceaabea294bc20abc9ef408c6aae978a75c25c83168a76cd87a37c38938", size = 33865, upload-time = "2025-11-28T10:55:35.335Z" }, + { url = "https://files.pythonhosted.org/packages/91/87/8bf6f412f93c8b6dc14866a021b83321331fbdd17f6ab902a24dbf88773d/inflate64-1.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:7d13b04cba65c12d21e65eaa77da9484e265e8e821b26e0761d1455ad3a878d9", size = 36943, upload-time = "2025-11-28T10:55:36.983Z" }, + { url = "https://files.pythonhosted.org/packages/7d/85/33447bb3c4e3c0ae7b1fde3aadc52a18b2b0193cfcf4f585977e924c6463/inflate64-1.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:9ae3ee727235a06dc3cd353ee5761fdd8e3b56ad119c711f61680528972a6ced", size = 34846, upload-time = "2025-11-28T10:55:38.433Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.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" } +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" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsmin" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925, upload-time = "2022-01-16T20:35:59.13Z" } + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, +] + +[[package]] +name = "limits" +version = "5.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104, upload-time = "2026-02-05T07:17:35.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954, upload-time = "2026-02-05T07:17:34.425Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[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/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 = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, +] + +[[package]] +name = "memory-profiler" +version = "0.61.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/88/e1907e1ca3488f2d9507ca8b0ae1add7b1cd5d3ca2bc8e5b329382ea2c7b/memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0", size = 35935, upload-time = "2022-11-15T17:57:28.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/26/aaca612a0634ceede20682e692a6c55e35a94c21ba36b807cc40fe910ae1/memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84", size = 31803, upload-time = "2022-11-15T17:57:27.031Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autolinks-plugin" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/b7/efc75b7870a4fecbc9a05ba94ce622462a40b5a13670d00036c5b47bf82f/mkdocs-autolinks-plugin-0.7.1.tar.gz", hash = "sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151", size = 4375, upload-time = "2023-08-04T14:42:25.67Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/9c/ee3d81a799b8b0b73a89625bcbf3c58cd369c8a26a1b415413edea9bf259/mkdocs_autolinks_plugin-0.7.1-py3-none-any.whl", hash = "sha256:5c6c17f6649b68e79a9ef0b2648d59f3072e18002b90ee1586a64c505f11ab12", size = 4235, upload-time = "2023-08-04T14:42:23.955Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, +] + +[[package]] +name = "mkdocs-gen-files" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "properdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/43/428f312149c161cae557eecd35f3c4a82b867998b1d47fb29fdfe927be26/mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763", size = 8746, upload-time = "2026-03-16T23:26:09.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/1b/3075eb67fe66e19db059f0a25744c4e56978a309603a20e1d3353d545b5e/mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189", size = 8282, upload-time = "2026-03-16T23:26:08.292Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-git-revision-date-plugin" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitpython" }, + { name = "jinja2" }, + { name = "mkdocs" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/c7/73990d826985bbfcb87e3c46887ff025db336ffb12fe505f2d0990d51178/mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef", size = 4160, upload-time = "2022-03-08T14:42:31.97Z" }, +] + +[[package]] +name = "mkdocs-include-markdown-plugin" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/2d/bdf1aee3f4f7b34148b0f62298b62f03415160cb2707f09503c99a0a7cd5/mkdocs_include_markdown_plugin-7.2.2.tar.gz", hash = "sha256:f052ccb741eccf498116b826c1d78a2d761c56747372594709441cee0963fbc9", size = 25415, upload-time = "2026-03-29T15:15:14.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/a5/f6b2f0aa805dbda52f6265e9aff1450c8643195442facf29d475bdeba15d/mkdocs_include_markdown_plugin-7.2.2-py3-none-any.whl", hash = "sha256:f2ec4487cf32d3e33ca528f9366f20fb9280ded9c8d1630eb2bbda244962dcd1", size = 29528, upload-time = "2026-03-29T15:15:13.079Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba", size = 9305470, upload-time = "2026-03-19T15:41:55.217Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-minify-plugin" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "csscompressor" }, + { name = "htmlmin2" }, + { name = "jsmin" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/67/fe4b77e7a8ae7628392e28b14122588beaf6078b53eb91c7ed000fd158ac/mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d", size = 8366, upload-time = "2024-01-29T16:11:32.982Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/cd/2e8d0d92421916e2ea4ff97f10a544a9bd5588eb747556701c983581df13/mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6", size = 6723, upload-time = "2024-01-29T16:11:31.851Z" }, +] + +[[package]] +name = "mkdocs-pymdownx-material-extras" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs-material" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f6/5199d1e251e15b3c554f46b5796d02870e129f524dc2f117e828c5444674/mkdocs_pymdownx_material_extras-2.8.tar.gz", hash = "sha256:7b22bb119cd9592f98d6c6d4d269506d9a68d7038355c71525aadc88169ee9fe", size = 26512, upload-time = "2025-03-16T14:24:50.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/78/0059cb24b62a3dd68293daea38acb2cb75415d5735a62d6d60f752cd00ed/mkdocs_pymdownx_material_extras-2.8-py3-none-any.whl", hash = "sha256:81b68789420c51b9b15514180d0f3ab7136d56ee512c830c998d2edb77ca3d77", size = 28846, upload-time = "2025-03-16T14:24:49.235Z" }, +] + +[[package]] +name = "mkdocs-same-dir" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "properdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/58/f49fdf5fd82cb82ab1ee4fb9a5729d741d8dd6bb9623e0471ad704a6f0c0/mkdocs_same_dir-0.1.5.tar.gz", hash = "sha256:677cf8d682d0ae6226620871c01906a1a631083f2b00fac58ffe9019b2cb2322", size = 5082, upload-time = "2026-04-17T16:22:24.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/f0/bc3618e170db0f47baf02f15ca465ee342a28400c5630e561fad9d1ff191/mkdocs_same_dir-0.1.5-py3-none-any.whl", hash = "sha256:a9cc599caf438da288ba14b21d24db4bb7975106eef1cd56d0d92e6835c366aa", size = 4379, upload-time = "2026-04-17T16:22:23.679Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/5d/f888d4d3eb31359b327bc9b17a212d6ef03fe0b0682fbb3fc2cb849fb12b/mkdocstrings-1.0.4.tar.gz", hash = "sha256:3969a6515b77db65fd097b53c1b7aa4ae840bd71a2ee62a6a3e89503446d7172", size = 100088, upload-time = "2026-04-15T09:16:53.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl", hash = "sha256:63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b", size = 35560, upload-time = "2026-04-15T09:16:51.436Z" }, +] + +[package.optional-dependencies] +python = [ + { name = "mkdocstrings-python" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multivolumefile" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/f0/a7786212b5a4cb9ba05ae84a2bbd11d1d0279523aea0424b6d981d652a14/multivolumefile-0.2.3.tar.gz", hash = "sha256:a0648d0aafbc96e59198d5c17e9acad7eb531abea51035d08ce8060dcad709d6", size = 77984, upload-time = "2021-04-29T12:18:39.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/31/ec5f46fd4c83185b806aa9c736e228cb780f13990a9cf4da0beb70025fcc/multivolumefile-0.2.3-py3-none-any.whl", hash = "sha256:237f4353b60af1703087cf7725755a1f6fcaeeea48421e1896940cd1c920d678", size = 17037, upload-time = "2021-04-29T12:18:38.886Z" }, +] + +[[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/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 = "omnitils" +version = "1.4.6" +source = { git = "https://github.com/pappnu/omnitils.git?rev=dev#dab468c228d746a1832f421a092d902b4a3f5fb8" } +dependencies = [ + { name = "backoff" }, + { name = "click" }, + { name = "limits" }, + { name = "loguru" }, + { name = "pathvalidate" }, + { name = "pillow" }, + { name = "py7zr" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tomli" }, + { name = "tomlkit" }, + { name = "tqdm" }, + { name = "yarl" }, +] + +[[package]] +name = "packaging" +version = "26.2" +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" } +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" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + +[[package]] +name = "photoshop-python-api" +version = "0.24.1" +source = { git = "https://github.com/pappnu/photoshop-python-api.git?rev=type-annotation#4cd7f34986a17da959295dc698650d5e5b58e3f0" } +dependencies = [ + { name = "comtypes" }, + { name = "wheel" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "properdocs" +version = "1.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/29/f27a4e1eddf72ed3db6e47818fbafe6debbf09fd7051f9c1a007239b46ef/properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e", size = 276141, upload-time = "2026-03-20T20:07:48.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/4d/fc923f5c85318ee8cc903566dc4e0ebe41b2dfc1d2ecf5546db232397ed6/properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd", size = 225406, upload-time = "2026-03-20T20:07:46.875Z" }, +] + +[[package]] +name = "proxyshop" +version = "1.13.2" +source = { virtual = "." } +dependencies = [ + { name = "backoff" }, + { name = "fonttools" }, + { name = "hexproof" }, + { name = "limits" }, + { name = "omnitils" }, + { name = "pathvalidate" }, + { name = "photoshop-python-api" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyside6" }, + { name = "pywin32" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "yarl" }, +] + +[package.dev-dependencies] +build = [ + { name = "pyclean" }, + { name = "pyinstaller" }, + { name = "setuptools" }, +] +dev = [ + { name = "commitizen" }, + { name = "psd-tools" }, + { name = "pytest" }, + { name = "types-pywin32" }, + { name = "types-requests" }, +] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-autolinks-plugin" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-git-revision-date-plugin" }, + { name = "mkdocs-include-markdown-plugin" }, + { name = "mkdocs-material" }, + { name = "mkdocs-minify-plugin" }, + { name = "mkdocs-pymdownx-material-extras" }, + { name = "mkdocs-same-dir" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "mkdocstrings-python" }, +] +tests-extra = [ + { name = "matplotlib" }, + { name = "memory-profiler" }, +] + +[package.metadata] +requires-dist = [ + { name = "backoff", specifier = ">=2.2.1,<3.0.0" }, + { name = "fonttools", specifier = ">=4.62.1,<5.0.0" }, + { name = "hexproof", git = "https://github.com/pappnu/hexproof.git?rev=dev" }, + { name = "limits", specifier = ">=5.8.0,<6.0.0" }, + { name = "omnitils", git = "https://github.com/pappnu/omnitils.git?rev=dev" }, + { name = "pathvalidate", specifier = ">=3.3.1,<4.0.0" }, + { name = "photoshop-python-api", git = "https://github.com/pappnu/photoshop-python-api.git?rev=type-annotation" }, + { name = "pillow", specifier = ">=12.2.0,<13.0.0" }, + { name = "pydantic", specifier = ">=2.13.3,<3.0.0" }, + { name = "pydantic-settings", specifier = ">=2.14.0,<3.0.0" }, + { name = "pyside6", specifier = ">=6.11.0,<7.0.0" }, + { name = "pywin32", specifier = ">=311.0.0,<312.0.0" }, + { name = "pyyaml", specifier = ">=6.0.3,<7.0.0" }, + { name = "requests", specifier = ">=2.33.1,<3.0.0" }, + { name = "yarl", specifier = ">=1.23.0,<2.0.0" }, +] + +[package.metadata.requires-dev] +build = [ + { name = "pyclean", specifier = ">=3.6.0,<4.0.0" }, + { name = "pyinstaller", specifier = ">=6.20.0,<7.0.0" }, + { name = "setuptools", specifier = ">=82.0.1,<83.0.0" }, +] +dev = [ + { name = "commitizen", specifier = ">=4.13.10,<5.0.0" }, + { name = "psd-tools", specifier = ">=1.16.0,<2.0.0" }, + { name = "pytest", specifier = ">=9.0.3,<10.0.0" }, + { name = "types-pywin32", specifier = ">=311.0.0.20260402" }, + { name = "types-requests", specifier = ">=2.33.0.20260402" }, +] +docs = [ + { name = "mkdocs", specifier = ">=1.6.1,<2.0.0" }, + { name = "mkdocs-autolinks-plugin", specifier = ">=0.7.1,<1.0.0" }, + { name = "mkdocs-gen-files", specifier = ">=0.6.1,<1.0.0" }, + { name = "mkdocs-git-revision-date-plugin", specifier = ">=0.3.2,<1.0.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.2.2,<8.0.0" }, + { name = "mkdocs-material", specifier = ">=9.7.6,<10.0.0" }, + { name = "mkdocs-minify-plugin", specifier = ">=0.8.0,<1.0.0" }, + { name = "mkdocs-pymdownx-material-extras", specifier = ">=2.8.0,<3.0.0" }, + { name = "mkdocs-same-dir", specifier = ">=0.1.5,<1.0.0" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.4,<2.0.0" }, + { name = "mkdocstrings-python", specifier = ">=2.0.3,<3.0.0" }, +] +tests-extra = [ + { name = "matplotlib", specifier = ">=3.10.9,<4.0.0" }, + { name = "memory-profiler", specifier = ">=0.61.0,<1.0.0" }, +] + +[[package]] +name = "psd-tools" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/9b/452cb58795a11b33eecc1dfbf84771fbaa50857dc3f2bd3f4793b0e9068d/psd_tools-1.16.0-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:2496628209a01eae3e5657c941ad0e27acec4c8ae0b72fe57fee0e3b039a3f49", size = 375521, upload-time = "2026-04-24T10:36:18.471Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c4/85fc97ea2549807f13cde18d1727865171e0b9ca87d9681a72b70a23541c/psd_tools-1.16.0-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:ae361357ec228cc5f76f8814d0c668375ace6c883c97b85c868b591a658292df", size = 374679, upload-time = "2026-04-24T10:36:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/0e/33/873f58292768cfcfbc4619dcbe04e357b62e37546810d910f05111ebfe22/psd_tools-1.16.0-cp313-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40a5650ed17032027d18747601afdfb740e46f6ba918d43a08a55f900361ae8a", size = 714006, upload-time = "2026-04-24T10:36:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/bf/83/4d69d4d69213e5cc703d14219dcefedb9b6768ae4fcb83b8cbe9ce2ebb4a/psd_tools-1.16.0-cp313-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6940eada4882f64db5a2bd4097f21c8c7eed9c391083302e46b8dc07b2d0fb7c", size = 718879, upload-time = "2026-04-24T10:36:21.783Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2f/0bb0e9f67755761f19f4e7600ed93566a08f4a4d7f65e9c22300c8557d90/psd_tools-1.16.0-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b8e71330f18ec165497e4c1fb6841781a4faa20125f7229a2d1293ccd543c22", size = 1687647, upload-time = "2026-04-24T10:36:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/15/fd/1d162c3ffa1af3e84f66f62782759c68ba2e5ef3028cdc121a15501966d2/psd_tools-1.16.0-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3388ffbfd0213e5ad4c2b0d923cd931403b0b422c8954e22cabc32404a461a9d", size = 1750475, upload-time = "2026-04-24T10:36:24.385Z" }, + { url = "https://files.pythonhosted.org/packages/74/2f/5b6e622a50e8a303121a8c14c891dab1d07d1595ef73d4a6072a1771792a/psd_tools-1.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:522781ade64766ed5c4f3428d3b2fcc79070297f2a290902e2ae8f3fc2ae1738", size = 564078, upload-time = "2026-04-24T10:36:28.13Z" }, + { url = "https://files.pythonhosted.org/packages/76/9a/5b5baf290f8853e43a96a5c1860d0fc3ea9affdf936f02e88a9b7d493bf1/psd_tools-1.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:6ec8a50f5cae6801c5c797a924f08081088c336be5eecd8761f6325ee2a38c87", size = 728901, upload-time = "2026-04-24T10:36:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f0/9efc1b726daff600aaf3d7aa38476ca8da87364b249ae66f8ad6b09e7700/psd_tools-1.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fe2ddef38f54d917bfc0e0d4a928eec0809f75a1b6f8b27b275f56ba302cc78b", size = 387418, upload-time = "2026-04-24T10:36:30.906Z" }, + { url = "https://files.pythonhosted.org/packages/07/b0/9af743ceeeaf0aad79851af3feb30d3bddd8310110caf4326f5c894255b9/psd_tools-1.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd96d069915d7a0a1450d3518dd00c7200cbd1aa012dd828e700f183cc28e2bf", size = 387156, upload-time = "2026-04-24T10:36:32.067Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/f9121b776b13049f5184dac342ba0e39591fe07596711fba51e9ba70279c/psd_tools-1.16.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fe559baa68ea01f460073a144efab23d5f6f62743bb7da3436f148212314b3c", size = 773533, upload-time = "2026-04-24T10:36:33.089Z" }, + { url = "https://files.pythonhosted.org/packages/4a/31/733e75a4fa9fe2f3de206e68a349f17a59772bc1fc58980faf3eb8b51ff5/psd_tools-1.16.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:441e5942692a25464f2248d536235b5addf1cf9c0d8c0c3625d022134d2fd4e3", size = 772161, upload-time = "2026-04-24T10:36:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cf/b9bd9e7b86175323f5c045a73d8d8a2fd5bc86d33df1f5b4e0ea51265c1f/psd_tools-1.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09d675eaf47d1ce3f5841a6d8ab00f3ed34e53a18830c81f8fd2e04f1f52de00", size = 1737878, upload-time = "2026-04-24T10:36:35.777Z" }, + { url = "https://files.pythonhosted.org/packages/11/12/3a2620a73c80121a7af09ed8c08df59968b6612614ce9b1c20d54fcb4c1b/psd_tools-1.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:114d8a648e1a6c3fe67dadfc1af41fb21962c6276893c4774ccc8c0b5bad7f62", size = 1796510, upload-time = "2026-04-24T10:36:37.021Z" }, + { url = "https://files.pythonhosted.org/packages/34/43/60c2a09061cd4d943027ceefd4ae474dffa7a505421a08d983e31c369781/psd_tools-1.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9a3265cbac76097e8a6372964a3af1014c6f61d93fdcd76547c628f3b57f1f12", size = 575104, upload-time = "2026-04-24T10:36:38.14Z" }, + { url = "https://files.pythonhosted.org/packages/21/a4/07d1b509030b9ab4000582304c700c88071fcf5bd7bc01d2f17a3dea5f68/psd_tools-1.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:38b6c205490ceb81cde8e41b201e7c05a8dbef3a9406ba068c980e739a7f0d7d", size = 734757, upload-time = "2026-04-24T10:36:39.581Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "py7zr" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "brotli", marker = "platform_python_implementation == 'CPython'" }, + { name = "brotlicffi", marker = "platform_python_implementation == 'PyPy'" }, + { name = "inflate64" }, + { name = "multivolumefile" }, + { name = "psutil", marker = "sys_platform != 'cygwin'" }, + { name = "pybcj" }, + { name = "pycryptodomex" }, + { name = "pyppmd" }, + { name = "texttable" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/e6/01fb15361ca75ee5d01df6361825a49816a836c99980c5481da0e40c6877/py7zr-1.1.0.tar.gz", hash = "sha256:087b1a94861ad9eb4d21604f6aaa0a8986a7e00580abd79fedd6f82fecf0592c", size = 70855, upload-time = "2025-12-21T03:27:44.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/9c/762284710ead9076eeecd55fb60509c19cd1f4bea811df5f3603725b44cb/py7zr-1.1.0-py3-none-any.whl", hash = "sha256:5921bc30fb72b5453aafe3b2183664c08ef508cde2655988d5e9bd6078353ef7", size = 71257, upload-time = "2025-12-21T03:27:42.881Z" }, +] + +[[package]] +name = "pybcj" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/2670b672655b18454841b8e88f024b9159d637a4c07f6ce6db85accf8467/pybcj-1.0.7.tar.gz", hash = "sha256:72d64574069ffb0a800020668376b7ebd7adea159adbf4d35f8effc62f0daa67", size = 31282, upload-time = "2025-11-29T00:53:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/9e/eb50f11ea7fb6342167bb8353d48966b490afa8ae47c98917e9acd045b71/pybcj-1.0.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:293f951eb3877840acab79f0c4dcfc06eab03e087cb9e4c004ec058e093acb1d", size = 32409, upload-time = "2025-11-29T00:53:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f6/b55f4a5faf9bd162426f49de84873fbf813698d1b018234c3c99816a9662/pybcj-1.0.7-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3ae64960904f362d33ffca10715803afd9f9a6a2a592f871dcb335acf82edf29", size = 23818, upload-time = "2025-11-29T00:53:09.236Z" }, + { url = "https://files.pythonhosted.org/packages/4c/24/78cf0973b3deded1e072479ac35d387083a626b3dc0b29e2e099cf73e082/pybcj-1.0.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:70aa4476910f982025f878e598c136559a6d78b59fc20ba8b4b592306cde6051", size = 24115, upload-time = "2025-11-29T00:53:10.241Z" }, + { url = "https://files.pythonhosted.org/packages/e6/69/e044d7127018158c67b119e6ed26d1a4b34b1e1defd9b0d3de029a782b9f/pybcj-1.0.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79ce3ce9b380b1b75c5e490abc3888ee3b5b2d28c22b59618674bf410b9cee16", size = 52484, upload-time = "2025-11-29T00:53:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/98/05/55337b0a9807887967b2cf49dd6f3bf47c7745786b0bda51a5cbfda66c78/pybcj-1.0.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc73ee1bc064d6f97dfd66051d3859b32e1b6a4cf89b077f5c8ef6c2dccb71af", size = 51471, upload-time = "2025-11-29T00:53:12.86Z" }, + { url = "https://files.pythonhosted.org/packages/59/09/6c57cbbf4931ac304f822ef78f478dd6b34b00b08e9530d308737959f064/pybcj-1.0.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5b0d13f41a9f85b3f95dd5dc7bfaa9539e80f8ae60a96db7f34c07ed732e4a82", size = 50435, upload-time = "2025-11-29T00:53:14.264Z" }, + { url = "https://files.pythonhosted.org/packages/bc/93/26415ca1b96456e27550dae67092023af3e8454621b1efa701079d8acb64/pybcj-1.0.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:597d7e9a8cbb30a6ed54d552fd3436edb32bbb821a7ac2fa8e5c7ebd1f7e0e93", size = 50010, upload-time = "2025-11-29T00:53:15.396Z" }, + { url = "https://files.pythonhosted.org/packages/91/f7/d4e55aede85143adff3ded89a1ae87bbf29060ddc2249fb861d78b103d4a/pybcj-1.0.7-cp314-cp314-win_amd64.whl", hash = "sha256:4603cc41ceb1236abe9169e2ead344140be5d2c3ac01bbc5e44cb1b13078a009", size = 25283, upload-time = "2025-11-29T00:53:16.472Z" }, + { url = "https://files.pythonhosted.org/packages/f6/19/9939ba437140d7375aecf8063d8695dfeea56ca40f57ddaa6ca3b828c131/pybcj-1.0.7-cp314-cp314-win_arm64.whl", hash = "sha256:adf985e816ddd59f3bf6d1066b7fa89de7424a4f19f3725f9976284cabe54e28", size = 23622, upload-time = "2025-11-29T00:53:17.994Z" }, + { url = "https://files.pythonhosted.org/packages/62/6e/5e14c70f3ddc268f28e7ac912510c8bc2b5430f18bb7f326bbb9d1878955/pybcj-1.0.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bbd835873de147481d62c11ba91a75d26a72df1142de3516b384b04e5a1db6d", size = 33016, upload-time = "2025-11-29T00:53:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/49/c1/25a1a8d5811c5d2a2ca3439c548abbba882fa72eba9a6eb41040206fdc26/pybcj-1.0.7-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b7576b25d7b01a953e2f987e77cef93c001db7b95924a5541d5a55f9195a7e89", size = 24114, upload-time = "2025-11-29T00:53:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/35/66/09567564cdc6cc2713d729bd29794367ebb67fa9ea5c10313c49608c08fa/pybcj-1.0.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57b36920498f82ca6a325a98b13e0fbff8fc29bade7aaaddc7d284640bffd87d", size = 24428, upload-time = "2025-11-29T00:53:21.522Z" }, + { url = "https://files.pythonhosted.org/packages/c5/49/4f6793624eb418cc2536ef39e67c8783d7ca542f6139a051e9d2d76fdc80/pybcj-1.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac2a46faf41e373939f6d3e6a5aa2121bf09e2446972c14a8e5d1ca3b0f8130", size = 58806, upload-time = "2025-11-29T00:53:22.568Z" }, + { url = "https://files.pythonhosted.org/packages/be/1c/8604f7fe360a9340bbf798b826a88f8e9d186fc031eb531bcd11ac9e684b/pybcj-1.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c7d6156ef2b4e8ecd450b62dc4cc3a89e8dda307cb26288b670952ef0df3a37", size = 56975, upload-time = "2025-11-29T00:53:23.691Z" }, + { url = "https://files.pythonhosted.org/packages/83/71/d706b4220f60fa4e6a5756dc0051fb76c32a5615958fc12f0dcacef3f86b/pybcj-1.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0fe306213de1e764abae63c06ae5a4e9a83632f62612805f1f883b8d74431901", size = 56023, upload-time = "2025-11-29T00:53:25.135Z" }, + { url = "https://files.pythonhosted.org/packages/3e/28/ad306f5acb1226241960bb86e9021b4e32bb7da426ccdc824d9d36d61261/pybcj-1.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00448182d535cca37e8f24d892d480fa86f80ff20c79385f6eca75f118efcbb4", size = 55194, upload-time = "2025-11-29T00:53:26.209Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d8/70c5e5701926fc67dae02101f0298d21c0b89ff83fa705712c3b4da252e5/pybcj-1.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:7e94aa712d0fa5fda9875828441755ece7121fc3f8c5cc3bc8ee92d05b853590", size = 25826, upload-time = "2025-11-29T00:53:27.655Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5d/7a87ba32c0c0756f36000fafe642fa4609be2c26a50a7913a057a47eabdf/pybcj-1.0.7-cp314-cp314t-win_arm64.whl", hash = "sha256:16fd4e51a5556d1f38d7ba5d1fab588bfb60ae23d2299b5179779bf9900adf71", size = 24049, upload-time = "2025-11-29T00:53:28.679Z" }, +] + +[[package]] +name = "pyclean" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0d/7690e519b66101c41c114e2ddbf144f9b4ff6ea938fb199c09ed958f3970/pyclean-3.6.0.tar.gz", hash = "sha256:a46c179be187999d50d2d3362b2c94856e60f77de32ca7ec2db4eeb16bc39199", size = 27000, upload-time = "2026-03-30T17:34:30.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/2a/1797004bb000db555bd45e38550b3ca10699f87e22a78e6753882e0ac2cf/pyclean-3.6.0-py3-none-any.whl", hash = "sha256:3e5f9c83f2472df5bdafa78ed533bd38690756e0a7b3b6f11c8b29c9affc728b", size = 27750, upload-time = "2026-03-30T17:34:29.034Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +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" } +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" }, +] + +[[package]] +name = "pycryptodomex" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", size = 4922157, upload-time = "2025-05-17T17:23:41.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", size = 2499240, upload-time = "2025-05-17T17:22:46.953Z" }, + { url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", size = 1644042, upload-time = "2025-05-17T17:22:49.098Z" }, + { url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", size = 2186227, upload-time = "2025-05-17T17:22:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", size = 2272578, upload-time = "2025-05-17T17:22:53.676Z" }, + { url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", size = 2312166, upload-time = "2025-05-17T17:22:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", size = 2185467, upload-time = "2025-05-17T17:22:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", size = 2346104, upload-time = "2025-05-17T17:23:02.112Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", size = 2271038, upload-time = "2025-05-17T17:23:04.872Z" }, + { url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", size = 1771969, upload-time = "2025-05-17T17:23:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", size = 1803124, upload-time = "2025-05-17T17:23:09.267Z" }, + { url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", size = 1707161, upload-time = "2025-05-17T17:23:11.414Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +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/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.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +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/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" }, +] + +[[package]] +name = "pydantic-settings" +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/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/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]] +name = "pygments" +version = "2.20.0" +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" } +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" }, +] + +[[package]] +name = "pyinstaller" +version = "6.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728, upload-time = "2026-04-22T20:59:36.96Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642, upload-time = "2026-04-22T20:58:32.006Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494, upload-time = "2026-04-22T20:58:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191, upload-time = "2026-04-22T20:58:40.603Z" }, + { url = "https://files.pythonhosted.org/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902, upload-time = "2026-04-22T20:58:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634, upload-time = "2026-04-22T20:58:48.579Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490, upload-time = "2026-04-22T20:58:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650, upload-time = "2026-04-22T20:58:57.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413, upload-time = "2026-04-22T20:59:01.174Z" }, + { url = "https://files.pythonhosted.org/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584, upload-time = "2026-04-22T20:59:07.201Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851, upload-time = "2026-04-22T20:59:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259, upload-time = "2026-04-22T20:59:20.509Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/67/f4452d68793fb15beba4f19ef39a38a8822f0da7452b503c400d5a21f5c1/pyinstaller_hooks_contrib-2026.5.tar.gz", hash = "sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08", size = 173689, upload-time = "2026-05-04T22:36:55.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/5c/fd465d11da4d12b50d7eb5d2ee2ceb780d8d049dbb489f3828d131e387af/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl", hash = "sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9", size = 457314, upload-time = "2026-05-04T22:36:53.598Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pyppmd" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/d7/803232913cab9163a1a97ecf2236cd7135903c46ac8d49613448d88e8759/pyppmd-1.3.1.tar.gz", hash = "sha256:ced527f08ade4408c1bfc5264e9f97ffac8d221c9d13eca4f35ec1ec0c7b6b2e", size = 1351815, upload-time = "2025-11-27T22:08:44.175Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/10/ac2a011af1c7c40b6482e60d85d267ac5923fb8794b51bfddbb56b04d1ec/pyppmd-1.3.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ea53f71ac16e113599b8441a9d8b6dcd71cfdf15cdb33ba5151810b8e656c5ec", size = 77897, upload-time = "2025-11-27T22:08:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/20/13/737f4dac06685865d23f51b7061dbe9373c7bcc60b5b6456cd08301bc807/pyppmd-1.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:985c8703b53e5f68fe17f653e96748d60b1f855676c852a6e67cd472eb853671", size = 48268, upload-time = "2025-11-27T22:08:18.263Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d3/c50ebaee8b8a064fe9a534e98df5051e309efb705728aadff4e6893cd87f/pyppmd-1.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33daa996ad5203c665c0b55aff6329817b2cb7fa95f2c33a2e83ed0121b400cb", size = 48521, upload-time = "2025-11-27T22:08:19.742Z" }, + { url = "https://files.pythonhosted.org/packages/80/6c/4a97c0a0ab7640aeddde4843cb6381b80ecb71fe2c6c48b9032d60586b11/pyppmd-1.3.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b49870c6d7194f6eb80f30335ca03596d153e02fcde2c222e4f1202ac25f7fcf", size = 142892, upload-time = "2025-11-27T22:08:20.972Z" }, + { url = "https://files.pythonhosted.org/packages/8f/22/744c32c7da3de171d9e62a1b2cb4104544ac778358565a3dbc06a2253324/pyppmd-1.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:385e92c97c42e8a6f0bfc0e4acfc6c074cb1ba3a2f650f292696dd9f19e2e603", size = 144550, upload-time = "2025-11-27T22:08:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/4a/18/84af6808e0754923062122d3ee9f0532050f1612069d558bb5d53978e67a/pyppmd-1.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:017a1e2903f1c3147a1046db486990d401e8a25eb52c320b1fc2fb3e7b83cbeb", size = 139850, upload-time = "2025-11-27T22:08:23.863Z" }, + { url = "https://files.pythonhosted.org/packages/20/5e/dc6166ea7929d442625301289d99313a5b358e3cb2e927a6bd913047e8ee/pyppmd-1.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f2770a4b777c0c5236b3d9294b7bf4bc15538c95d45b2079eb8ebc1298e62e37", size = 142589, upload-time = "2025-11-27T22:08:25.228Z" }, + { url = "https://files.pythonhosted.org/packages/77/92/adc6b12ddf11173a02fd631010f88ad4fe4c532363959638d5d53583a3d9/pyppmd-1.3.1-cp314-cp314-win32.whl", hash = "sha256:b9d54cd59ce97f2ba57be1da91b3d874d129faca21c9565d7afec111f942e6a1", size = 42761, upload-time = "2025-11-27T22:08:26.917Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/5003a413d9f2743298a070df6713a75dedccc470e7adeeced5b43cad7418/pyppmd-1.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0e64247618cb150d2909beb0137da3084fef1d3479b4cc73b5b47fda7611abf9", size = 47806, upload-time = "2025-11-27T22:08:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/07/b7/db927e1df7fc3132cb57960f4ea23bf174c7e86ccec22857e6a35cccdae7/pyppmd-1.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:d354d6e551d2630b0ac98f27e3ad63e86cdcac9ce2115b5dfe46e2c9d3f4e82c", size = 46122, upload-time = "2025-11-27T22:08:29.191Z" }, + { url = "https://files.pythonhosted.org/packages/a7/5f/3150227624f374b06e331a7f67ae3383e796dd90973ee17ec3e35d30524c/pyppmd-1.3.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2d77ed79662c32e2551748d59763cfe3dcd10855bf3495937e3d5e5917507818", size = 78876, upload-time = "2025-11-27T22:08:30.635Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/11a3bba62d1d92050f4dd86db810062b506ec76d20b6e00b4ab567ff15e4/pyppmd-1.3.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6a1f92b94635c23d85270bb26db25cc0db544e436af86efc1cf58302d71d5af1", size = 48830, upload-time = "2025-11-27T22:08:32.087Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/e8f50cf89c689543d5a16c7be3df3c4afd43cbeb1a019b84ff9e82f13415/pyppmd-1.3.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:610f214f2405e27eb5a3dad7fa15f385cfc42141a01cda71995d9c1e0b09fab9", size = 48956, upload-time = "2025-11-27T22:08:33.567Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0f/944b30679a8dea3ea95a66531ee30cb6feb5d011ec7fd6832069d9130bf3/pyppmd-1.3.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae81a14895498d9a23429d92114c98da478b74b8e33251527d7cff3e01c09de0", size = 152872, upload-time = "2025-11-27T22:08:35.098Z" }, + { url = "https://files.pythonhosted.org/packages/ae/87/1e48ea92994f4c72dc9b5520a6a386b21c524d3d2969c2c2a5c325929ad7/pyppmd-1.3.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1683e6d1ba09e377e0ae02de3a518191a3d63ccdb0b6037c74e6ddf577b5644", size = 154210, upload-time = "2025-11-27T22:08:36.368Z" }, + { url = "https://files.pythonhosted.org/packages/3f/21/650911f98c4cb360442724bbdade27cb3679b0587ea77e73512693d2918d/pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e4d74fa0f3531e9dadc56e0ace41bce82d3c0babed47b3f224101dc0dbde7287", size = 147636, upload-time = "2025-11-27T22:08:37.657Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/4f8cfc1dd67b04ced8c10669fa14c4e35a42cf4a84e1101793735a82d5f0/pyppmd-1.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f8d6375b18b9c79127fee0885cfd52e2e983edb67041464309426571d38dcd4", size = 150998, upload-time = "2025-11-27T22:08:38.951Z" }, + { url = "https://files.pythonhosted.org/packages/c4/da/dc9e5e10bc56056bd8c33e533517cb327752cbaf172688bba3c23f6b2318/pyppmd-1.3.1-cp314-cp314t-win32.whl", hash = "sha256:de87f7acd575fb07a4ff42d41bcc071570fe759a36f345f1f54f574ecfccfc5b", size = 43016, upload-time = "2025-11-27T22:08:40.234Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a0/46dc15549ad2c0427a9825730e6bdf342045ad410543368afd4389a16f36/pyppmd-1.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:76e4800aa67292b4cc80058fd29b39e02a5dded721af9fe5654f356ef24307f4", size = 48636, upload-time = "2025-11-27T22:08:41.45Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7b/7e9a83848de928c26f0ab3ee105c0da63a932a0804a94807205e6313e73a/pyppmd-1.3.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e066cbf1d335fe20480cd8ecc10848ba78d99fe6d1e44ea00def48feaf46afdf", size = 46402, upload-time = "2025-11-27T22:08:42.804Z" }, +] + +[[package]] +name = "pyside6" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-addons" }, + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/95/f3f5a2799163b6658126d78a85bc1dec9eda88c75c26780556b26071a1d8/pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f", size = 571544, upload-time = "2026-03-23T12:47:20.263Z" }, + { url = "https://files.pythonhosted.org/packages/da/89/9a1f521051714e6694ebbe2b979ded279845ec8e25cb309ca3960158d74f/pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00", size = 571725, upload-time = "2026-03-23T12:47:21.727Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/f779d8bba00fcde31a7d7fb6b59347a70773c9cc8135592dea9972579877/pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e", size = 571722, upload-time = "2026-03-23T12:47:22.761Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/150e01a026df3e9697310236821fa825319bb4b9d6137539cb25a3032968/pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc", size = 577988, upload-time = "2026-03-23T12:47:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/e7/55960f7c6b41d058e95cb4af02652c46c48702c506c8bbf12e99550e1fb3/pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e", size = 561372, upload-time = "2026-03-23T12:47:25.073Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/df/241f311c61a46b7b1195927da77b2537692ee3442aa9ccd87981164ff78d/pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182", size = 331554157, upload-time = "2026-03-23T12:40:40.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/b9/e81172835ccc9d8b9792cc6bf7524a252a0db9a76ddd693de230402697f9/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35", size = 174948482, upload-time = "2026-03-23T12:41:05.379Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/426d9333782bf65ab2a20257d6b4b3af9b8d5d7a710da719865fab49d492/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753", size = 170430798, upload-time = "2026-03-23T12:41:38.134Z" }, + { url = "https://files.pythonhosted.org/packages/35/9a/46d271fedfabad8c6dce2ebb69bb593745487ed33753a56a47c3ba4fdb1c/pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e", size = 168723088, upload-time = "2026-03-23T12:42:00.668Z" }, + { url = "https://files.pythonhosted.org/packages/16/cd/1b28264f7dc9a642da2e4e7c02f67418d0949eb7ce329ae20869703c2630/pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8", size = 35698324, upload-time = "2026-03-23T12:42:13.748Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/00/8a8583d3429c737cc20e61a43eba8ab1ec13ddb101e99802c2ffeedf3b41/pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01", size = 108085251, upload-time = "2026-03-23T12:42:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a9/07c9e5c014b871c1b19caf8f994bcd50b345559b81f81671217b49559b67/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849", size = 78316055, upload-time = "2026-03-23T12:43:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/7c/35/f06b1b641d7600ec46374c16cd37c66fa4a22870326b4eb073a95471035f/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e", size = 77380821, upload-time = "2026-03-23T12:43:24.649Z" }, + { url = "https://files.pythonhosted.org/packages/ff/37/ba95c6262836d2b286b4e05a9d16a5e870995d5d2503ac6adc6312208049/pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf", size = 75793322, upload-time = "2026-03-23T12:43:35.575Z" }, + { url = "https://files.pythonhosted.org/packages/53/27/d17f25e45820e633a70e6109b35991eda09a5e8000c2a306f0ab7538d48c/pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187", size = 56337457, upload-time = "2026-03-23T12:43:43.573Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { 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" } +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" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +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" } +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" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[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/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 = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "questionary" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { 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" } +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" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "shiboken6" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/1d/b56b7b694fbc871496435488d1f41c5068de546334850d722756511cef65/shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b", size = 476085, upload-time = "2026-03-23T12:47:05.724Z" }, + { url = "https://files.pythonhosted.org/packages/65/cb/4bb0c76011166230daa7c0074aeb3fdb3935c83ac1fef3789b85fcd1a8fc/shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053", size = 271055, upload-time = "2026-03-23T12:47:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/f5/96/771a6e2b530f725303d16d78a321fa4876b98b4f3615c9851880df8c1a43/shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9", size = 267456, upload-time = "2026-03-23T12:47:08.689Z" }, + { url = "https://files.pythonhosted.org/packages/72/f7/44c0c42c3f5f29dec457fd46ea0552174bcb8aa75becf03bbd90308ba07b/shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1", size = 1222132, upload-time = "2026-03-23T12:47:10.143Z" }, + { url = "https://files.pythonhosted.org/packages/fb/99/6e5ee21db2d6af84bbbd7d871d441dafeb069c6de5667b1aa49891a77c66/shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4", size = 1783186, upload-time = "2026-03-23T12:47:11.832Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, +] + +[[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 = "termcolor" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, +] + +[[package]] +name = "texttable" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/dc/0aff23d6036a4d3bf4f1d8c8204c5c79c4437e25e0ae94ffe4bbb55ee3c2/texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638", size = 12831, upload-time = "2023-10-03T09:48:12.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917", size = 10768, upload-time = "2023-10-03T09:48:10.434Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "types-pywin32" +version = "311.0.0.20260408" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/40/0d182fbf578f30f7ff2b07b8fe494cc42178992d0087a739c70990adca8c/types_pywin32-311.0.0.20260408.tar.gz", hash = "sha256:cb86c6beae20195165e770a65c3ee707746dc777ca8e03e4f06a66d4013a4bd0", size = 332341, upload-time = "2026-04-08T04:33:29.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/b5/3cc67baf622805270d84e2252dfa130daf7ccd49795f80b51350abb91bd9/types_pywin32-311.0.0.20260408-py3-none-any.whl", hash = "sha256:0b691da60aaed0ee7169a69268bad1e2051eb52f4acc10248c103aadcd1f2451", size = 395413, upload-time = "2026-04-08T04:33:28.476Z" }, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260503" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b8/57e94268c0d82ac3eaa2fc35aa8ca7bbc2542f726b67dcf90b0b00a3b14d/types_requests-2.33.0.20260503.tar.gz", hash = "sha256:9721b2d9dbee7131f2fb39f20f0ebb1999c18cef4b512c9a7932f3722de7c5f4", size = 23931, upload-time = "2026-05-03T05:20:08.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/82/959113a6351f3ca046cd0a8cd2cee071d7ea47473560557a01eeae9a6fe2/types_requests-2.33.0.20260503-py3-none-any.whl", hash = "sha256:02aaa7e3577a13471715bb1bddb693cc985ea514f754b503bf033e6a09a3e528", size = 20736, upload-time = "2026-05-03T05:20:07.858Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +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" } +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" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +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" } +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" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, +] + +[[package]] +name = "wheel" +version = "0.46.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "wrapt" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, + { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, + { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, + { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, +] + +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +] From 8f8c8eb455e08444f5f5f9037bd25bf9fa38090f Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 7 May 2026 14:00:45 +0300 Subject: [PATCH 148/190] docs(README.md): Document how to use uv for setting up the Python environment --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e65e83cd..d38eb95b 100644 --- a/README.md +++ b/README.md @@ -183,37 +183,32 @@ You may supply Proxyshop with image and JSON pairs to render cards with custom s # 🐍 Setup Guide (Python Environment) Setting up the Python environment for Proxyshop is intended for advanced users, contributors, and anyone who wants to -get their hands dirty making a plugin or custom template for the app! This guide assumes you already have Python installed. -See `pyproject.toml` for supported Python versions. -1. Install Poetry with pipx. - ```bash - # Install pipx and poetry - python -m pip install --user pipx - python -m pipx ensurepath - pipx install poetry - # Store the virtual environment in the project directory - poetry config virtualenvs.in-project true +get their hands dirty making a plugin or custom template for the app! +1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/) +2. If you don't have Python installed yet, you may install it with uv. See `pyproject.toml` for supported Python versions. + ```bash + uv python install 3.14 ``` -2. Clone Proxyshop somewhere on your system, we'll call this the ***root directory***. +3. Clone Proxyshop somewhere on your system, we'll call this the ***root directory***. ```bash git clone https://github.com/MrTeferi/Proxyshop.git ``` -3. Navigate to the **root directory** and install the project environment. +4. Navigate to the **root directory** and install the project environment. ```bash cd proxyshop - poetry install + uv sync ``` -4. Install the fonts included in the `fonts/` folder. -5. Run the app. +5. Install the fonts included in the `fonts/` folder. +6. Run the app. ```bash # OPTION 1) Activate the virtual environment and run the app's entrypoint with Python ./.venv/Scripts/Activate python main.py - # OPTION 2) Execute via poetry - poetry run python main.py + # OPTION 2) Execute via uv + uv run main.py ``` -6. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. +7. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. # 🖥 Development Environment From c79bf040eb1f79a43327f90f5d89c9ca2be43e52 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 10 May 2026 05:46:56 +0300 Subject: [PATCH 149/190] fix(templates/__init__.py): Re-export the prepare module so that it usable in the built version of the app --- src/templates/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templates/__init__.py b/src/templates/__init__.py index 25118b2e..e71dc511 100644 --- a/src/templates/__init__.py +++ b/src/templates/__init__.py @@ -15,6 +15,7 @@ from src.templates.normal import * from src.templates.planar import * from src.templates.planeswalker import * +from src.templates.prepare import * from src.templates.prototype import * from src.templates.saga import * from src.templates.split import * From 04d74a2807792f448c8db63c24c43b65cf16f7b3 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 10 May 2026 06:08:16 +0300 Subject: [PATCH 150/190] fix(ConfigHandler): Create the config ini directory if it doesn't exist --- src/_loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_loader.py b/src/_loader.py index b931d25a..712d4e44 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -310,6 +310,8 @@ def __init__( self.schema_path = schema_path self.ini_path = ini_path + self.ini_path.parent.mkdir(parents=True, exist_ok=True) + self.config_added: SubscribableEvent[ConfigHandler] = SubscribableEvent() self.config_reset: SubscribableEvent[ConfigHandler] = SubscribableEvent() self.config_deleted: SubscribableEvent[ConfigHandler] = SubscribableEvent() From e0b72fc9c315ce794e49d0ac33f852f9f99431ba Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 18 May 2026 10:59:33 +0300 Subject: [PATCH 151/190] feat: Add support for filtering settings --- src/gui/qml/components/CustomTextField.qml | 13 +++- src/gui/qml/views/SettingsWindow.qml | 74 +++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/gui/qml/components/CustomTextField.qml b/src/gui/qml/components/CustomTextField.qml index fe64461a..5efae8fc 100644 --- a/src/gui/qml/components/CustomTextField.qml +++ b/src/gui/qml/components/CustomTextField.qml @@ -1,9 +1,20 @@ import QtQuick import QtQuick.Controls +import QtQuick.Controls.impl TextField { + id: control + implicitHeight: 30 leftPadding: 4 rightPadding: 4 - background.implicitWidth: 0 + background: Rectangle { + implicitWidth: 0 + implicitHeight: control.implicitHeight + color: control.palette.base + + // Custom active highlight + border.color: control.activeFocus ? control.palette.highlight : "transparent" + border.width: control.activeFocus ? 2 : 0 + } } \ No newline at end of file diff --git a/src/gui/qml/views/SettingsWindow.qml b/src/gui/qml/views/SettingsWindow.qml index 28cc052c..a7d5d422 100644 --- a/src/gui/qml/views/SettingsWindow.qml +++ b/src/gui/qml/views/SettingsWindow.qml @@ -22,6 +22,39 @@ ApplicationWindow { visible: true color: systemPalette.window + component RoleData: QtObject { + property string type + property string title + } + + SortFilterProxyModel { + id: sfSettingsModel + + model: settingsWindow.settingsModel + filters: [ + FunctionFilter { + id: settingsFilter + + property string searchString: "" + property string effectiveSearchString: searchString.toLowerCase() + + function filter(data: RoleData): bool { + if (!settingsFilter.searchString) + return true; + return data.type !== "title" && data.title.toLowerCase().includes(settingsFilter.effectiveSearchString); + } + } + ] + } + + Timer { + id: settingsFilterInvalidationDebounce + + interval: 150 + repeat: false + onTriggered: settingsFilter.invalidate() + } + Settings { id: settings @@ -129,6 +162,8 @@ ApplicationWindow { spacing: 0 Rectangle { + id: headerBackground + Layout.alignment: Qt.AlignTop Layout.fillWidth: true Layout.minimumHeight: headerTitle.height + 10 @@ -145,13 +180,48 @@ ApplicationWindow { id: headerTitle Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true Layout.leftMargin: 10 text: settingsWindow.settingsTreeModel.selected_title font.pointSize: 16 color: settingsWindow.systemPalette.text } + Rectangle { + Layout.fillWidth: true + Layout.minimumHeight: settingsSearchInput.height + Layout.maximumHeight: settingsSearchInput.height + + color: settingsWindow.systemPalette.base + + RowLayout { + anchors.fill: parent + spacing: 3 + + Text { + Layout.leftMargin: 3 + + text: `🔍` + font.pointSize: 11 + } + CustomTextField { + id: settingsSearchInput + + Layout.fillWidth: true + + text: settingsFilter.searchString + color: settingsWindow.systemPalette.text + placeholderText: "Search settings" + + function onSetValue() { + settingsFilter.searchString = settingsSearchInput.text; + settingsFilterInvalidationDebounce.restart(); + } + + //onEditingFinished: onSetValue() + onTextEdited: onSetValue() + } + } + } CustomButton { id: resetButton @@ -199,7 +269,7 @@ ApplicationWindow { clip: true spacing: 5 highlightFollowsCurrentItem: false - model: settingsWindow.settingsModel + model: sfSettingsModel delegate: ItemDelegate { id: settingsListDelegate From fa150c924a6d7c2cd568f340461690427788cbf7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 05:32:34 +0300 Subject: [PATCH 152/190] feat: Allow clicking links in selectable text components --- src/gui/qml/components/SelectableText.qml | 7 +++++++ src/gui/qml/views/Console.qml | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/qml/components/SelectableText.qml b/src/gui/qml/components/SelectableText.qml index 349f4bc7..708e701f 100644 --- a/src/gui/qml/components/SelectableText.qml +++ b/src/gui/qml/components/SelectableText.qml @@ -5,4 +5,11 @@ TextEdit { textFormat: TextEdit.AutoText wrapMode: Text.WordWrap selectByMouse: true + onLinkActivated: Qt.openUrlExternally(hoveredLink) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : undefined + } } diff --git a/src/gui/qml/views/Console.qml b/src/gui/qml/views/Console.qml index 284596cb..abf7351c 100644 --- a/src/gui/qml/views/Console.qml +++ b/src/gui/qml/views/Console.qml @@ -91,13 +91,6 @@ ColumnLayout { color: root.systemPalette.text text: logDelegate.message font.family: root.monospaceFontFamily - onLinkActivated: Qt.openUrlExternally(hoveredLink) - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : undefined - } } } From b5fddd36bddc019dfc8d7b64c03e7011243c9b6a Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 05:34:38 +0300 Subject: [PATCH 153/190] perf(colors.py): Avoid unnecessary Photoshop API object instantiations --- src/helpers/colors.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/helpers/colors.py b/src/helpers/colors.py index 9471a539..fce964d2 100644 --- a/src/helpers/colors.py +++ b/src/helpers/colors.py @@ -101,6 +101,14 @@ def get_rgb_from_hex(hex_code: str) -> SolidColor: return color +def get_rgb_tuple(color: ColorObject) -> tuple[float, float, float]: + if isinstance(color, tuple) and len(color) == 3: + return color + colr = get_color(color) + rgb = colr.rgb + return (rgb.red, rgb.green, rgb.blue) + + def get_cmyk(c: float, m: float, y: float, k: float) -> SolidColor: """Creates a SolidColor object with the given CMYK values. @@ -276,7 +284,8 @@ def apply_rgb( c: SolidColor object matching RGB model. color_type: Color action descriptor type, defaults to 'color'. """ - apply_rgb_from_list(action, (c.rgb.red, c.rgb.green, c.rgb.blue), color_type) + rgb = c.rgb + apply_rgb_from_list(action, (rgb.red, rgb.green, rgb.blue), color_type) def apply_cmyk( @@ -289,8 +298,9 @@ def apply_cmyk( c: SolidColor object matching CMYK model. color_type: Color action descriptor type, defaults to 'color'. """ + cmyk = c.cmyk apply_cmyk_from_list( - action, (c.cmyk.cyan, c.cmyk.magenta, c.cmyk.yellow, c.cmyk.black), color_type + action, (cmyk.cyan, cmyk.magenta, cmyk.yellow, cmyk.black), color_type ) From 54f5bc9dad755d34d4e92fd68c68f38557f19b00 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 05:42:23 +0300 Subject: [PATCH 154/190] refactor(ClassicTemplate): Remove redundant override of collector_info function --- src/templates/normal.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/templates/normal.py b/src/templates/normal.py index 4169afb4..b7ae6905 100644 --- a/src/templates/normal.py +++ b/src/templates/normal.py @@ -17,7 +17,6 @@ from src.enums.settings import ( BorderlessColorMode, BorderlessTextbox, - CollectorMode, ModernClassicCrown, ) from src.frame_logic import is_multicolor_string @@ -507,20 +506,6 @@ def align_collector_layers(self, layers: Iterable[ArtLayer | None]) -> None: if self.is_align_collector_left and self.collector_reference: [psd.align_left(n, ref=self.collector_reference.dims) for n in layers] - def collector_info(self) -> None: - """Format and add the collector info at the bottom.""" - - # Which collector info mode? - if ( - self.config.collector_mode in [CollectorMode.Default, CollectorMode.Modern] - and self.layout.collector_data - ): - self.collector_info_authentic() - elif self.config.collector_mode == CollectorMode.ArtistOnly: - self.collector_info_artist_only() - else: - self.collector_info_basic() - def collector_info_basic(self) -> None: """Called to generate basic collector info.""" From 851836fe57988d32544bb635d0e25ad51b5b4b74 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 05:43:36 +0300 Subject: [PATCH 155/190] feat(prepare_test_render): Disable Scryfall scan import in test renders --- src/utils/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/tests.py b/src/utils/tests.py index 623478f6..555fc810 100644 --- a/src/utils/tests.py +++ b/src/utils/tests.py @@ -20,6 +20,7 @@ def prepare_test_render(render_operation: RenderOperation, config: AppConfig): """Modifies render operation so that the render process won't pause for manual editing. Call this after loading the config for a test render.""" config.fill_mode = FillMode.NO_FILL + config.import_scryfall_scan = False render_operation.do_not_pause = True From a079ac74aba08cd8d453bdc036aed1a8f0a12de9 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 05:46:35 +0300 Subject: [PATCH 156/190] fix(_loader.py): Disable interpolation in config parsers as it causes problems with percent symbols --- src/_loader.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_loader.py b/src/_loader.py index 712d4e44..b86fed4e 100644 --- a/src/_loader.py +++ b/src/_loader.py @@ -3,7 +3,7 @@ """ from collections.abc import Callable -from configparser import ConfigParser +from configparser import RawConfigParser from contextlib import suppress from enum import Enum from functools import cached_property @@ -267,7 +267,7 @@ def post_validate(self) -> PluginManifest: # region Configs -class CustomConfigParser(ConfigParser): +class CustomConfigParser(RawConfigParser): def optionxform(self, optionstr: str) -> str: return optionstr @@ -293,7 +293,7 @@ def parse_settings_config(data_path: Path) -> FormattedSettingsConfig: } -def configparser_to_dict(parser: ConfigParser) -> dict[str, dict[str, str]]: +def configparser_to_dict(parser: RawConfigParser) -> dict[str, dict[str, str]]: return { section: {key: value for key, value in content.items()} for section, content in parser.items() @@ -385,14 +385,14 @@ def validator(v: Any, handler: Callable[[Any], Any]) -> Any: ) @cached_property - def _parser(self) -> ConfigParser: + def _parser(self) -> RawConfigParser: parser = CustomConfigParser(default_section="", allow_no_value=True) if self.ini_path.is_file(): parser.read_string(self.ini_path.read_text(encoding="utf-8")) return parser @property - def parser(self) -> ConfigParser: + def parser(self) -> RawConfigParser: vals = self.setting_values self._parser.clear() self._parser.read_dict(vals) From 090004ef255e22f4bf5ed954974e18a764d8e125 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 06:26:01 +0300 Subject: [PATCH 157/190] feat: Allow spcifying user defined custom formats for collector info layers --- src/_config.py | 6 + src/data/config/base.toml | 35 ++++- src/enums/settings.py | 30 +--- src/helpers/descriptors.py | 303 ++++++++++++++++++++++++++++++++++++- src/helpers/text.py | 109 +++++++++++-- src/layouts.py | 24 ++- src/templates/_core.py | 88 ++++++++++- src/utils/text.py | 75 +++++++++ 8 files changed, 604 insertions(+), 66 deletions(-) create mode 100644 src/utils/text.py diff --git a/src/_config.py b/src/_config.py index 194253de..1c366fdf 100644 --- a/src/_config.py +++ b/src/_config.py @@ -142,6 +142,12 @@ def update_definitions(self): self.collector_mode = self.get_option( "BASE.TEXT", "Collector.Mode", CollectorMode, default=CollectorMode.Normal ) + self.collector_line_a_format = self.get_setting( + "BASE.TEXT", "Collector.Line.A", default="" + ) + self.collector_line_b_format = self.get_setting( + "BASE.TEXT", "Collector.Line.B", default="" + ) self.collector_promo = self.get_option( "BASE.TEXT", "Collector.Promo", diff --git a/src/data/config/base.toml b/src/data/config/base.toml index fe46892d..1c764e30 100644 --- a/src/data/config/base.toml +++ b/src/data/config/base.toml @@ -32,10 +32,41 @@ title = "Collector Mode" desc = """Default: Formats collector info like modern cards pre-2023. Modern: Formats collector info like March of the Machine and onwards. Minimal: Only includes artist, set code, and language. -Artist: Only includes artist.""" +Artist: Only includes artist. +Custom: Formats collector info lines according to specified custom formats.""" type = "options" default = "default" -options = ["default", "modern", "minimal", "artist"] +options = ["default", "modern", "minimal", "artist", "custom"] + +[TEXT."Collector.Line.A"] +title = "First Collector Info Line Format" +desc = """Fills the first collector info line with given text. The following variable substitutions may be used in the text:
+
+{pen}: Artist pen icon.
+{artist}: Card's artist.
+{number}: Card's collector number.
+{set}: Card's set.
+{rarity}: Card's rarity code.
+{lang}: Card's language code.
+{date:}: Current datetime formatted with strftime(). +

+If no text is provided, the layer will be hidden. +

+Example:
+With Together as One [SOS] {4}
+Static Text {lang} - {set} - {number}
+would convert to
+Static Text EN - SOS - 4 +

+This is an experimental setting and the format syntax might change in a later version.""" +type = "string" +default = "" + +[TEXT."Collector.Line.B"] +title = "Second Collector Info Line Format" +desc = "Fills the second collector info line with given text. Supports the same variable substitutions as the first line." +type = "string" +default = "" [TEXT."Collector.Promo"] title = "Collector Promo Star" diff --git a/src/enums/settings.py b/src/enums/settings.py index 15c5a2b3..0edfd87a 100644 --- a/src/enums/settings.py +++ b/src/enums/settings.py @@ -2,14 +2,7 @@ * Enums: Settings """ -from enum import StrEnum, nonmember -from typing import Protocol, runtime_checkable - - -@runtime_checkable -class HasDefault(Protocol): - Default: nonmember[str] - +from enum import StrEnum """ * App Settings @@ -21,8 +14,6 @@ class OutputFileType(StrEnum): PNG = "png" PSD = "psd" - Default = nonmember(JPG) - class ScryfallSorting(StrEnum): Released = "released" @@ -33,15 +24,11 @@ class ScryfallSorting(StrEnum): EDHRec = "edhrec" Artist = "artist" - Default = nonmember(Released) - class ScryfallUnique(StrEnum): Prints = "prints" Arts = "arts" - Default = nonmember(Arts) - """ * Base Settings @@ -53,8 +40,7 @@ class CollectorMode(StrEnum): Modern = "modern" Minimal = "minimal" ArtistOnly = "artist" - - Default = nonmember(Normal) + Custom = "custom" class BorderColor(StrEnum): @@ -63,16 +49,12 @@ class BorderColor(StrEnum): Silver = "silver" Gold = "gold" - Default = nonmember(Black) - class CollectorPromo(StrEnum): Automatic = "automatic" Always = "always" Never = "never" - Default = nonmember(Automatic) - class WatermarkMode(StrEnum): Disabled = "Disabled" @@ -80,8 +62,6 @@ class WatermarkMode(StrEnum): Fallback = "Fallback" Forced = "Forced" - Default = nonmember(Disabled) - class FillMode(StrEnum): NO_FILL = "No Fill" @@ -109,8 +89,6 @@ class BorderlessColorMode(StrEnum): PT = "PT Box" Disabled = "None" - Default = nonmember(Twins_And_PT) - class BorderlessTextbox(StrEnum): Automatic = "Automatic" @@ -120,8 +98,6 @@ class BorderlessTextbox(StrEnum): Short = "Short" Tall = "Tall" - Default = nonmember(Automatic) - """ * Template: Modern Classic @@ -132,5 +108,3 @@ class ModernClassicCrown(StrEnum): Pinlines = "Pinlines" TexturePinlines = "Texture Pinlines" TextureBackground = "Texture Background" - - Default = nonmember(Pinlines) diff --git a/src/helpers/descriptors.py b/src/helpers/descriptors.py index b8a61cbe..17b1c983 100644 --- a/src/helpers/descriptors.py +++ b/src/helpers/descriptors.py @@ -2,11 +2,21 @@ * Helpers: PS Object Descriptors """ -from photoshop.api import ActionDescriptor, ActionReference +from collections.abc import Callable +from ctypes import COMError +from logging import getLogger +from os import PathLike + +from photoshop.api import ActionDescriptor, ActionList, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DescValueType from src import APP +from src.helpers.colors import apply_color +from src.schema.colors import ColorObject + +_logger = getLogger(__name__) """ * Layer Action Descriptors @@ -25,3 +35,294 @@ def get_layer_action_descriptor(layer: ArtLayer | LayerSet) -> ActionDescriptor: ref = ActionReference() ref.putIdentifier(APP.instance.sID("layer"), layer.id) return APP.instance.executeActionGet(ref) + + +# region Copying + + +def copy_descriptor( + source: ActionDescriptor, recursive: bool = False +) -> ActionDescriptor: + """Creates a copy of an ActionDescriptor by copying all properties from source. + + Args: + source: Descriptor to copy from. + recursive: Whether to create copies of nested descriptors or not. + + Returns: + New descriptor with all properties copied from source. + """ + copy = ActionDescriptor() + + for i in range(source.count): + copy_descriptor_value( + source.getKey(i), source=source, target=copy, recursive=recursive + ) + + return copy + + +def copy_descriptor_value( + key: int, + source: ActionDescriptor, + target: ActionDescriptor, + recursive: bool = False, +) -> None: + """Copies value at key from source to target descriptor.""" + if source.hasKey(key): + value_type = source.getType(key) + try: + if value_type == DescValueType.AliasType: + target.putPath(key, source.getPath(key)) + elif value_type == DescValueType.BooleanType: + target.putBoolean(key, source.getBoolean(key)) + elif value_type == DescValueType.ClassType: + target.putClass(key, source.getClass(key)) + elif value_type == DescValueType.DoubleType: + target.putDouble(key, source.getDouble(key)) + elif value_type == DescValueType.EnumeratedType: + enum_type = source.getEnumerationType(key) + enum_value = source.getEnumerationValue(key) + target.putEnumerated(key, enum_type, enum_value) + elif value_type == DescValueType.IntegerType: + target.putInteger(key, source.getInteger(key)) + elif value_type == DescValueType.LargeIntegerType: + target.putLargeInteger(key, source.getLargeInteger(key)) + elif value_type == DescValueType.ListType: + target.putList(key, source.getList(key)) + elif value_type == DescValueType.ObjectType: + obj_type = source.getObjectType(key) + obj_desc = source.getObjectValue(key) + # Recursive copy for nested objects + target.putObject( + key, obj_type, copy_descriptor(obj_desc) if recursive else obj_desc + ) + elif value_type == DescValueType.RawType: + target.putData(key, source.getData(key)) + elif value_type == DescValueType.ReferenceType: + target.putReference(key, source.getReference(key)) + elif value_type == DescValueType.StringType: + target.putString(key, source.getString(key)) + elif value_type == DescValueType.UnitDoubleType: + unit_type = source.getUnitDoubleType(key) + unit_value = source.getUnitDoubleValue(key) + target.putUnitDouble(key, unit_type, unit_value) + else: + _logger.warning( + f"ActionDescriptor value at key '{key}' has an unknown type '{value_type}'" + ) + except COMError: + # Skip properties that cannot be copied + _logger.warning( + f"Can't copy key '{key}' of type '{value_type}' from ActionDescriptor" + ) + + +# endregion Copying + +# region Descriptor Value Setters + + +def set_or_copy_alias( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: str | PathLike[str] | None = None, +) -> None: + """Set alias (path) value or copy from source if value is None.""" + if value is not None: + target.putPath(key, value) + elif source is not None and source.hasKey(key): + target.putPath(key, source.getPath(key)) + + +def set_or_copy_boolean( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: bool | None = None, +) -> None: + """Set boolean value or copy from source if value is None.""" + if value is not None: + target.putBoolean(key, value) + elif source is not None and source.hasKey(key): + target.putBoolean(key, source.getBoolean(key)) + + +def set_or_copy_class( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: int | None = None, +) -> None: + """Set class value or copy from source if value is None.""" + if value is not None: + target.putClass(key, value) + elif source is not None and source.hasKey(key): + target.putClass(key, source.getClass(key)) + + +def set_or_copy_double( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: float | None = None, +) -> None: + """Set double value or copy from source if value is None.""" + if value is not None: + target.putDouble(key, value) + elif source is not None and source.hasKey(key): + target.putDouble(key, source.getDouble(key)) + + +def set_or_copy_enumerated( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + enum_type: int | None = None, + enum_value: int | None = None, +) -> None: + """Set enumerated value or copy from source if both enum_type and enum_value are None.""" + if enum_type is not None and enum_value is not None: + target.putEnumerated(key, enum_type, enum_value) + elif source is not None and source.hasKey(key): + source_enum_type = source.getEnumerationType(key) + source_enum_value = source.getEnumerationValue(key) + target.putEnumerated(key, source_enum_type, source_enum_value) + + +def set_or_copy_integer( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: int | None = None, +) -> None: + """Set integer value or copy from source if value is None.""" + if value is not None: + target.putInteger(key, value) + elif source is not None and source.hasKey(key): + target.putInteger(key, source.getInteger(key)) + + +def set_or_copy_large_integer( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: int | None = None, +) -> None: + """Set large integer value or copy from source if value is None.""" + if value is not None: + target.putLargeInteger(key, value) + elif source is not None and source.hasKey(key): + target.putLargeInteger(key, source.getLargeInteger(key)) + + +def set_or_copy_list( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: ActionList | None = None, +) -> None: + """Set list value or copy from source if value is None.""" + if value is not None: + target.putList(key, value) + elif source is not None and source.hasKey(key): + target.putList(key, source.getList(key)) + + +def set_or_copy_object( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + obj_type: int | None = None, + obj_desc: ActionDescriptor | None = None, +) -> None: + """Set object value or copy from source if both obj_type and obj_desc are None.""" + if obj_type is not None and obj_desc is not None: + target.putObject(key, obj_type, obj_desc) + elif source is not None and source.hasKey(key): + source_obj_type = source.getObjectType(key) + source_obj_desc = source.getObjectValue(key) + target.putObject(key, source_obj_type, source_obj_desc) + + +def set_or_copy_raw( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: str | None = None, +) -> None: + """Set raw (data) value or copy from source if value is None.""" + if value is not None: + target.putData(key, value) + elif source is not None and source.hasKey(key): + target.putData(key, source.getData(key)) + + +def set_or_copy_reference( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: ActionReference | None = None, +) -> None: + """Set reference value or copy from source if value is None.""" + if value is not None: + target.putReference(key, value) + elif source is not None and source.hasKey(key): + target.putReference(key, source.getReference(key)) + + +def set_or_copy_string( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + value: str | None = None, +) -> None: + """Set string value or copy from source if value is None.""" + if value is not None: + target.putString(key, value) + elif source is not None and source.hasKey(key): + target.putString(key, source.getString(key)) + + +def set_or_copy_unit_double( + key: int, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + unit_type: int | None = None, + unit_value: float | None = None, + source_value_hook: Callable[[float], float] | None = None, +) -> None: + """Set unit double value or copy from source if both unit_type and unit_value are None.""" + if unit_type is not None and unit_value is not None: + target.putUnitDouble(key, unit_type, unit_value) + elif source is not None and source.hasKey(key): + source_unit_type = source.getUnitDoubleType(key) + source_unit_value = source.getUnitDoubleValue(key) + target.putUnitDouble( + key, + source_unit_type, + source_value_hook(source_unit_value) + if source_value_hook + else source_unit_value, + ) + + +def set_or_copy_color( + color_str_id: str, + target: ActionDescriptor, + source: ActionDescriptor | None = None, + color: ColorObject | None = None, +) -> None: + """Set object value or copy from source if both obj_type and obj_desc are None.""" + if color is not None: + return apply_color(target, color, color_type=color_str_id) + + key = APP.instance.sID(color_str_id) + if source is not None and source.hasKey(key): + source_obj_type = source.getObjectType(key) + source_obj_desc = source.getObjectValue(key) + target.putObject(key, source_obj_type, source_obj_desc) + + +# endregion Descriptor Value Setters diff --git a/src/helpers/text.py b/src/helpers/text.py index 5bad7a6e..de2e28e0 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -6,7 +6,7 @@ from logging import getLogger from typing import Literal, overload -from photoshop.api import ActionDescriptor, ActionList, ActionReference +from photoshop.api import ActionDescriptor, ActionList, ActionReference, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -20,6 +20,13 @@ get_textbox_width, get_width_no_effects, ) +from src.helpers.descriptors import ( + set_or_copy_color, + set_or_copy_enumerated, + set_or_copy_integer, + set_or_copy_string, + set_or_copy_unit_double, +) from src.helpers.document import pixels_to_points from src.utils.adobe import PS_EXCEPTIONS @@ -30,13 +37,18 @@ """ -def get_font_size(layer: ArtLayer) -> float: +def get_font_size(layer: ArtLayer, raw_size: float | None = None) -> float: """Get scale factor adjusted font size of a given text layer. Args: layer: Text layer to get size of. """ - return round(layer.textItem.size * get_text_scale_factor(layer), 2) + return round( + layer.textItem.size + if raw_size is None + else raw_size * get_text_scale_factor(layer), + 2, + ) def get_text_key(layer: ArtLayer) -> ActionDescriptor: @@ -301,11 +313,67 @@ def remove_leading_text(layer: ArtLayer, idx: int) -> None: apply_text_key(layer, key) +def set_text_style_values( + text_style: ActionDescriptor, + source: ActionDescriptor | None = None, + font: str | None = None, + size: float | None = None, + leading: float | None = None, + kerning: Literal["metricsKern", "opticalKern", "manual"] | None = None, + tracking: int | None = None, + color: SolidColor | None = None, + language: str | None = None, + reference_layer: ArtLayer | None = None, +) -> None: + """Set text style descriptor values.""" + set_or_copy_string(APP.instance.sID("fontPostScriptName"), text_style, source, font) + set_or_copy_unit_double( + APP.instance.sID("size"), + text_style, + source, + unit_type=APP.instance.sID("pointsUnit"), + unit_value=size, + source_value_hook=(lambda value: get_font_size(reference_layer, value)) + if reference_layer + else None, + ) + set_or_copy_unit_double( + APP.instance.sID("leading"), + text_style, + source, + unit_type=APP.instance.sID("pointsUnit"), + unit_value=leading, + ) + kern_id = APP.instance.sID("autoKern") + set_or_copy_enumerated( + kern_id, + text_style, + source, + enum_type=kern_id, + enum_value=APP.instance.sID(kerning) if kerning else None, + ) + set_or_copy_integer(APP.instance.sID("tracking"), text_style, source, tracking) + set_or_copy_color("color", text_style, source, color) + lang_id = APP.instance.sID("textLanguage") + set_or_copy_enumerated( + lang_id, + text_style, + source, + enum_type=lang_id, + enum_value=APP.instance.sID(language) if language else None, + ) + + def override_text_style_ranges( layer: ArtLayer, ranges: Iterable[tuple[int, int]], font: str | None = None, size: float | None = None, + leading: float | None = None, + kerning: Literal["metricsKern", "opticalKern", "manual"] | None = None, + tracking: int | None = None, + color: SolidColor | None = None, + language: str | None = None, ) -> None: """Override style properties in specified character ranges of a text layer. @@ -324,9 +392,6 @@ def override_text_style_ranges( from_id = APP.instance.sID("from") to_id = APP.instance.sID("to") text_style_id = APP.instance.sID("textStyle") - points_unit_id = APP.instance.sID("pointsUnit") - font_post_script_id = APP.instance.sID("fontPostScriptName") - size_id = APP.instance.sID("size") # Get text key and current text text_key = get_text_key(layer) @@ -364,17 +429,33 @@ def override_text_style_ranges( new_range.putInteger(from_id, start) new_range.putInteger(to_id, end) - # Copy the text style from original - text_style = orig_range.getObjectValue(text_style_id) + orig_style = orig_range.getObjectValue(text_style_id) - # Check if this range is overridden overridden = any(ov_start <= start < ov_end for ov_start, ov_end in ranges) if overridden: - # Apply overrides - if font is not None: - text_style.putString(font_post_script_id, font) - if size is not None: - text_style.putUnitDouble(size_id, points_unit_id, size) + # Create new range if values require overriding, + # as editing the original range's text style + # doesn't allow changing unit values, such as size, + # because the Photoshop API doesn't seem to support it. + # The values can be successfully set to the original text + # style descriptor but they just won't come into effect when + # the action is applied. + text_style = ActionDescriptor() + set_text_style_values( + text_style, + orig_style, + font=font, + size=size, + leading=leading, + kerning=kerning, + tracking=tracking, + color=color, + language=language, + reference_layer=layer, + ) + else: + # Use original range's text style if no overriding should be done + text_style = orig_style new_range.putObject(text_style_id, text_style_id, text_style) new_style_ranges.putObject(text_style_range_id, new_range) diff --git a/src/layouts.py b/src/layouts.py index f34b3868..7ee044d9 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -32,7 +32,7 @@ layout_map_types, planeswalkers_tall, ) -from src.enums.settings import CollectorMode, NicknameShorten, WatermarkMode +from src.enums.settings import NicknameShorten, WatermarkMode from src.frame_logic import ( check_hybrid_mana_cost, get_frame_details, @@ -350,11 +350,13 @@ def card(self) -> ScryfallCard | ScryfallCardFace: if not matching_face: face_listing = "\n".join(f" - {face.name}" for face in faces) - _logger.warning(f"None of the card faces
{ + _logger.warning( + f"None of the card faces
{ face_listing }
matches the input file's name { self.input_name - }.
Defaulting to first face.") + }.
Defaulting to first face." + ) return faces[0] return matching_face @@ -616,13 +618,10 @@ def collector_number_raw(self) -> str | None: @cached_property def card_count(self) -> int | None: - """int | None: Number of cards within the card's release set. Only required in 'Normal' Collector Mode.""" + """int | None: Number of cards within the card's release set.""" - # Skip if collector mode doesn't require it or if collector number is bad - if ( - self.config.collector_mode != CollectorMode.Normal - or not self.collector_number_raw - ): + # Skip if collector number is bad + if not self.collector_number_raw: return # Prefer printed count, fallback to card count, skip if count isn't found @@ -1929,11 +1928,8 @@ def set(self) -> str: def card_count(self) -> int | None: """Optional[int]: Use token count for token cards.""" - # Skip if collector mode doesn't require it or if collector number is bad - if ( - self.config.collector_mode != CollectorMode.Normal - or not self.collector_number_raw - ): + # Skip if collector number is bad + if not self.collector_number_raw: return if self.set_data: diff --git a/src/templates/_core.py b/src/templates/_core.py index 40ce41a0..96c79bcb 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -4,9 +4,11 @@ from collections.abc import Callable, Sequence from contextlib import suppress +from datetime import UTC, datetime from functools import cached_property from logging import getLogger from pathlib import Path +from re import Match, sub from threading import Event from traceback import format_stack from typing import TYPE_CHECKING, Any, Unpack @@ -25,7 +27,7 @@ from src._state import PATH, WatermarkFormat from src.cards import sanitize_card_filename, strip_reminder_text from src.enums.layers import LAYERS -from src.enums.mtg import CardTextPatterns, MagicIcons +from src.enums.mtg import CardFonts, CardTextPatterns, MagicIcons from src.enums.settings import ( BorderColor, CollectorMode, @@ -38,6 +40,7 @@ from src.helpers.adjustments import CreateColorLayerKwargs from src.helpers.effects import LayerEffects from src.helpers.position import DimensionNames +from src.helpers.text import get_font_size, override_text_style_ranges from src.layouts import NormalLayout, SplitLayout from src.schema.adobe import EffectBevel, EffectColorOverlay, EffectGradientOverlay from src.schema.colors import ( @@ -63,6 +66,7 @@ try_photoshop, ) from src.utils.scryfall import get_card_scan +from src.utils.text import format_string_with_indices from src.utils.windows import WindowState if TYPE_CHECKING: @@ -802,7 +806,12 @@ def load_artwork( # Frame the artwork if self.layout.is_panorama: - psd.frame_panorama(art_layer, self.docref, self.layout.panorama_element, self.layout.panorama_size) + psd.frame_panorama( + art_layer, + self.docref, + self.layout.panorama_element, + self.layout.panorama_size, + ) elif art_reference: psd.frame_layer(layer=art_layer, ref=art_reference) @@ -887,22 +896,20 @@ def paste_scryfall_scan( def collector_info(self) -> None: """Format and add the collector info at the bottom.""" - # Ignore this step if legal layer not present - if not self.legal_group: - return - # If creator is specified add the text if self.layout.creator and self.text_layer_creator: self.text_layer_creator.textItem.contents = self.layout.creator # Which collector info mode? if ( - self.config.collector_mode in [CollectorMode.Default, CollectorMode.Modern] + self.config.collector_mode in [CollectorMode.Normal, CollectorMode.Modern] and self.layout.collector_data ): return self.collector_info_authentic() elif self.config.collector_mode == CollectorMode.ArtistOnly: return self.collector_info_artist_only() + elif self.config.collector_mode == CollectorMode.Custom: + return self.collector_info_custom() return self.collector_info_basic() def collector_info_basic(self) -> None: @@ -986,6 +993,73 @@ def collector_info_artist_only(self) -> None: if self.text_layer_artist: psd.replace_text(self.text_layer_artist, "Artist", self.layout.artist) + def collector_info_custom(self) -> None: + """Formats collector layers according to configured custom format.""" + if layer_a := psd.getLayer(LAYERS.SET, self.legal_group): + layer_a_ti = layer_a.textItem + font = layer_a_ti.font + font_size = get_font_size(layer_a) + + if self.config.collector_line_a_format: + self.format_collector_info_line_custom( + layer_a, self.config.collector_line_a_format + ) + else: + layer_a.visible = False + + if layer_b := self.text_layer_artist: + layer_b_ti = layer_b.textItem + layer_b_ti.font = font + layer_b_ti.size = font_size + + if self.config.collector_line_b_format: + self.format_collector_info_line_custom( + layer_b, self.config.collector_line_b_format + ) + else: + layer_b.visible = False + + def format_collector_info_line_custom(self, layer: ArtLayer, format: str) -> None: + """Format collector info line according to given format, e.g. + `{artist} {date:%Y-%m-%d %H:%M}Z`""" + + # Build values dictionary with all available placeholders + values = { + "pen": MagicIcons.PAINTBRUSH_MODERN, + "artist": self.layout.artist, + "number": self.layout.collector_number, + "set": self.layout.set, + "rarity": self.layout.rarity_letter, + "lang": self.layout.lang, + } + + now = datetime.now(tz=UTC) + id_counter: list[int] = [0] + prefix = "_TkJqxrzSveisbe_" + + def replace_date(match: Match[str]) -> str: + new_id = f"{prefix}{id_counter[0]}" + id_counter[0] += 1 + values[new_id] = now.strftime(match.group(1)) + return f"{{{new_id}}}" + + # Handle {date:} pattern + format_str = sub(r"\{date:([^}]+)\}", replace_date, format) + + # Format string with values + formatted_text, indices = format_string_with_indices(format_str, **values) + layer.textItem.contents = formatted_text + + # Apply specific text styles to substitutions that need them + if ranges := indices.get("pen"): + override_text_style_ranges( + layer, ranges, font=CardFonts.MANA, size=5, tracking=200 + ) + if ranges := indices.get("artist"): + override_text_style_ranges( + layer, ranges, font=CardFonts.ARTIST, size=4.6, tracking=0 + ) + """ * Expansion Symbol """ diff --git a/src/utils/text.py b/src/utils/text.py new file mode 100644 index 00000000..706f6451 --- /dev/null +++ b/src/utils/text.py @@ -0,0 +1,75 @@ +from re import finditer + + +def format_string_with_indices( + format_string: str, **variables: object +) -> tuple[str, dict[str, list[tuple[int, int]]]]: + """ + Format a string with named variables and track the replaced indices. + + Args: + format_string: String with {variable_name} placeholders + **variables: Named variables to substitute + + Returns: + A tuple containing: + - The formatted string + - A dict mapping variable names to lists of (start, end) index pairs + representing where each variable appears in the formatted string. + The start is inclusive and the end exclusive. + + Example: + ``` + result, indices = format_string_with_indices( + "The {adjective} brown {animal} jumps over the lazy dog", + adjective="quick", + animal="fox", + ) + + # result + "The quick brown fox jumps over the lazy dog" + # indices + {"adjective": [(4, 9)], "animal": [(16, 19)]} + ``` + """ + indices_map: dict[str, list[tuple[int, int]]] = {} + result = "" + current_pos = 0 + last_end = 0 + + # Find all named placeholders {variable_name} + pattern = r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}" + + for match in finditer(pattern, format_string): + var_name = match.group(1) + placeholder_start = match.start() + placeholder_end = match.end() + + # Add the part of the string before this placeholder + before_text = format_string[last_end:placeholder_start] + result += before_text + current_pos += len(before_text) + + # Get the variable value and add it to result + if var_name in variables: + value_str = str(variables[var_name]) + start_idx = current_pos + result += value_str + current_pos += len(value_str) + end_idx = current_pos + + # Track the indices for this variable + if var_name not in indices_map: + indices_map[var_name] = [] + indices_map[var_name].append((start_idx, end_idx)) + else: + full_match = match.group(0) + result += full_match + current_pos += len(full_match) + + last_end = placeholder_end + + # Add any remaining part of the format string + result += format_string[last_end:] + + return result, indices_map From 45133cab1fd76dcc495c1304e45456f32eac8e24 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 06:29:47 +0300 Subject: [PATCH 158/190] test: Add tests for format_string_with_indices --- tests/cards/test_layouts.py | 2 +- tests/cards/test_text.py | 2 +- tests/{utils.py => testing_utils.py} | 0 tests/utils/__init__.py | 0 .../utils/test_format_string_with_indices.py | 69 +++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) rename tests/{utils.py => testing_utils.py} (100%) create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/test_format_string_with_indices.py diff --git a/tests/cards/test_layouts.py b/tests/cards/test_layouts.py index f9f6ed7d..3f9080a8 100644 --- a/tests/cards/test_layouts.py +++ b/tests/cards/test_layouts.py @@ -17,7 +17,7 @@ from src.layouts import NormalLayout, layout_map from src.utils.data_structures import parse_model from src.utils.scryfall import CardIdentifier, ScryfallCard, get_cards_collection -from tests.utils import TestDataPaths +from tests.testing_utils import TestDataPaths LayoutTestCases = RootModel[dict[str, dict[str, tuple[str, str, str, str, bool, bool]]]] diff --git a/tests/cards/test_text.py b/tests/cards/test_text.py index 81950da6..da43d7b7 100644 --- a/tests/cards/test_text.py +++ b/tests/cards/test_text.py @@ -4,7 +4,7 @@ from src.cards import generate_italics from src.utils.data_structures import parse_model -from tests.utils import TestDataPaths +from tests.testing_utils import TestDataPaths class TestCaseTextItalic(TypedDict): diff --git a/tests/utils.py b/tests/testing_utils.py similarity index 100% rename from tests/utils.py rename to tests/testing_utils.py diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/test_format_string_with_indices.py b/tests/utils/test_format_string_with_indices.py new file mode 100644 index 00000000..13af7f3e --- /dev/null +++ b/tests/utils/test_format_string_with_indices.py @@ -0,0 +1,69 @@ +from src.utils.text import format_string_with_indices + + +class TestFormatStringWithIndices: + def test_format_string_with_indices(self) -> None: + """Test basic string formatting with single variables.""" + result, indices = format_string_with_indices( + "The {adjective} brown {animal}", adjective="quick", animal="fox" + ) + assert result == "The quick brown fox" + assert indices == {"adjective": [(4, 9)], "animal": [(16, 19)]} + + def test_format_string_with_indices_multiple_occurrences(self) -> None: + """Test that repeated variables are tracked correctly.""" + result, indices = format_string_with_indices( + "{name} likes {name}", name="Alice" + ) + assert result == "Alice likes Alice" + assert indices == {"name": [(0, 5), (12, 17)]} + + def test_format_string_with_indices_no_placeholders(self) -> None: + """Test string with no placeholders.""" + result, indices = format_string_with_indices("Hello world") + assert result == "Hello world" + assert indices == {} + + def test_format_string_with_indices_missing_variable(self) -> None: + """Test that missing variables are not replaced.""" + result, indices = format_string_with_indices( + "Hello {name}, you are {age}. {name}? {age}", name="Bob" + ) + assert result == "Hello Bob, you are {age}. Bob? {age}" + assert indices == {"name": [(6, 9), (26, 29)]} + + def test_format_string_with_indices_empty_string(self) -> None: + """Test empty format string.""" + result, indices = format_string_with_indices("") + assert result == "" + assert indices == {} + + def test_format_string_with_indices_only_placeholder(self) -> None: + """Test string that is only a placeholder.""" + result, indices = format_string_with_indices("{value}", value="test") + assert result == "test" + assert indices == {"value": [(0, 4)]} + + def test_format_string_with_indices_adjacent_placeholders(self) -> None: + """Test adjacent placeholders with no space between them.""" + result, indices = format_string_with_indices( + "{first}{second}", first="Hello", second="World" + ) + assert result == "HelloWorld" + assert indices == {"first": [(0, 5)], "second": [(5, 10)]} + + def test_format_string_with_indices_numeric_values(self) -> None: + """Test that numeric values are converted to strings.""" + result, indices = format_string_with_indices("I have {count} apples", count=42) + assert result == "I have 42 apples" + assert indices == {"count": [(7, 9)]} + + def test_format_string_with_indices_variable_names(self) -> None: + """Test that variable names can contain underscores and numbers.""" + result, indices = format_string_with_indices( + "Value: {var_1}, another: {value_2_name}", + var_1="first", + value_2_name="second", + ) + assert result == "Value: first, another: second" + assert indices == {"var_1": [(7, 12)], "value_2_name": [(23, 29)]} From 320f2a5215cb367a372aeb67126653d41e4c87ef Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 06:45:36 +0300 Subject: [PATCH 159/190] refactor: Fix type checking and linting errors --- src/helpers/effects.py | 3 ++- src/helpers/position.py | 2 +- src/templates/planeswalker.py | 2 +- src/templates/split.py | 7 +++++-- src/text_layers.py | 15 ++++++--------- src/utils/adobe.py | 19 ++++++++----------- src/utils/fonts.py | 5 ++++- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/helpers/effects.py b/src/helpers/effects.py index 265fa706..a314e3ee 100644 --- a/src/helpers/effects.py +++ b/src/helpers/effects.py @@ -5,9 +5,10 @@ from _ctypes import COMError from logging import getLogger -from photoshop.api import ActionDescriptor, ActionList, ActionReference, DialogModes +from photoshop.api import ActionDescriptor, ActionList, ActionReference from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DialogModes from src import APP from src.enums.adobe import Stroke diff --git a/src/helpers/position.py b/src/helpers/position.py index acc59de9..45ba4ba5 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -16,11 +16,11 @@ from src.enums.adobe import Dimensions from src.helpers.bounds import ( LayerDimensions, + get_card_dimensions, get_dimensions_from_bounds, get_layer_dimensions, get_layer_height, get_layer_width, - get_card_dimensions, ) from src.helpers.selection import ( check_selection_bounds, diff --git a/src/templates/planeswalker.py b/src/templates/planeswalker.py index b205294b..d2b53a17 100644 --- a/src/templates/planeswalker.py +++ b/src/templates/planeswalker.py @@ -6,9 +6,9 @@ from functools import cached_property from logging import getLogger -from photoshop.api import ColorBlendMode, ElementPlacement from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import ColorBlendMode, ElementPlacement import src.helpers as psd import src.text_layers as text_classes diff --git a/src/templates/split.py b/src/templates/split.py index fd5cbb89..1706aa66 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -9,12 +9,13 @@ from pathlib import Path from omnitils.strings import normalize_str -from photoshop.api import BlendMode, ElementPlacement from photoshop.api._artlayer import ArtLayer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import BlendMode, ElementPlacement import src.helpers as psd from src import CON +from src._state import WatermarkFormat from src.enums.layers import LAYERS from src.helpers import LayerEffects from src.helpers.effects import copy_layer_fx @@ -467,7 +468,9 @@ def create_watermark(self) -> None: and (textbox_ref := self.textbox_references[i]) ): # Get watermark custom settings if available - wm_details = CON.watermarks.get(watermark, {}) + wm_details: WatermarkFormat | dict[str, int] = CON.watermarks.get( + watermark, {} + ) # Import and frame the watermark wm = psd.import_svg( diff --git a/src/text_layers.py b/src/text_layers.py index 910e07fc..aa0a28f8 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -8,21 +8,18 @@ from logging import getLogger from typing import NotRequired, TypedDict, Unpack -from photoshop.api import ( - ActionDescriptor, - ActionList, - ActionReference, +from photoshop.api import ActionDescriptor, ActionList, ActionReference, SolidColor +from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document +from photoshop.api._layerSet import LayerSet +from photoshop.api._selection import Selection +from photoshop.api.enumerations import ( DialogModes, Justification, Language, LayerKind, RasterizeType, - SolidColor, ) -from photoshop.api._artlayer import ArtLayer -from photoshop.api._document import Document -from photoshop.api._layerSet import LayerSet -from photoshop.api._selection import Selection from photoshop.api.text_item import TextItem from src import APP, CFG, CON diff --git a/src/utils/adobe.py b/src/utils/adobe.py index c37036e4..5adf94d5 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -14,17 +14,14 @@ ActionDescriptor, ActionReference, Application, - DialogModes, - ElementPlacement, PhotoshopPythonAPIError, - TypeUnits, - Units, ) from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop from photoshop.api._document import Document from photoshop.api._layer import Layer from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DialogModes, ElementPlacement, TypeUnits, Units from win32api import FormatMessage from src._state import AppEnvironment @@ -219,7 +216,7 @@ def is_running(cls) -> bool: """ @cache - def charIDToTypeID(self, index: str) -> int: + def charIDToTypeID(self, index: str) -> int: # pyright: ignore[reportIncompatibleMethodOverride] """Caching handler for charIDToTypeID. Args: @@ -235,7 +232,7 @@ def cID(self, index: str) -> int: return self.charIDToTypeID(index) @cache - def typeIDToCharID(self, index: int) -> str: + def typeIDToCharID(self, index: int) -> str: # pyright: ignore[reportIncompatibleMethodOverride] """Caching handler for typeIDToCharID. Args: @@ -255,7 +252,7 @@ def t2c(self, index: int) -> str: """ @cache - def stringIDToTypeID(self, index: str) -> int: + def stringIDToTypeID(self, index: str) -> int: # pyright: ignore[reportIncompatibleMethodOverride] """Caching handler for stringIDToTypeID. Args: @@ -271,7 +268,7 @@ def sID(self, index: str) -> int: return self.stringIDToTypeID(index) @cache - def typeIDToStringID(self, index: int) -> str: + def typeIDToStringID(self, index: int) -> str: # pyright: ignore[reportIncompatibleMethodOverride] """Caching handler for typeIDToStringID. Args: @@ -430,7 +427,7 @@ def sID(self, index: str) -> int: """ @cached_property - def id(self) -> int: + def id(self) -> int: # pyright: ignore[reportIncompatibleMethodOverride] """int: This layer's ID (cached).""" return super().id @@ -450,8 +447,8 @@ def action_getter(self) -> ActionDescriptor: """ @cached_property - def bounds(self) -> LayerBounds: - """LayerBounds: Bounds of the layer (left, top, right, bottom).""" + def bounds(self) -> LayerBounds: # pyright: ignore[reportIncompatibleMethodOverride] + """LayerBounds: Bounds of the layer (left, top, right, bottom) (cached).""" return super().bounds @cached_property diff --git a/src/utils/fonts.py b/src/utils/fonts.py index 3737422f..ed957671 100644 --- a/src/utils/fonts.py +++ b/src/utils/fonts.py @@ -12,7 +12,10 @@ from pathlib import Path from typing import TypedDict -from fontTools.ttLib import TTFont, TTLibError +from fontTools.ttLib import ( # pyright: ignore[reportMissingTypeStubs] + TTFont, + TTLibError, +) from packaging.version import parse from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet From 5956970e6a21c8b85610e5d1c1f59d4367c73335 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 21 May 2026 07:40:21 +0300 Subject: [PATCH 160/190] fix(SettingsWindow.qml): Fix changing setting values when filtering is active --- src/gui/qml/views/SettingsWindow.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gui/qml/views/SettingsWindow.qml b/src/gui/qml/views/SettingsWindow.qml index a7d5d422..301a0994 100644 --- a/src/gui/qml/views/SettingsWindow.qml +++ b/src/gui/qml/views/SettingsWindow.qml @@ -281,6 +281,7 @@ ApplicationWindow { required property var default_value required property var options property bool isTitle: settingsListDelegate.type === "title" + readonly property int sourceIndex: sfSettingsModel.mapToSource(sfSettingsModel.index(index, 0)).row width: settingsList.width highlighted: false @@ -369,7 +370,7 @@ ApplicationWindow { color: settingsWindow.systemPalette.text function onSetValue() { - settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingTextInput.text); + settingsWindow.settingsModel.str_value_changed(settingsListDelegate.sourceIndex, settingTextInput.text); } onEditingFinished: onSetValue() @@ -393,7 +394,7 @@ ApplicationWindow { systemPalette: settingsWindow.systemPalette checked: settingsListDelegate.value - onClicked: settingsWindow.settingsModel.bool_value_changed(settingsListDelegate.index, settingCheckbox.checked) + onClicked: settingsWindow.settingsModel.bool_value_changed(settingsListDelegate.sourceIndex, settingCheckbox.checked) } } Component { @@ -408,7 +409,7 @@ ApplicationWindow { value: settingsListDelegate.value function onSetValue(): void { - settingsWindow.settingsModel.int_value_changed(settingsListDelegate.index, settingSpinBoxInt.value); + settingsWindow.settingsModel.int_value_changed(settingsListDelegate.sourceIndex, settingSpinBoxInt.value); } onValueModified: onSetValue() @@ -462,7 +463,7 @@ ApplicationWindow { } function onSetValue(): void { - settingsWindow.settingsModel.float_value_changed(settingsListDelegate.index, settingSpinBoxDouble.realValue); + settingsWindow.settingsModel.float_value_changed(settingsListDelegate.sourceIndex, settingSpinBoxDouble.realValue); } onValueModified: onSetValue() @@ -491,7 +492,7 @@ ApplicationWindow { currentValue: settingsListDelegate.value onActivated: idx => { - settingsWindow.settingsModel.str_value_changed(settingsListDelegate.index, settingComboBox.model[idx]); + settingsWindow.settingsModel.str_value_changed(settingsListDelegate.sourceIndex, settingComboBox.model[idx]); } } } From b73dd4ca1f0612b0057493c397104e528d60d7df Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 22 May 2026 12:44:30 +0300 Subject: [PATCH 161/190] feat: Apply custom formatting to specific word end characters in Beleren font in order to more closely match real cards --- src/enums/mtg.py | 11 +++++++++++ src/text_layers.py | 27 +++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/enums/mtg.py b/src/enums/mtg.py index 606d85e5..cb2764f2 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -409,6 +409,10 @@ class MagicIcons(StrEnum): """ +def _create_word_end_regex(end: str) -> re.Pattern[str]: + return re.compile(end + r"(?:\s|$)") + + @dataclass class CardTextPatterns: """Defined card data regex patterns.""" @@ -452,3 +456,10 @@ class CardTextPatterns: TEXT_ABILITY: re.Pattern[str] = re.compile( r"(?:^|\r)+(?:• )*([^\r]+) — ", re.MULTILINE ) + + # Text - Specific letters at word end + TEXT_WORD_END_F: re.Pattern[str] = _create_word_end_regex("f") + TEXT_WORD_END_H: re.Pattern[str] = _create_word_end_regex("h") + TEXT_WORD_END_M: re.Pattern[str] = _create_word_end_regex("m") + TEXT_WORD_END_N: re.Pattern[str] = _create_word_end_regex("n") + TEXT_WORD_END_K: re.Pattern[str] = _create_word_end_regex("k") diff --git a/src/text_layers.py b/src/text_layers.py index aa0a28f8..1f0a9a52 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -30,7 +30,7 @@ locate_italics, locate_symbols, ) -from src.enums.mtg import CardFonts +from src.enums.mtg import CardFonts, CardTextPatterns from src.helpers import select_layer from src.helpers.bounds import LayerDimensions, get_layer_dimensions, get_layer_width from src.helpers.colors import apply_color, rgb_black @@ -79,13 +79,10 @@ class TextFieldKwargs(TypedDict): scale_width: NotRequired[bool] fix_overflow_height: NotRequired[bool] fix_overflow_width: NotRequired[bool] + replace_characters_in_title_font: NotRequired[bool] class TextField: - FONT = CardFonts.TITLES - FONT_ITALIC = CardFonts.RULES_ITALIC - FONT_BOLD = CardFonts.RULES_BOLD - def __init__( self, layer: ArtLayer, contents: str = "", **kwargs: Unpack[TextFieldKwargs] ): @@ -230,6 +227,12 @@ def font(self) -> str: """Font provided, or fallback on global constant.""" return self.kw_font or CON.font_title + @cached_property + def replace_characters_in_title_font(self) -> bool: + """Whether to replace specific characters when using title font + in order to more closely match real cards.""" + return self.kwargs.get("replace_characters_in_title_font", True) + """ * Methods """ @@ -253,6 +256,18 @@ def validate(self): def execute(self): """Executes all text actions.""" + # Apply custom word end characters if using Beleren font, + # which is usually used in card name and type. + if self.replace_characters_in_title_font and self.font == CardFonts.TITLES: + for pattern, replacement in ( + (CardTextPatterns.TEXT_WORD_END_F, "\ue006"), + (CardTextPatterns.TEXT_WORD_END_H, "\ue007"), + (CardTextPatterns.TEXT_WORD_END_M, "\ue008"), + (CardTextPatterns.TEXT_WORD_END_N, "\ue009"), + (CardTextPatterns.TEXT_WORD_END_K, "\ue00a"), + ): + self.input = pattern.sub(replacement, self.input) + # Update TextItem contents self.TI.contents = self.input @@ -261,7 +276,7 @@ def execute(self): self.TI.color = self.color # Update font manually if mismatch detected - if self.font != self.FONT: + if self.font != CardFonts.TITLES: self.TI.font = self.font # Change to English formatting if needed From 2d4469df5ccdf0960639c62db42c7a920fb12f5f Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 22 May 2026 12:46:14 +0300 Subject: [PATCH 162/190] fix(selection.py): Fix extending selection to the pixels covered by a shape layer --- src/helpers/selection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/helpers/selection.py b/src/helpers/selection.py index 86127591..e99ec407 100644 --- a/src/helpers/selection.py +++ b/src/helpers/selection.py @@ -100,7 +100,7 @@ def select_layer_pixels( layer: Layer to select. Uses active layer if not provided. """ if layer and layer.kind == LayerKind.SolidFillLayer: - return select_vector_layer_pixels(layer) + return select_vector_layer_pixels(layer, add_to_selection=add_to_selection) des1 = ActionDescriptor() ref1 = ActionReference() ref2 = ActionReference() @@ -143,10 +143,9 @@ def select_vector_layer_pixels( if layer: ref2.putIdentifier(APP.instance.sID("layer"), layer.id) desc1.putReference(APP.instance.sID("to"), ref2) - desc1.putInteger(APP.instance.sID("version"), 1) desc1.putBoolean(APP.instance.sID("vectorMaskParams"), True) APP.instance.executeAction( - APP.instance.sID("add" if add_to_selection else "set"), + APP.instance.sID("addTo" if add_to_selection else "set"), desc1, DialogModes.DisplayNoDialogs, ) From 4456bd9a0499808e0697cc6cb643abd51d85ec88 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 22 May 2026 12:47:45 +0300 Subject: [PATCH 163/190] feat(SettingsModel): Add stricter typing to `setData` --- src/gui/qml/models/settings_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/qml/models/settings_model.py b/src/gui/qml/models/settings_model.py index c925a79b..dae648c0 100644 --- a/src/gui/qml/models/settings_model.py +++ b/src/gui/qml/models/settings_model.py @@ -1,5 +1,5 @@ from functools import cached_property -from typing import Any, Literal, override +from typing import Literal, override from pydantic import BaseModel from PySide6.QtCore import ( @@ -88,7 +88,7 @@ def _value_role(self) -> int: def setData( self, index: QModelIndex | QPersistentModelIndex, - value: Any, + value: int | float | str | bool, /, role: int = Qt.ItemDataRole.EditRole, ) -> bool: From efe8eb8c38af37eaf57b0df40459ae5423395b78 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 22 May 2026 16:27:34 +0300 Subject: [PATCH 164/190] feat(FormattedTextField): Revert paragraph justification change if it causes overflow --- src/text_layers.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/text_layers.py b/src/text_layers.py index 1f0a9a52..ced5548f 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -35,7 +35,7 @@ from src.helpers.bounds import LayerDimensions, get_layer_dimensions, get_layer_width from src.helpers.colors import apply_color, rgb_black from src.helpers.position import clear_reference_vertical, position_between_layers -from src.helpers.selection import select_layer_bounds +from src.helpers.selection import select_layer_bounds, select_layer_pixels from src.helpers.text import ( get_text_scale_factor, remove_trailing_text, @@ -768,7 +768,21 @@ def execute(self): # Justify center if required if self.contents_centered: + initial_justification = self.TI.justification self.TI.justification = Justification.Center + # Revert justification change if it caused overflow + if initial_justification != Justification.Center and self.reference_dims: + selection = select_layer_pixels(self.layer) + selection_bounds = selection.bounds + selection.deselect() + if ( + initial_justification == Justification.Left + and self.reference_dims["right"] < selection_bounds[2] + ) or ( + initial_justification == Justification.Right + and self.reference_dims["left"] > selection_bounds[0] + ): + self.TI.justification = initial_justification # Ensure hyphenation disabled self.TI.hyphenation = False From 86b9339347f8fc93c0da5ef645ed1e105c5db835 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 22 May 2026 16:29:21 +0300 Subject: [PATCH 165/190] feat(align_text): Use stricter typing --- src/helpers/text.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/helpers/text.py b/src/helpers/text.py index de2e28e0..5cba2cfd 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -525,7 +525,10 @@ def get_text_scale_factor( def align_text( - action_list: ActionList, start: int, end: int, alignment: str = "right" + action_list: ActionList, + start: int, + end: int, + alignment: Literal["left", "center", "right"] = "right", ) -> ActionList: """Align a slice of text in an action using given alignment. From 89611e36bcf1c81d2e98e0e4954a2c7bc3371ba7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 24 May 2026 06:42:19 +0300 Subject: [PATCH 166/190] fix(get_font_size): Fix scaling being skipped when using the layer's font size --- src/helpers/text.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/helpers/text.py b/src/helpers/text.py index 5cba2cfd..355cb22d 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -44,9 +44,8 @@ def get_font_size(layer: ArtLayer, raw_size: float | None = None) -> float: layer: Text layer to get size of. """ return round( - layer.textItem.size - if raw_size is None - else raw_size * get_text_scale_factor(layer), + (layer.textItem.size if raw_size is None else raw_size) + * get_text_scale_factor(layer), 2, ) From e02b4cbc56e1d10ae6506fe7bb051ef8634876cc Mon Sep 17 00:00:00 2001 From: pappnu Date: Sun, 24 May 2026 07:35:26 +0300 Subject: [PATCH 167/190] fix: Preserve whitespace when replacing trailing characters in Beleren font --- src/enums/mtg.py | 2 +- src/text_layers.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/enums/mtg.py b/src/enums/mtg.py index cb2764f2..fe471a5a 100644 --- a/src/enums/mtg.py +++ b/src/enums/mtg.py @@ -410,7 +410,7 @@ class MagicIcons(StrEnum): def _create_word_end_regex(end: str) -> re.Pattern[str]: - return re.compile(end + r"(?:\s|$)") + return re.compile(end + r"(\s|$)") @dataclass diff --git a/src/text_layers.py b/src/text_layers.py index ced5548f..8bc588e5 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -6,6 +6,7 @@ from contextlib import suppress from functools import cached_property from logging import getLogger +from re import Match from typing import NotRequired, TypedDict, Unpack from photoshop.api import ActionDescriptor, ActionList, ActionReference, SolidColor @@ -266,7 +267,14 @@ def execute(self): (CardTextPatterns.TEXT_WORD_END_N, "\ue009"), (CardTextPatterns.TEXT_WORD_END_K, "\ue00a"), ): - self.input = pattern.sub(replacement, self.input) + + def replacer(match: Match[str]) -> str: + after_char = match.group(1) + return ( + replacement + after_char if isinstance(after_char, str) else "" + ) + + self.input = pattern.sub(replacer, self.input) # Update TextItem contents self.TI.contents = self.input From c654656a197c9d467654ef192b5f95c42f5cbbf2 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 25 May 2026 08:55:12 +0300 Subject: [PATCH 168/190] fix(process_card_data): Ensure idempotency for Planeswalker Transform cards --- src/cards.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cards.py b/src/cards.py index 2437e0f3..20603896 100644 --- a/src/cards.py +++ b/src/cards.py @@ -323,7 +323,8 @@ def process_card_data(data: ScryfallCard, card: CardDetails) -> ScryfallCard: if "Planeswalker" in card_face.type_line: data.layout = ( LayoutScryfall.PlaneswalkerTransform - if data.layout == LayoutScryfall.Transform + if data.layout + in (LayoutScryfall.Transform, LayoutScryfall.PlaneswalkerTransform) else LayoutScryfall.PlaneswalkerMDFC ) # Transform Saga layout From 8c394acae5dac4da107cf5f93c35d122d6fe6f9e Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 25 May 2026 11:01:49 +0300 Subject: [PATCH 169/190] feat(SplitMod): Rotate the render in the hooks step --- src/templates/split.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/split.py b/src/templates/split.py index 1706aa66..3d302d3e 100644 --- a/src/templates/split.py +++ b/src/templates/split.py @@ -118,9 +118,9 @@ def frame_layer_methods(self) -> list[Callable[[], None]]: return methods @cached_property - def post_text_methods(self) -> list[Callable[[], None]]: + def hooks(self) -> list[Callable[[], None]]: """Rotate card sideways.""" - methods = super().post_text_methods + methods = super().hooks if self.is_split: methods.append(psd.rotate_counter_clockwise) return methods From 5f958bd46239a3547a57d1ca781bda766fed1229 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 25 May 2026 11:21:04 +0300 Subject: [PATCH 170/190] feat(BaseTemplate): Add a property for collector info set text layer --- src/templates/_core.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/templates/_core.py b/src/templates/_core.py index 96c79bcb..605cc49a 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -551,6 +551,11 @@ def text_layer_artist(self) -> ArtLayer | None: """Card artist text layer.""" return psd.getLayer(LAYERS.ARTIST, self.legal_group) + @cached_property + def text_layer_set(self) -> ArtLayer | None: + """Card set text layer.""" + return psd.getLayer(LAYERS.SET, self.legal_group) + @cached_property def text_layer_creator(self) -> ArtLayer | None: """Optional[ArtLayer]: Proxy creator name text layer.""" @@ -916,8 +921,7 @@ def collector_info_basic(self) -> None: """Called to generate basic collector info.""" # Collector layers - set_layer = psd.getLayer(LAYERS.SET, self.legal_group) - set_TI = set_layer.textItem if set_layer else None + set_TI = self.text_layer_set.textItem if self.text_layer_set else None # Correct color for non-black border if self.border_color != BorderColor.Black: @@ -927,12 +931,12 @@ def collector_info_basic(self) -> None: self.text_layer_artist.textItem.color = self.RGB_BLACK # Fill optional collector star - if set_layer and self.is_collector_promo: - psd.replace_text(set_layer, "•", MagicIcons.COLLECTOR_STAR) + if self.text_layer_set and self.is_collector_promo: + psd.replace_text(self.text_layer_set, "•", MagicIcons.COLLECTOR_STAR) # Fill language, artist, and set - if set_layer and self.layout.lang != "en": - psd.replace_text(set_layer, "EN", self.layout.lang.upper()) + if self.text_layer_set and self.layout.lang != "en": + psd.replace_text(self.text_layer_set, "EN", self.layout.lang.upper()) if self.text_layer_artist: psd.replace_text(self.text_layer_artist, "Artist", self.layout.artist) @@ -946,8 +950,8 @@ def collector_info_authentic(self) -> None: # Hide basic layers if self.text_layer_artist: self.text_layer_artist.visible = False - if layer := psd.getLayer(LAYERS.SET, self.legal_group): - layer.visible = False + if self.text_layer_set: + self.text_layer_set.visible = False # Get the collector layers group = psd.getLayerSet(LAYERS.COLLECTOR, self.legal_group) @@ -982,8 +986,8 @@ def collector_info_artist_only(self) -> None: """Called to generate 'Artist Only' collector info.""" # Collector layers - if layer := psd.getLayer(LAYERS.SET, self.legal_group): - layer.visible = False + if self.text_layer_set: + self.text_layer_set.visible = False # Correct color for non-black border if self.text_layer_artist and self.border_color != BorderColor.Black: @@ -995,7 +999,7 @@ def collector_info_artist_only(self) -> None: def collector_info_custom(self) -> None: """Formats collector layers according to configured custom format.""" - if layer_a := psd.getLayer(LAYERS.SET, self.legal_group): + if layer_a := self.text_layer_set: layer_a_ti = layer_a.textItem font = layer_a_ti.font font_size = get_font_size(layer_a) From 7f1a8835a590ef05a795508a4be8057ff26afa99 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 26 May 2026 06:12:46 +0300 Subject: [PATCH 171/190] feat(Proxyglyph.ttf): Update the numbers 0-9 to more closely match real cards Idea, asset sourcing from Wikimedia and alignment made by AmProsius from Discord --- README.md | 1 + fonts/Proxyglyph.ttf | Bin 46852 -> 46888 bytes 2 files changed, 1 insertion(+) diff --git a/README.md b/README.md index d38eb95b..827f14dd 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,7 @@ In your proxyshop directory, look for a folder named `logs`, inside that folder - iDerp - Tupinambá (Pedro Neves) - Andrew Gioia for his various font projects which have been of use for Proxyshop in the past. +- AmProsius for additional Proxyglyph adjustments. - John Prime, Haven King, and members of [CCGHQ](https://www.slightlymagic.net/forum/viewtopic.php?f=15&t=7010) for providing expansion symbol SVG's. - Hal and the other contributors over at [Photoshop Python API](https://github.com/loonghao/photoshop-python-api). - Wizards of the Coast and all the talented artists who make Magic the Gathering a reality. diff --git a/fonts/Proxyglyph.ttf b/fonts/Proxyglyph.ttf index b6688a94733df18b68c40b926d0d9828a1d6d4a6..31accec43de23f7c151df106185dcf14f131663d 100644 GIT binary patch delta 2028 zcmY*aYitzP6~6b*?2Nr$@B3xE@7dkim)Gl=+4sW>Uaxo8@e526g26Tc?1V>1;vX)- ztV>#qn)dJ z@407Y?m6E%-@WIpWps8Kxc~$J$nih|kj-|totZTb0B}))wT6~VbCwuIcL125J?ZKw zp2f0L>hF<@F)O zRonMizHsy8s==^y)9_WVEb%X3#xCRMZv&tHv^?L7{U$aSfHZV`O^ia8D1ryXC|JS$ zi}m^by6!HX?@acf-@~8J8Hm=3;u2vYb`d`z7D)*ykkjOKaz$hlg+v8WkLW%XqK2t+ z)MNSu`hEI#MX2Ib#k@Esp04_+>YC(I^^|l>dQNsl_E>&haY6Zs@|Mb{>Q)V@rc`HC zpQ)~?ZmU-`DXm}IquZ=U`a(@&L$+3FSTH;?YK^;$SB*=i#iHq3^Y1LrS`J&zTfVX0 zvvG`;d2ILDWA=mg3EbxGtBzfcpE*{YV&}`wapxi{W^F9TrrCCOKRd>L$bQD&W|v)* zE9dHSopU31+CApJ;Sqa6o-xlYugQDNJLSFL{nC5GyXrIfBEDyQXMIa`iMrkX*XoZ2 zio=1c4gchha36BFc#0438Gevo;I9jIp-)%{T7rYYOCfcrC$t!Lg~!7;BH73vqn7A+ z^l@w`wj7TodJ9vQJtJEk|1}x7^HG za$GKx+nGC*JC!@1Tg*MmS9Ioud?EjQes6v}e>Q)+U@G(!77B}nQfpu9q1MsXiPrhn z@1Bvj2ikM($J)=gf7?NISUMsdn>(gDuC2F~&UELl&gssZT^qWN&I#45sQ5Kdp(%6< zNB|}i6#S5}Y1Agfa}FT|c}}ZQO9{s2L@ozs9@N?$>V2hoZg*<$5xbDCSN0ZRVBe4V ze{>ygG(Bmb9-KM0`G?s$>95LEgOCL#)PX(%8vtyVg4f0$H1r>smX0RBF7a?4g z=w(uE>3VIoTqiNu^|du^`rt2g#wwJ_5MrYaHrMHK#~XqNc*9>}XBoU97ixsGl}!=h zkdv{|Y)YgvL0T%Jk%(ty;fB&-_|j-l3dyv6OEage_j4|LWo{>e)$(ZF>-BHwc{t-A zD2}&VHL2t~TE5L?Bq;*Dnl|{XG0&i{i~y_vHToEx03K}6=B$TdE%u)X3Qo3c5n`ci zI)p9j)HF7)mLhnvzD*`K^EoM7@2X9jO&Q(3MpsQ{n|EL&)D5@rZ7}-m^J=}M%54?b z6q$s*E`V4rowOJn{me;{h=-Gn)Rv8jUIE7qA87CeP@?zI1eR*BPXy0EUcrUJJk4m- z<(P(XAlaY@Ub#45f~xJx8X~Cd;0>i^WYrkqQ-EZpNATU!t8Vp3G7$=ytZ`x;9Epk( z=*|B}ht@z*G^^q{Y{N>Y5N{$i;WD(MIipgk1H3+xU|XB5Ng31fkRdpIX<0+c8}tpG z0ZTRHyS9@Oif&LS?ajVuw^2qzU)4)}h^mCvKtj-3qJFUKgzu4=-%5EMFR51qH48BQLAu2mpC~5!GF1AM#oX{Euh0IT1wK4 zGlejvunSC>*YfB%q_t6>1HMc+V+oDs4^BoBcH3nZ^hQ+TDUQtKgm>Q@Vi@Rl{j-Az z=?3@>XRK6I%POlWDJ3UpvEHtQY_hb+5M*j;N+NS|#z2;lJ3@7`qCcxZw8_!v*xcV+ z&dD91K@(^arv!5zuKA4gb-*i*j(}N2 zMw-G~olx)YPKKZ3HjoNqq%f34+j}BPSK3|JG4bz*&=tW6scY(!?{{yE2e(W)nEW9Aco&fZklbDQE(AQ9g>w;r-nD? yrf0u`Vh;ELu85pJFgf)pBVD5z9sqcH9PpD5Db3v9FX%}1-Rg^zW$pi##s2_+&Eb&% delta 1376 zcmYjRdrXs86hHU2v{rYpWx&S&b3^Ws-eclms}p&~}0=306cK4#)gSkBneK%2=##+E=~9du$pY z!%1S#8meQ>SckqMew5@(8mk*>m7ENMuaJCvySeGm;q0$vFNi;Kt?wB{Io|Q~?d3WD zfGMF`a#Mjel{yd4Tz1D3ydyQ!svoIqECt5Ci2fF~^wG%w!rrO_k&k=K4 zGCDHinP*lvb8EOgyg}Y<)>!r_eiwg2kS;I?!h&YOalr*aTreq|7UhXKVn>cXXFgZ8 zCO1!(w?qw4cc>@%<@rPTQw77F1=kDDN=%X}Nw4HH=?xiI_C!9bNLPpzenmZze#MNk zTzO16qg+(ERT0&&YEhL`bJdi(NbObEsAK92>Phu2^@2vBacX)r^IA$LH3b>q4j-Ey(8SX10xJXMlg;?f7#?lE+3GYlEOHSI9POcSOBbEa8h zt~3vr$1GrRS_UjD)=KLc>u)xPZP?DVN9^Mcnd6dUr8H7HTNW<6wNC7GI0v1Ha&CE~ zBI=54Xx%up@y}NuSN^o=kvr&abq~1X?(f~tJUcz-Jl8xcUanW-E%R>nHhO!#!`?gI zMA(<*Q~4~ujlPJl+c)W3_B;Fo{$c-Iz!}&a*d3@3^as8PJPu|C6~R5h-r)7%LU1J{ z4CzD7q4<9?9j3zN;g;~V@YAh3M=h(>Y1{=6;6B_BJfHvsH17!2xnNv>u+C{TmcO;% zY1z59(A*)EhT2d?TlLXsRZH96_GnfA`6}mMHNZl!fN&w+<+y#6BvW4rANNP2k ztl1VFBG!)XZ{a3?%?`LI&Q=%hO2RIMViyX(DKnCEm;*U!U=YY`DA`i0%}&c&3(3uU ztyP9@(C@ZNvQk-3fs4=M_rX8_eXrGk(GgnErbVT6s7;lH;WqGbKRyj2$R#}V#kO1< zHn;@I7a~`383{uyEnmuu|9I27TQ1*avm-hgsc6unK=Knp{v4RN8=nA4io7Hy!N^VT zjpbt;qff^~g#7_vlF&uCHK~-lF%3q3dR4mw9i*M@QVoIxIf0Y7^JR6p)nd0*Ai0dq zV$1Dju~{l&5toJT(O24Y)STB(PBn9cg0+`3i~{o;vCFA5WI$?;PY?zQwBSfpiSMIm zYX^l<1HG?9iz?}h9VQ+|7#R3N{5FU|1wb!%sHM2f?F;FP&5rF+r@c0E)|7irit^+x z$15UXeyInE>Dr@`(S&$S0poAHG-CZSlZ^D#hEE%LJhVh!KebrE!9uz>zKK_W?vQQ} za}|jRQF3%Xei!MhpcY!-5Y#~vG?H^N5HW)Or6r%Fs Date: Sun, 31 May 2026 17:23:45 +0300 Subject: [PATCH 172/190] fix(NormalLayout): Fix modern collector mode option not working --- src/layouts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/layouts.py b/src/layouts.py index 7ee044d9..cea9fd98 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -32,7 +32,7 @@ layout_map_types, planeswalkers_tall, ) -from src.enums.settings import NicknameShorten, WatermarkMode +from src.enums.settings import CollectorMode, NicknameShorten, WatermarkMode from src.frame_logic import ( check_hybrid_mana_cost, get_frame_details, @@ -634,8 +634,8 @@ def card_count(self) -> int | None: @cached_property def collector_data(self) -> str: - """str: Formatted collector info line, e.g. 050/230 M.""" - if self.card_count: + """str: Formatted collector info line, e.g. '050/230 M' or 'M 0050'.""" + if self.card_count and self.config.collector_mode == CollectorMode.Normal: return f"{str(self.collector_number).zfill(3)}/{str(self.card_count).zfill(3)} {self.rarity_letter}" if self.collector_number_raw: return f"{self.rarity_letter} {str(self.collector_number).zfill(4)}" From cc7cfe02b5decdc6f74fdcf4f887ce96a97abf83 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Jun 2026 11:41:28 +0300 Subject: [PATCH 173/190] feat(BaseTemplate): Allow templates to more easily access collector text layers --- src/templates/_core.py | 77 +++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/templates/_core.py b/src/templates/_core.py index 605cc49a..69f9caca 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -98,7 +98,6 @@ def __init__(self, layout: NormalLayout): # Setup manual properties self.layout = layout self.config = layout.config - self._text: list[FormattedTextLayer] = [] """ * Enabled Method Lists @@ -513,6 +512,10 @@ def legal_group(self) -> LayerSet: """LayerSet: Group containing artist credit, collector info, and other legal details.""" return self.docref.layerSets[LAYERS.LEGAL] + @cached_property + def collector_group(self) -> LayerSet | None: + return psd.getLayerSet(LAYERS.COLLECTOR, self.legal_group) + @cached_property def border_group(self) -> LayerSet | None: """Optional[Union[LayerSet, ArtLayer]]: Group, or sometimes a layer, containing the card border.""" @@ -556,6 +559,14 @@ def text_layer_set(self) -> ArtLayer | None: """Card set text layer.""" return psd.getLayer(LAYERS.SET, self.legal_group) + @cached_property + def text_layer_collector_first(self) -> ArtLayer | None: + return psd.getLayer(LAYERS.TOP, self.collector_group) + + @cached_property + def text_layer_collector_second(self) -> ArtLayer | None: + return psd.getLayer(LAYERS.BOTTOM, self.collector_group) + @cached_property def text_layer_creator(self) -> ArtLayer | None: """Optional[ArtLayer]: Proxy creator name text layer.""" @@ -565,10 +576,12 @@ def text_layer_creator(self) -> ArtLayer | None: def text_layer_name(self) -> ArtLayer | None: """Optional[ArtLayer]: Card name text layer.""" layer = psd.getLayer(LAYERS.NAME, self.text_group) - if layer and self.is_name_shifted: - layer.visible = False - if shift_layer := psd.getLayer(LAYERS.NAME_SHIFT, self.text_group): - shift_layer.visible = True + if self.is_name_shifted and ( + shift_layer := psd.getLayer(LAYERS.NAME_SHIFT, self.text_group) + ): + if layer: + layer.visible = False + shift_layer.visible = True return shift_layer return layer @@ -953,34 +966,41 @@ def collector_info_authentic(self) -> None: if self.text_layer_set: self.text_layer_set.visible = False + if self.collector_group: + self.collector_group.visible = True + # Get the collector layers - group = psd.getLayerSet(LAYERS.COLLECTOR, self.legal_group) - if group: - group.visible = True - top = layer.textItem if (layer := psd.getLayer(LAYERS.TOP, group)) else None - bottom = psd.getLayer(LAYERS.BOTTOM, group) + if self.text_layer_collector_first: + first = self.text_layer_collector_first.textItem # Correct color for non-black border if self.border_color != "black": - if top: - top.color = self.RGB_BLACK - if bottom: - bottom.textItem.color = self.RGB_BLACK + first.color = self.RGB_BLACK + # Apply the collector info + first.contents = self.layout.collector_data + + if self.text_layer_collector_second and self.text_layer_collector_second: # Fill in language if needed - if bottom and self.layout.lang != "en": - psd.replace_text(bottom, "EN", self.layout.lang.upper()) + if self.layout.lang != "en": + psd.replace_text( + self.text_layer_collector_second, "EN", self.layout.lang.upper() + ) # Fill optional collector star - if bottom and self.is_collector_promo: - psd.replace_text(bottom, "•", MagicIcons.COLLECTOR_STAR) + if self.is_collector_promo: + psd.replace_text( + self.text_layer_collector_second, "•", MagicIcons.COLLECTOR_STAR + ) - # Apply the collector info - if top: - top.contents = self.layout.collector_data - if bottom: - psd.replace_text(bottom, "SET", self.layout.set) - psd.replace_text(bottom, "Artist", self.layout.artist) + # Correct color for non-black border + if self.border_color != "black": + self.text_layer_collector_second.textItem.color = self.RGB_BLACK + + psd.replace_text(self.text_layer_collector_second, "SET", self.layout.set) + psd.replace_text( + self.text_layer_collector_second, "Artist", self.layout.artist + ) def collector_info_artist_only(self) -> None: """Called to generate 'Artist Only' collector info.""" @@ -1294,15 +1314,10 @@ def color_border(self) -> None: * Formatted Text Layers """ - @property + @cached_property def text(self) -> list[FormattedTextLayer]: """List of text layer objects to execute.""" - return self._text - - @text.setter - def text(self, value: list[FormattedTextLayer]): - """Add text layer to execute.""" - self._text = value + return [] def format_text_layers(self) -> None: """Validate and execute each formatted text layer.""" From 6b6b4fa675c472571018c31fc57dfaf33603e43b Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Jun 2026 12:08:54 +0300 Subject: [PATCH 174/190] feat: Add support for programmatically executing UXP scripts in Photoshop --- .gitignore | 3 + README.md | 34 ++-- package-lock.json | 102 +++++++++++ package.json | 14 ++ scripts_js/build.ts | 35 ++++ src/_state.py | 2 + src/helpers/shapes.py | 59 +++++++ src/utils/adobe.py | 10 +- src/utils/uxp/__init__.py | 0 src/utils/uxp/base.py | 61 +++++++ src/utils/uxp/batch_play.py | 62 +++++++ src/utils/uxp/path.py | 29 ++++ src/utils/uxp/shape.py | 52 ++++++ src/utils/uxp/text.py | 335 ++++++++++++++++++++++++++++++++++++ src_js/batchPlay.ts | 11 ++ src_js/createPath.ts | 43 +++++ src_js/types.d.ts | 11 ++ tsconfig.json | 11 ++ tsconfig.scripts.json | 11 ++ 19 files changed, 867 insertions(+), 18 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts_js/build.ts create mode 100644 src/helpers/shapes.py create mode 100644 src/utils/uxp/__init__.py create mode 100644 src/utils/uxp/base.py create mode 100644 src/utils/uxp/batch_play.py create mode 100644 src/utils/uxp/path.py create mode 100644 src/utils/uxp/shape.py create mode 100644 src/utils/uxp/text.py create mode 100644 src_js/batchPlay.ts create mode 100644 src_js/createPath.ts create mode 100644 src_js/types.d.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.scripts.json diff --git a/.gitignore b/.gitignore index e3d046f7..ab0fb757 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__/ build/ develop-eggs/ dist/ +dist_js/ downloads/ eggs/ .eggs/ @@ -160,6 +161,8 @@ node_modules/ /art # Render output folder /out +# Temporary files +/tmp # Temporary logging directory /logs # For personal testing diff --git a/README.md b/README.md index 827f14dd..004a81b3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ If you need help with this app or wish to troubleshoot an issue, [please join ou # 🛠️ Requirements -- Photoshop (2017-2026 Supported) +- Photoshop (23.5 or newer recommended but earlier versions down to 18.0 might work) - Windows (currently incompatible with Mac/Linux) - [The Photoshop templates](https://drive.google.com/drive/u/1/folders/1moEdGmpAJloW4htqhrdWZlleyIop_z1W) (Can be downloaded in the app, which is recommended over manual download) - Required fonts (included in `fonts/`): @@ -181,25 +181,28 @@ You may supply Proxyshop with image and JSON pairs to render cards with custom s - **Transform images**: Allows re-encoding and downscaling chosen images. Completed transformations are saved to `compressed/` next to the input image. -# 🐍 Setup Guide (Python Environment) -Setting up the Python environment for Proxyshop is intended for advanced users, contributors, and anyone who wants to -get their hands dirty making a plugin or custom template for the app! -1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/) -2. If you don't have Python installed yet, you may install it with uv. See `pyproject.toml` for supported Python versions. - ```bash - uv python install 3.14 - ``` -3. Clone Proxyshop somewhere on your system, we'll call this the ***root directory***. +# 🐍 Setup Guide +Setting up the Python & Node.js environments for Proxyshop is intended for advanced users, contributors, and anyone who wants to get their hands dirty making a plugin or custom template for the app! +1. Install requirements: + - [uv](https://docs.astral.sh/uv/getting-started/installation/) + - Python. See `pyproject.toml` for supported versions. E.g., you may install Python with uv: + ```bash + uv python install 3.14 + ``` + - Node.js + - Fonts included in the `fonts/` folder +2. Clone Proxyshop somewhere on your system, we'll call this the ***root directory***. ```bash git clone https://github.com/MrTeferi/Proxyshop.git ``` -4. Navigate to the **root directory** and install the project environment. +3. Navigate to the **root directory** and install the project environment. ```bash cd proxyshop uv sync + npm install + npm run build ``` -5. Install the fonts included in the `fonts/` folder. -6. Run the app. +4. Run the app. ```bash # OPTION 1) Activate the virtual environment and run the app's entrypoint with Python ./.venv/Scripts/Activate @@ -208,7 +211,7 @@ get their hands dirty making a plugin or custom template for the app! # OPTION 2) Execute via uv uv run main.py ``` -7. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. +5. Refer to the [usage guide](#-using-the-proxyshop-gui) for navigating the GUI. # 🖥 Development Environment @@ -233,7 +236,7 @@ Predefined template test render cases may be run via the _Tests_ menu in the GUI ## Automatic tests -Automatic tests are defined under the `/tests` directory. All of them can run with the command: +Automatic tests are defined under the `/tests` directory. All of them can be run with the command: ```bash pytest ./tests ``` @@ -251,6 +254,7 @@ pyinstaller -n Proxyshop --onefile --icon "./src/img/favicon.ico" --distpath "./ ``` Additionally the following directories and their contents should be distributed alongside the executable: +- /dist_js - /fonts - /plugins - /templates diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..dee678ff --- /dev/null +++ b/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "MTG-Proxyshop", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/node": "^25.9.1", + "@types/photoshop": "^25.0.4", + "prettier": "^3.8.3", + "pyright": "^1.1.409", + "typescript": "^6.0.3" + } + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/photoshop": { + "version": "25.0.4", + "resolved": "https://registry.npmjs.org/@types/photoshop/-/photoshop-25.0.4.tgz", + "integrity": "sha512-HfsVvgCrkOhGYlEmtsqT3pvLHFSoa1OXIHr37GsWy1y9FQJw/vb7xcDBGMoYuJemnmWlDMqdKgnnYUZL6kF5HA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pyright": { + "version": "1.1.409", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.409.tgz", + "integrity": "sha512-13VFQyw4mJzshZxcxiYbNjo1hG/WHSRDj70Y3lbJEHqCkI2dvBAUTti8VV6Ezsr5gT93pFvC0e/jAQS4JdHarA==", + "dev": true, + "license": "MIT", + "bin": { + "pyright": "index.js", + "pyright-langserver": "langserver.index.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..f9d389e6 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "type": "module", + "scripts": { + "build": "tsc && node --experimental-transform-types scripts_js/build.ts", + "type-check": "pyright ./plugins/** ./src/** ./tests/** ./scripts/** && tsc --noEmit && npx tsc --project tsconfig.tools.json --noEmit" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "@types/photoshop": "^25.0.4", + "prettier": "^3.8.3", + "pyright": "^1.1.409", + "typescript": "^6.0.3" + } +} diff --git a/scripts_js/build.ts b/scripts_js/build.ts new file mode 100644 index 00000000..3bfbec86 --- /dev/null +++ b/scripts_js/build.ts @@ -0,0 +1,35 @@ +import { readdir, readFile, rename, rm, writeFile } from "fs/promises"; +// import { format, parse } from "path"; + +const distPath = "dist_js/"; +const awaitInjectionLocator = /\sphotoshop_[0-9]+.core.executeAsModal\(/g; + +// TypeScript doesn't allow top level await when transpiling to CommonJS, +// but that is the combination we need with UXP scripts, so we have to +// add the awaits after tsc transpilation. +async function fixUXPScript(path: string) { + const content = await readFile(path, "utf-8"); + const matches = content.match(awaitInjectionLocator); + if (matches) { + const lastIndex = content.lastIndexOf(matches[matches.length - 1]); + await writeFile( + path, + content.slice(0, lastIndex) + " await" + content.slice(lastIndex), + ); + } + // await rename(path, format({ ...parse(path), base: "", ext: ".psjs" })); +} + +async function build() { + const operations: Promise[] = []; + + for (let pth of await readdir(distPath)) { + pth = distPath + pth; + if (pth.endsWith(".psjs")) operations.push(rm(pth)); + else if (pth.endsWith(".js")) operations.push(fixUXPScript(pth)); + } + + await Promise.all(operations); +} + +await build(); diff --git a/src/_state.py b/src/_state.py index 6c3f6bf4..eeac79fd 100644 --- a/src/_state.py +++ b/src/_state.py @@ -91,10 +91,12 @@ class PATH(DefinedPaths): # Root Level Directories SRC = CWD / "src" OUT = CWD / "out" + TMP = CWD / "tmp" LOGS = CWD / "logs" FONTS = CWD / "fonts" PLUGINS = CWD / "plugins" TEMPLATES = CWD / "templates" + JS_SCRIPTS = CWD / "dist_js" PROJECT_FILE = CWD / "pyproject.toml" # Source Level Directories diff --git a/src/helpers/shapes.py b/src/helpers/shapes.py new file mode 100644 index 00000000..63132d54 --- /dev/null +++ b/src/helpers/shapes.py @@ -0,0 +1,59 @@ +from collections.abc import Iterable + +from photoshop.api import ActionDescriptor, ActionReference +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import DialogModes, ElementPlacement + +from src import APP +from src.helpers.colors import get_color, rgb_black +from src.schema.colors import ColorObject +from src.utils.uxp.path import PathPointConf, create_path + + +def create_shape_layer( + points: Iterable[PathPointConf], + name: str = "", + relative_layer: ArtLayer | LayerSet | None = None, + placement: ElementPlacement = ElementPlacement.PlaceAfter, + hide: bool = False, + color: ColorObject | None = None, +) -> ArtLayer: + solid_color = get_color(color) if color else rgb_black() + docref = APP.instance.activeDocument + + create_path(points) + + # Convert path to a layer + ref1 = ActionReference() + desc1 = ActionDescriptor() + desc2 = ActionDescriptor() + desc3 = ActionDescriptor() + desc4 = ActionDescriptor() + ref1.putClass(APP.instance.sID("contentLayer")) + desc1.putReference(APP.instance.sID("target"), ref1) + desc4.putDouble(APP.instance.sID("red"), solid_color.rgb.red) + desc4.putDouble(APP.instance.sID("green"), solid_color.rgb.green) + desc4.putDouble(APP.instance.sID("blue"), solid_color.rgb.blue) + desc3.putObject(APP.instance.sID("color"), APP.instance.sID("RGBColor"), desc4) + desc2.putObject( + APP.instance.sID("type"), APP.instance.sID("solidColorLayer"), desc3 + ) + desc1.putObject(APP.instance.sID("using"), APP.instance.sID("contentLayer"), desc2) + APP.instance.executeAction( + APP.instance.sID("make"), desc1, DialogModes.DisplayNoDialogs + ) + + layer = docref.activeLayer + if not isinstance(layer, ArtLayer): + raise ValueError( + "Failed to create shape layer. Active layer is unexpectedly not an ArtLayer." + ) + if name: + layer.name = name + if hide: + layer.visible = False + if relative_layer: + layer.move(relative_layer, placement) + + return layer diff --git a/src/utils/adobe.py b/src/utils/adobe.py index 5adf94d5..61e10b24 100644 --- a/src/utils/adobe.py +++ b/src/utils/adobe.py @@ -342,21 +342,25 @@ def executeAction( * Version Checks """ - @cache + @cached_property def supports_target_text_replace(self) -> bool: """bool: Checks if Photoshop version supports targeted text replacement.""" return self.version_meets_requirement("22.0.0") - @cache + @cached_property def supports_webp(self) -> bool: """bool: Checks if Photoshop version supports WEBP files.""" return self.version_meets_requirement("23.2.0") - @cache + @cached_property def supports_generative_fill(self) -> bool: """Checks if Photoshop version supports Generative Fill.""" return self.version_meets_requirement("24.6.0") + @cached_property + def supports_uxp_scripts(self) -> bool: + return self.version_meets_requirement("23.5.0") + def version_meets_requirement(self, value: str) -> bool: """Checks if Photoshop version meets or exceeds required value. diff --git a/src/utils/uxp/__init__.py b/src/utils/uxp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/utils/uxp/base.py b/src/utils/uxp/base.py new file mode 100644 index 00000000..aac79b0d --- /dev/null +++ b/src/utils/uxp/base.py @@ -0,0 +1,61 @@ +from functools import cached_property +from json import dumps +from pathlib import Path +from typing import Any + +from photoshop.api import ActionDescriptor +from photoshop.api.enumerations import DialogModes + +from src import APP +from src._state import PATH + + +def replace_last(string: str, old: str, new: str) -> str: + old_idx = string.rfind(old) + if old_idx > -1: + return string[:old_idx] + new + string[old_idx + len(old) :] + return string + + +def open_in_photoshop(path: Path | str): + desc = ActionDescriptor() + desc.putPath(APP.instance.cID("null"), str(path)) + APP.instance.executeAction( + APP.instance.sID("open"), desc, DialogModes.DisplayNoDialogs + ) + + +class _UXPAccess: + @cached_property + def path_temp_script(self) -> Path: + return PATH.TMP / "_temp.psjs" + + @cached_property + def path_temp_script_absolute(self) -> str: + return str(self.path_temp_script.resolve()).replace("\\", "/") + + def read_script(self, name: str) -> str: + with open(PATH.JS_SCRIPTS / name, "r", encoding="utf-8") as f: + return f.read() + + def construct_script(self, script: str, data: Any) -> None: + script_str = script.replace( + "data = []", f"data = {dumps(data, ensure_ascii=False)}" + ) + with open(self.path_temp_script, "w", encoding="utf-8") as f: + f.write(script_str) + + def run_script(self, script: str, data: Any) -> None: + """Runs an UXP script in Photoshop.""" + self.construct_script(script, data) + open_in_photoshop(self.path_temp_script_absolute) + # try: + # APP.instance.open(self.path_temp_script_absolute) + # except COMError as err: + # # The open script operation errors even if the script executes successfully + # if "-2147213504," not in str(err): + # print("Batch play failed for script:", script) + # raise err + + +uxp = _UXPAccess() diff --git a/src/utils/uxp/batch_play.py b/src/utils/uxp/batch_play.py new file mode 100644 index 00000000..dd5d2136 --- /dev/null +++ b/src/utils/uxp/batch_play.py @@ -0,0 +1,62 @@ +from functools import cached_property +from typing import Literal, NotRequired, TypedDict + +from .base import uxp + + +class ActionTarget(TypedDict): + _ref: str + + +class ActionTargetID(ActionTarget): + _id: int + + +class ActionTargetName(ActionTarget): + _name: str + + +class ActionTargetIndex(ActionTarget): + _index: int + + +class ActionTargetEnumeration(ActionTarget): + _enum: NotRequired[str] + _value: NotRequired[str] + + +class ActionTargetProperty(ActionTarget): + _property: str + + +class OptionsDescriptor(TypedDict): + dialogOptions: NotRequired[Literal["silent", "dontDisplay", "display"]] + suppressProgressBar: NotRequired[bool] + + +class ActionDescriptor(TypedDict): + _target: NotRequired[ + list[ + ActionTarget + | ActionTargetID + | ActionTargetName + | ActionTargetIndex + | ActionTargetEnumeration + | ActionTargetProperty + ] + ] + _options: NotRequired[OptionsDescriptor] + + +class _Cache: + @cached_property + def batch_play_template_script(self) -> str: + return uxp.read_script("batchPlay.js") + + +_cache = _Cache() + + +def batch_play(*descriptors: ActionDescriptor) -> None: + """Runs a batch play script in Photoshop.""" + uxp.run_script(_cache.batch_play_template_script, descriptors) diff --git a/src/utils/uxp/path.py b/src/utils/uxp/path.py new file mode 100644 index 00000000..c1ee157b --- /dev/null +++ b/src/utils/uxp/path.py @@ -0,0 +1,29 @@ +from collections.abc import Iterable +from functools import cached_property +from typing import NotRequired, TypedDict + +from .base import uxp + + +class PointConf(TypedDict): + x: float | int + y: float | int + + +class PathPointConf(PointConf): + left: NotRequired[PointConf] + right: NotRequired[PointConf] + + +class _PathCache: + @cached_property + def create_path_template_script(self) -> str: + return uxp.read_script("createPath.js") + + +_cache = _PathCache() + + +def create_path(points: Iterable[PathPointConf]) -> None: + """Creates a path layer according to the given points.""" + uxp.run_script(_cache.create_path_template_script, points) diff --git a/src/utils/uxp/shape.py b/src/utils/uxp/shape.py new file mode 100644 index 00000000..4973e020 --- /dev/null +++ b/src/utils/uxp/shape.py @@ -0,0 +1,52 @@ +from enum import StrEnum +from typing import Literal, TypedDict + +from photoshop.api._artlayer import ArtLayer + +from src import APP +from src.helpers.layers import select_layers + +from .batch_play import ActionDescriptor, batch_play + + +class ShapeOperation(StrEnum): + Unite = "add" + SubtractFront = "subtract" + UniteAtOverlap = "interfaceIconFrameDimmed" + SubtractAtOverlap = "xor" + + +class ShapeOperationDescriptor(TypedDict): + _enum: Literal["shapeOperation"] + _value: ShapeOperation + + +class MergeShapesDescriptor(ActionDescriptor): + _obj: Literal["mergeLayersNew"] + shapeOperation: ShapeOperationDescriptor + + +class CombineShapeComponentsDescriptor(ActionDescriptor): + _obj: Literal["combine"] + + +def merge_shapes(*args: ArtLayer, operation: ShapeOperation) -> ArtLayer: + """Merges shapes, consuming the shapes that are earlier in the document order.""" + for layer in args: + layer.visible = True + select_layers([*args]) + desc: MergeShapesDescriptor = { + "_obj": "mergeLayersNew", + "shapeOperation": {"_enum": "shapeOperation", "_value": operation}, + } + comb_desc: CombineShapeComponentsDescriptor = { + "_obj": "combine", + "_target": [{"_ref": "path", "_enum": "ordinal"}], + } + batch_play(desc, comb_desc) + active_layer = APP.instance.activeDocument.activeLayer + if not isinstance(active_layer, ArtLayer): + raise ValueError( + "Failed to merge shapes. Active layer is unexpectedly not an ArtLayer." + ) + return active_layer diff --git a/src/utils/uxp/text.py b/src/utils/uxp/text.py new file mode 100644 index 00000000..8cc3209b --- /dev/null +++ b/src/utils/uxp/text.py @@ -0,0 +1,335 @@ +from _ctypes import COMError +from typing import Literal, NotRequired, TypedDict, Unpack + +from photoshop.api import SolidColor +from photoshop.api._artlayer import ArtLayer +from photoshop.api.enumerations import AutoKernType, PointKind +from photoshop.api.path_item import PathItem + +from src import APP +from src.helpers.effects import copy_layer_fx +from src.helpers.layers import select_layer + +from .batch_play import ActionDescriptor, batch_play + +FromToDescriptor = TypedDict( + "FromToDescriptor", + { + "from": int, + "to": int, + }, +) + + +class UnitDescriptor(TypedDict): + _unit: Literal["pixelsUnit", "pointsUnit"] + _value: float | int + + +class OrientationDescriptor(TypedDict): + _enum: Literal["orientation"] + _value: Literal["horizontal", "vertical"] + + +class AlignmentTypeDescriptor(TypedDict): + _enum: Literal["alignmentType"] + _value: Literal["left", "right"] + + +class DirectionTypeDescriptor(TypedDict): + _enum: Literal["directionType"] + _value: Literal["dirLeftToRight", "dirRightToLeft"] + + +class ParagraphStyleDescriptor(TypedDict): + _obj: Literal["paragraphStyle"] + align: NotRequired[AlignmentTypeDescriptor] + directionType: NotRequired[DirectionTypeDescriptor] + spaceBefore: NotRequired[UnitDescriptor] + impliedSpaceBefore: NotRequired[UnitDescriptor] + spaceAfter: NotRequired[UnitDescriptor] + impliedSpaceAfter: NotRequired[UnitDescriptor] + + +class ParagraphStyleRangeDescriptor(FromToDescriptor): + _obj: Literal["paragraphStyleRange"] + paragraphStyle: ParagraphStyleDescriptor + + +class AnchorDescriptor(TypedDict): + _obj: Literal["paint"] + horizontal: UnitDescriptor + vertical: UnitDescriptor + + +class PathPointDescriptor(TypedDict): + _obj: Literal["pathPoint"] + anchor: AnchorDescriptor + backward: NotRequired[AnchorDescriptor] + forward: NotRequired[AnchorDescriptor] + smooth: NotRequired[bool] + + +class SubpathsListDescriptor(TypedDict): + _obj: Literal["subpathsList"] + closedSubpath: bool + points: list[PathPointDescriptor] + + +class ShapeOperationDescriptor(TypedDict): + _enum: Literal["shapeOperation"] + _value: Literal["xor"] + + +class PathComponentDescriptor(TypedDict): + _obj: Literal["pathComponent"] + shapeOperation: ShapeOperationDescriptor + subpathListKey: list[SubpathsListDescriptor] + + +class CharDescriptor(TypedDict): + _enum: Literal["char"] + _value: Literal["box"] + + +class PathClassDescriptor(TypedDict): + _obj: Literal["pathClass"] + pathComponents: list[PathComponentDescriptor] + + +class TextShapeDescriptor(TypedDict): + _obj: Literal["textShape"] + char: CharDescriptor + path: PathClassDescriptor + + +class ColorDescriptor(TypedDict): + _obj: Literal["RGBColor"] + blue: float | int + grain: float | int + red: float | int + + +class KerningDescriptor(TypedDict): + _enum: Literal["autoKern"] + _value: Literal["metricsKern", "opticalKern"] + + +class TextStyleDescriptor(TypedDict): + _obj: Literal["textStyle"] + color: NotRequired[ColorDescriptor] + fontName: NotRequired[str] + fontPostScriptName: NotRequired[str] + fontScript: NotRequired[int] + fontStyleName: NotRequired[str] + size: NotRequired[UnitDescriptor] + impliedFontSize: NotRequired[UnitDescriptor] + autoLeading: NotRequired[bool] + leading: NotRequired[UnitDescriptor] + impliedLeading: NotRequired[UnitDescriptor] + autoKern: NotRequired[KerningDescriptor] + + +class TextStyleRangeDescriptor(FromToDescriptor): + _obj: Literal["textStyleRange"] + textStyle: TextStyleDescriptor + + +class TextLayerDescriptor(TypedDict): + _obj: Literal["textLayer"] + kerningRange: NotRequired[list[None]] + orientation: NotRequired[OrientationDescriptor] + paragraphStyleRange: NotRequired[list[ParagraphStyleRangeDescriptor]] + textKey: NotRequired[str] + textShape: NotRequired[list[TextShapeDescriptor]] + textStyleRange: NotRequired[list[TextStyleRangeDescriptor]] + + +class MakeTextLayerActionDescriptor(ActionDescriptor): + _obj: Literal["make"] + using: TextLayerDescriptor + + +class CreateTextLayerWithPathOptions(TypedDict): + color: NotRequired[SolidColor] + size: NotRequired[float] + leading: NotRequired[float] + + +# leftDirection -> forward +# rightDirection -> backward +def create_text_layer_with_path( + reference_path: ArtLayer, + reference_text: ArtLayer, + **kwargs: Unpack[CreateTextLayerWithPathOptions], +) -> ArtLayer: + """Creates a shaped text layer, which aims to mimic the properties of reference_text layer.""" + select_layer(reference_path, make_visible=True) + + layer_path: PathItem | None = None + for path in APP.instance.activeDocument.pathItems: + if path.name == f"{reference_path.name} Shape Path": + layer_path = path + + if not layer_path: + layer_path = APP.instance.activeDocument.pathItems[-1] + + points: list[PathPointDescriptor] = [] + for sub_path in layer_path.subPathItems: + for point in sub_path.pathPoints: + points.append( + { + "_obj": "pathPoint", + "smooth": point.kind is PointKind.SmoothPoint, + "anchor": { + "_obj": "paint", + "horizontal": { + "_unit": "pixelsUnit", + "_value": point.anchor[0], + }, + "vertical": {"_unit": "pixelsUnit", "_value": point.anchor[1]}, + }, + "forward": { + "_obj": "paint", + "horizontal": { + "_unit": "pixelsUnit", + "_value": point.leftDirection[0], + }, + "vertical": { + "_unit": "pixelsUnit", + "_value": point.leftDirection[1], + }, + }, + "backward": { + "_obj": "paint", + "horizontal": { + "_unit": "pixelsUnit", + "_value": point.rightDirection[0], + }, + "vertical": { + "_unit": "pixelsUnit", + "_value": point.rightDirection[1], + }, + }, + } + ) + + reference_path.visible = False + + ref_text = reference_text.textItem + color = kwargs.get("color", ref_text.color) + size = kwargs.get("size", ref_text.size) + leading = kwargs.get("leading", ref_text.leading) + try: + space_after = ref_text.spaceAfter + space_before = ref_text.spaceBefore + except COMError: + # Hacky workaround to a value retrieval issue with an unknown cause. + # This was observed on a duplicated text layer when accessing spaceAfter. + # https://community.adobe.com/t5/photoshop-ecosystem-discussions/textitem-color-returns-quot-general-photoshop-error-quot/m-p/10900372#M303432 + ref_text.contents = ref_text.contents + space_after = ref_text.spaceAfter + space_before = ref_text.spaceBefore + desc: MakeTextLayerActionDescriptor = { + "_obj": "make", + "_target": [{"_ref": "textLayer"}], + "using": { + "_obj": "textLayer", + # If initially empty text is replaced later on that seems + # to mess up the text size and leading values. At least in my case + # when rendering a Showcase Adventure card they became very small (< 1 pt). + # We don't want to use placeholder text either as sometimes no text might + # be assigned to the layer. Whitespace seems to be an acceptable workaround. + "textKey": " ", # text contents + "textStyleRange": [ + { + "_obj": "textStyleRange", + "from": 0, + "to": 227, + "textStyle": { + "_obj": "textStyle", + "color": { + "_obj": "RGBColor", + "blue": color.rgb.blue, + "grain": color.rgb.green, + "red": color.rgb.red, + }, + "fontPostScriptName": ref_text.font, + "size": { + "_unit": "pointsUnit", + "_value": size, + }, + "autoLeading": bool(ref_text.useAutoLeading), + "leading": { + "_unit": "pointsUnit", + "_value": leading, + }, + "autoKern": { + "_enum": "autoKern", + "_value": "metricsKern" + if ref_text.autoKerning is AutoKernType.Metrics + else "opticalKern", + }, + }, + } + ], + "paragraphStyleRange": [ + { + "_obj": "paragraphStyleRange", + "from": 0, + "to": 227, + "paragraphStyle": { + "_obj": "paragraphStyle", + "spaceBefore": { + "_unit": "pointsUnit", + "_value": float(space_before), + }, + "spaceAfter": { + "_unit": "pointsUnit", + "_value": float(space_after), + }, + }, + } + ], + "textShape": [ + { + "_obj": "textShape", + "char": {"_enum": "char", "_value": "box"}, + "path": { + "_obj": "pathClass", + "pathComponents": [ + { + "_obj": "pathComponent", + "shapeOperation": { + "_enum": "shapeOperation", + "_value": "xor", + }, + "subpathListKey": [ + { + "_obj": "subpathsList", + "closedSubpath": True, + "points": points, + } + ], + } + ], + }, + } + ], + }, + } + batch_play(desc) + + created_layer = APP.instance.activeDocument.activeLayer + if not isinstance(created_layer, ArtLayer): + raise ValueError( + "Failed to create shaped text layer. Active layer is unexpectedly not an ArtLayer." + ) + created_layer.name = f"{reference_text.name} - Path" + + try: + copy_layer_fx(reference_text, created_layer) + except COMError: + pass + + return created_layer diff --git a/src_js/batchPlay.ts b/src_js/batchPlay.ts new file mode 100644 index 00000000..1b8ad364 --- /dev/null +++ b/src_js/batchPlay.ts @@ -0,0 +1,11 @@ +import { action, core } from "photoshop"; +import { ActionDescriptor } from "photoshop/dom/CoreModules"; + +const data: ActionDescriptor[] = []; + +async function doBatchPlay() { + return await action.batchPlay(data, {}); +} +core.executeAsModal(doBatchPlay, { + commandName: "Programmatic Batch Play", +}); diff --git a/src_js/createPath.ts b/src_js/createPath.ts new file mode 100644 index 00000000..2d521cdd --- /dev/null +++ b/src_js/createPath.ts @@ -0,0 +1,43 @@ +import { app, core, constants } from "photoshop"; +import { type PathPointInfo } from "photoshop/dom/objects/PathPointInfo"; + +interface PointConf { + x: number; + y: number; +} + +interface PathPointConf extends PointConf { + left?: PointConf; + right?: PointConf; +} + +const data: PathPointConf[] = []; + +function createPath(points: PathPointConf[]) { + const infoPoints: PathPointInfo[] = []; + for (const point of points) { + const info = new app.PathPointInfo(); + info.anchor = [point.x, point.y]; + info.kind = + point.left || point.right + ? constants.PointKind.SMOOTHPOINT + : constants.PointKind.CORNERPOINT; + info.leftDirection = point.left + ? [point.left.x, point.left.y] + : info.anchor; + info.rightDirection = point.right + ? [point.right.x, point.right.y] + : info.anchor; + infoPoints.push(info); + } + const subPath = new app.SubPathInfo(); + subPath.closed = true; + subPath.operation = constants.ShapeOperation.SHAPEADD; + subPath.entireSubPath = infoPoints; + const newPath = app.activeDocument.pathItems.add("New Path", [subPath]); + newPath.select(); +} + +core.executeAsModal(async () => createPath(data), { + commandName: "Programmatic Create Path", +}); diff --git a/src_js/types.d.ts b/src_js/types.d.ts new file mode 100644 index 00000000..b15c831e --- /dev/null +++ b/src_js/types.d.ts @@ -0,0 +1,11 @@ +import "photoshop/dom/Document"; +import type { Layer } from "photoshop/dom/Layer"; + +declare module "photoshop/dom/Document" { + export interface Document { + /** + * @minVersion set 26.9 + */ + set activeLayers(layers: Layer[]); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..b59e2082 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "rootDir": "./src_js", + "outDir": "./dist_js", + "module": "commonjs", + "target": "ES2017", + "allowJs": false, + "typeRoots": ["./node_modules/@types"] + }, + "include": ["src_js/**/*"] +} diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 00000000..74577cc5 --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "types": ["node"], + "noEmit": true, + "target": "esnext", + "module": "nodenext", + "rewriteRelativeImportExtensions": true, + "verbatimModuleSyntax": true + }, + "include": ["scripts_js/**/*"] +} From 03296a94f7f0a1fcd8d4ad103b1a011e282aa2d9 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Jun 2026 12:10:46 +0300 Subject: [PATCH 175/190] feat(layers.py): Add function for getting stroke size from layer effects --- src/helpers/layers.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/helpers/layers.py b/src/helpers/layers.py index d346805b..151cfe5b 100644 --- a/src/helpers/layers.py +++ b/src/helpers/layers.py @@ -3,7 +3,10 @@ """ from collections.abc import Iterable, Sequence +from contextlib import AbstractContextManager from logging import getLogger +from types import TracebackType +from typing import TypedDict from photoshop.api import ActionDescriptor, ActionReference from photoshop.api._artlayer import ArtLayer @@ -526,3 +529,50 @@ def select_no_layers() -> None: APP.instance.executeAction( APP.instance.sID("selectNoLayers"), d1, DialogModes.DisplayNoDialogs ) + + +class LayerVisibleContext(AbstractContextManager[None]): + def __init__(self, layer: ArtLayer | LayerSet) -> None: + self._layer = layer + self._initial_visibility: bool + + def __enter__(self) -> None: + self._initial_visibility = self._layer.visible + self._layer.visible = False + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._layer.visible = self._initial_visibility + + +class StrokeDetails(TypedDict): + size: int + + +def get_stroke_details(layer: ArtLayer | LayerSet) -> StrokeDetails | None: + with LayerVisibleContext(layer): + APP.instance.activeDocument.activeLayer = layer + + ref = ActionReference() + ref.putEnumerated( + APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt") + ) + desc: ActionDescriptor = APP.instance.executeActionGet(ref) + + layer_effects_id = APP.instance.sID("layerEffects") + if not desc.hasKey(layer_effects_id): + return + + layer_effects: ActionDescriptor = desc.getObjectValue(layer_effects_id) + + frame_fx_id = APP.instance.sID("frameFX") + if not layer_effects.hasKey(frame_fx_id): + return + + frame_fx: ActionDescriptor = layer_effects.getObjectValue(frame_fx_id) + + return {"size": frame_fx.getInteger(APP.instance.sID("size"))} From 37f76570490212b596ee48a0626beadd00cd9916 Mon Sep 17 00:00:00 2001 From: pappnu Date: Mon, 8 Jun 2026 12:16:15 +0300 Subject: [PATCH 176/190] feat: Use a shaped text layer to more space efficiently avoid overlapping the starting loyalty box in Planeswalker renders --- src/helpers/position.py | 141 +++++------------- src/helpers/text.py | 266 ++++++++++++++++++++++++++++++---- src/templates/planeswalker.py | 10 +- src/text_layers.py | 58 ++++---- 4 files changed, 314 insertions(+), 161 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 45ba4ba5..4e369ae6 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -2,8 +2,8 @@ * Helpers: Positioning """ -import math from collections.abc import Sequence +from enum import Enum from typing import Literal from photoshop.api._artlayer import ArtLayer @@ -27,7 +27,6 @@ select_bounds, select_overlapping, ) -from src.helpers.text import get_font_size, set_text_size_and_leading from src.utils.adobe import ReferenceLayer # Positioning @@ -267,8 +266,10 @@ def frame_panorama( # Get layer and full reference dimensions art_dim: LayerDimensions = get_layer_dimensions(layer) ref_dim = get_card_dimensions(document) - panorama_dim = (ref_dim["width"] * panorama_size[0], - ref_dim["height"] * panorama_size[1]) + panorama_dim = ( + ref_dim["width"] * panorama_size[0], + ref_dim["height"] * panorama_size[1], + ) # Scale the layer to fit either the largest dimension scale = 100 * max( @@ -379,11 +380,34 @@ def frame_layer_by_width( """ +class RefSide(Enum): + LEFT = 1 + TOP = 2 + RIGHT = 3 + BOTTOM = 4 + + +def check_bounds_overlap( + bounds: tuple[float, float, float, float], + ref_bounds: tuple[float, float, float, float], + ref_side: RefSide, +) -> bool: + if ref_side == RefSide.LEFT: + return bounds[2] > ref_bounds[0] + elif ref_side == RefSide.TOP: + return bounds[3] > ref_bounds[1] + elif ref_side == RefSide.RIGHT: + return bounds[0] < ref_bounds[2] + else: + return bounds[1] < ref_bounds[3] + + def check_reference_overlap( layer: ArtLayer, ref_bounds: tuple[float, float, float, float], + ref_side: RefSide = RefSide.TOP, docsel: Selection | None = None, -): +) -> float: """Checks if a layer is overlapping with given set of bounds. Args: @@ -392,14 +416,21 @@ def check_reference_overlap( docsel: Selection object, pull from document if not provided. Returns: - Bounds if overlap exists, otherwise None. + Amount of overlap between `ref_side` and the opposing side of `layer`. """ selection = docsel or APP.instance.activeDocument.selection select_bounds(ref_bounds, selection=selection) select_overlapping(layer) if bounds := check_selection_bounds(selection): selection.deselect() - return ref_bounds[1] - bounds[3] + if ref_side == RefSide.LEFT: + return ref_bounds[0] - bounds[2] + if ref_side == RefSide.TOP: + return ref_bounds[1] - bounds[3] + if ref_side == RefSide.RIGHT: + return ref_bounds[2] - bounds[0] + else: + return ref_bounds[3] - bounds[1] return 0 @@ -425,99 +456,3 @@ def clear_reference_vertical( layer.translate(0, delta) return delta return 0 - - -def clear_reference_vertical_multi( - text_layers: list[ArtLayer], - ref: ReferenceLayer, - loyalty_ref: ReferenceLayer, - space: int | float, - uniform_gap: bool = False, - font_size: float | None = None, - step: float = 0.2, - docref: Document | None = None, - docsel: Selection | None = None, -) -> None: - """Shift or resize multiple text layers to prevent vertical collision with a reference area. - - Note: - Used on Planeswalker cards to allow multiple text abilities to clear the loyalty box. - - Args: - text_layers: Ability text layers to nudge or resize. - ref: Reference area ability text layers must fit inside. - loyalty_ref: Reference area that covers the loyalty box. - space: Minimum space between planeswalker abilities. - uniform_gap: Whether the gap between abilities should be the same between each ability. - font_size: The current font size of the text layers, if known. Otherwise, calculate automatically. - step: The amount of font size and leading to step down each iteration. - docref: Reference document, use active if not provided (improves performance). - docsel: Selection object, pull from document if not provided (improves performance). - """ - # Return if adjustments weren't provided - if not loyalty_ref: - return - - # Establish fresh data - if font_size is None: - font_size = get_font_size(text_layers[0]) - layers = text_layers.copy() - movable = len(layers) - 1 - - # Calculate inside gap - total_space = ref.dims["height"] - sum( - [get_layer_height(layer) for layer in text_layers] - ) - if not uniform_gap: - inside_gap = ( - (total_space - space) - (ref.bounds[3] - layers[-1].bounds[1]) - ) / movable - else: - inside_gap = total_space / (len(layers) + 1) - leftover = (inside_gap - space) * movable - - # Does the bottom layer overlap with the loyalty box? - delta = check_reference_overlap( - layer=layers[-1], ref_bounds=loyalty_ref.bounds, docsel=docsel - ) - if delta >= 0: - return - - # Calculate the total distance needing to be covered - total_move = 0 - layers.pop(0) - for n, lyr in enumerate(layers): - total_move += math.fabs(delta) * ((len(layers) - n) / len(layers)) - - # Text layers can just be shifted upwards - if total_move < leftover: - layers.reverse() - for n, lyr in enumerate(layers): - move_y = delta * ((len(layers) - n) / len(layers)) - lyr.translate(0, move_y) - return - - # Layer gap would be too small, need to resize text then shift upward - font_size -= step - for lyr in text_layers: - set_text_size_and_leading(layer=lyr, size=font_size, leading=font_size) - - # Space apart planeswalker text evenly - spread_layers_over_reference( - layers=text_layers, - ref=ref, - gap=space if not uniform_gap else 0, - outside_matching=False, - ) - - # Check for another iteration - clear_reference_vertical_multi( - text_layers=text_layers, - ref=ref, - loyalty_ref=loyalty_ref, - space=space, - uniform_gap=uniform_gap, - font_size=font_size, - docref=docref, - docsel=docsel, - ) diff --git a/src/helpers/text.py b/src/helpers/text.py index 355cb22d..9039404b 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -4,13 +4,15 @@ from collections.abc import Iterable, Sequence from logging import getLogger +from math import fabs from typing import Literal, overload from photoshop.api import ActionDescriptor, ActionList, ActionReference, SolidColor from photoshop.api._artlayer import ArtLayer from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet -from photoshop.api.enumerations import DialogModes, LayerKind +from photoshop.api._selection import Selection +from photoshop.api.enumerations import DialogModes, ElementPlacement, LayerKind from photoshop.api.text_item import TextItem from src import APP @@ -28,7 +30,16 @@ set_or_copy_unit_double, ) from src.helpers.document import pixels_to_points -from src.utils.adobe import PS_EXCEPTIONS +from src.helpers.position import ( + RefSide, + check_bounds_overlap, + check_reference_overlap, + spread_layers_over_reference, +) +from src.helpers.shapes import create_shape_layer +from src.utils.adobe import PS_EXCEPTIONS, ReferenceLayer +from src.utils.uxp.shape import ShapeOperation, merge_shapes +from src.utils.uxp.text import create_text_layer_with_path _logger = getLogger(__name__) @@ -194,7 +205,7 @@ def replace_text_legacy( "checkAll" ), # Targeted replace doesn't work on old PS versions False - if targeted_replace and APP.instance.supports_target_text_replace() + if targeted_replace and APP.instance.supports_target_text_replace else True, ) desc32.putBoolean(APP.instance.sID("forward"), True) @@ -468,7 +479,7 @@ def override_text_style_ranges( * Text Item Size """ -ScaleAxis = Literal["xx", "yy"] +ScaleAxis = Literal["tx", "ty", "xx", "yy", "xy", "yx"] @overload @@ -488,7 +499,7 @@ def get_text_scale_factor( axis: ScaleAxis | list[ScaleAxis] = "yy", text_key: ActionDescriptor | None = None, ) -> float | list[float]: - """Get the scale factor of the document for changing text size. + """Gets a transform value from text layer. Args: layer: The layer to make active and run the check on. @@ -518,6 +529,26 @@ def get_text_scale_factor( return 1.0 +def get_text_click_point( + layer: ArtLayer, + document_width: float | None = None, + document_height: float | None = None, + text_key: ActionDescriptor | None = None, +) -> tuple[float, float]: + if not text_key: + text_key = get_text_key(layer) + if document_width is None or document_height is None: + doc = APP.instance.activeDocument + document_width = doc.width + document_height = doc.height + id_text_click_point = APP.instance.sID("textClickPoint") + desc = text_key.getObjectValue(id_text_click_point) + return ( + desc.getUnitDoubleValue(APP.instance.sID("horizontal")) / 100 * document_width, + desc.getUnitDoubleValue(APP.instance.sID("vertical")) / 100 * document_height, + ) + + """ * Text Alignment """ @@ -751,14 +782,19 @@ def ensure_visible_reference(reference: ArtLayer) -> TextItem | None: def scale_text_right_overlap( - layer: ArtLayer, reference: ArtLayer, gap: int = 30 + layer: ArtLayer, + reference: ArtLayer, + reference_side: RefSide, + step_sizes: Sequence[float] | None = None, + gap: float = 30, ) -> None: - """Scales a text layer down (in 0.2 pt increments) until its right bound - has a 30 px~ (based on DPI) clearance from a reference layer's left bound. + """Scales a text layer down (in 0.2 pt increments) until it + doesn't overlap with the reference layer's `reference_side` bound. Args: layer: The text item layer to scale. reference: Reference layer we need to avoid. + reference_side: Which side of reference to check overlap with. gap: Minimum gap to ensure between the layer and reference (DPI adjusted). """ # Ensure a valid and visible reference layer @@ -768,35 +804,55 @@ def scale_text_right_overlap( # Set starting variables font_size = old_size = get_font_size(layer) - ref_left_bound = reference.bounds[0] - APP.instance.scale_by_dpi(gap) - step, half_step = 0.4, 0.2 + scaled_gap = APP.instance.scale_by_dpi(gap) + ref_bounds = reference.bounds + ref_bounds = ( + ref_bounds[0] - scaled_gap, + ref_bounds[1] - scaled_gap, + ref_bounds[2] + scaled_gap, + ref_bounds[3] + scaled_gap, + ) + layer_bounds = layer.bounds + step_sizes = step_sizes or (0.4, 0.2) # Guard against reference being left of the layer - if ref_left_bound < layer.bounds[0]: + if ( + ref_bounds[0] < layer_bounds[0] + if reference_side == RefSide.LEFT + else ref_bounds[1] < layer_bounds[1] + if reference_side == RefSide.TOP + else ref_bounds[2] > layer_bounds[2] + if reference_side == RefSide.RIGHT + else ref_bounds[3] > layer_bounds[3] + ): # Reset reference if ref_TI: ref_TI.contents = "" return - # Make our first check if scaling is necessary - if continue_scaling := bool(layer.bounds[2] > ref_left_bound): - # Step down the font till it clears the reference - while continue_scaling: - font_size -= step - set_text_size(layer, font_size) - continue_scaling = bool(layer.bounds[2] > ref_left_bound) + uneven_round = False + # Adjust text size down and up in decreasing steps + for idx, step_size in enumerate(step_sizes): + uneven_round = bool(idx % 2) - # Go up a half step - font_size += half_step - set_text_size(layer, font_size) + # Check overlap + while check_bounds_overlap(layer_bounds, ref_bounds, reference_side): + if uneven_round: + font_size += step_size + else: + font_size -= step_size - # If out of bounds, revert half step - if layer.bounds[2] > ref_left_bound: - font_size -= half_step set_text_size(layer, font_size) - # Shift baseline up to keep text centered vertically - layer.textItem.baselineShift = (old_size * 0.3) - (font_size * 0.3) + layer_bounds = layer.bounds + + # If the last round was uneven we have to go one step down + if uneven_round: + font_size -= step_sizes[-1] + set_text_size(layer, font_size) + + # Shift baseline up to keep text centered vertically + layer.textItem.baselineShift = (old_size * 0.3) - (font_size * 0.3) # Fix corrected reference layer if ref_TI: @@ -911,7 +967,7 @@ def scale_text_to_height( step: float = 0.4, font_size: float | None = None, ) -> float | None: - """Resize a given text layer's font size/leading until it fits inside a reference width. + """Resize a given text layer's font size/leading until it fits inside a reference height. Args: layer: Text layer to scale. @@ -1027,3 +1083,159 @@ def scale_text_layers_to_height( set_text_size_and_leading(layer, font_size, font_size) return font_size + + +def clear_reference_vertical_multi( + text_layers: list[ArtLayer], + ref: ReferenceLayer, + loyalty_ref: ReferenceLayer, + space: int | float, + uniform_gap: bool = False, + font_size: float | None = None, + step: float = 0.2, + docsel: Selection | None = None, + bottom_ref: ReferenceLayer | None = None, +) -> None: + """Shift or resize multiple text layers to prevent vertical collision with a reference area. + + Note: + Used on Planeswalker cards to allow multiple text abilities to clear the loyalty box. + + Args: + text_layers: Ability text layers to nudge or resize. + ref: Reference area ability text layers must fit inside. + loyalty_ref: Reference area that covers the loyalty box. + space: Minimum space between planeswalker abilities. + uniform_gap: Whether the gap between abilities should be the same between each ability. + font_size: The current font size of the text layers, if known. Otherwise, calculate automatically. + step: The amount of font size and leading to step down each iteration. + docsel: Selection object, pull from document if not provided (improves performance). + bottom_ref: Reference layer used to check text overflow at bottom. + """ + # Return if adjustments weren't provided + if not loyalty_ref: + return + + # Establish fresh data + if font_size is None: + font_size = get_font_size(text_layers[0]) + layers = text_layers.copy() + movable = len(layers) - 1 + + # Calculate inside gap + total_space = ref.dims["height"] - sum( + [get_layer_height(layer) for layer in text_layers] + ) + if not uniform_gap: + inside_gap = ( + (total_space - space) - (ref.bounds[3] - layers[-1].bounds[1]) + ) / movable + else: + inside_gap = total_space / (len(layers) + 1) + leftover = (inside_gap - space) * movable + + # Does the bottom layer overlap with the loyalty box? + delta = check_reference_overlap( + layer=layers[-1], ref_bounds=loyalty_ref.bounds, docsel=docsel + ) + if delta >= 0: + return + + if APP.instance.supports_uxp_scripts and bottom_ref: + keep_adjusting: bool = True + + while keep_adjusting: + # Avoid overlapping the loyalty box using a shaped text layer + bot_layer = text_layers[-1] + bot_layer_bounds = bot_layer.bounds + bottom_ref_bounds = bottom_ref.bounds + # TODO Get the "actual" transform x and y, which are visible in Photoshop UI, + # in order to precisely align the shaped text layer with the others. The current + # implementation uses text click point which seems to precisely match the x value + # but not the y value. Bounds and boundsNoEffects don't give the transform values + # and as such can't be used here. + x, _ = get_text_click_point(bot_layer) + base_shape = create_shape_layer( + ( + {"x": x, "y": bot_layer_bounds[1]}, + {"x": bot_layer_bounds[2], "y": bot_layer_bounds[1]}, + {"x": bot_layer_bounds[2], "y": bottom_ref_bounds[3] + 500}, + {"x": x, "y": bottom_ref_bounds[3] + 500}, + ), + relative_layer=bot_layer, + placement=ElementPlacement.PlaceBefore, + ) + loyalty_box_cutout = loyalty_ref.duplicate( + base_shape, ElementPlacement.PlaceBefore + ) + merged_shape = merge_shapes( + loyalty_box_cutout, base_shape, operation=ShapeOperation.SubtractFront + ) + shaped_text = create_text_layer_with_path( + reference_path=merged_shape, + reference_text=bot_layer, + size=font_size, + leading=font_size, + ) + shaped_text.textItem.contents = bot_layer.textItem.contents + bot_layer.visible = False + text_layers[-1] = shaped_text + + spread_layers_over_reference( + layers=text_layers, + ref=ref, + gap=space if not uniform_gap else 0, + outside_matching=False, + ) + + if keep_adjusting := text_layers[-1].bounds[3] >= bottom_ref_bounds[1]: + font_size -= step + for lyr in text_layers: + set_text_size_and_leading( + layer=lyr, size=font_size, leading=font_size + ) + + spread_layers_over_reference( + layers=text_layers, + ref=ref, + gap=space if not uniform_gap else 0, + outside_matching=False, + ) + else: + # Calculate the total distance needing to be covered + total_move = 0 + layers.pop(0) + for n, lyr in enumerate(layers): + total_move += fabs(delta) * ((len(layers) - n) / len(layers)) + + # Text layers can just be shifted upwards + if total_move < leftover: + layers.reverse() + for n, lyr in enumerate(layers): + move_y = delta * ((len(layers) - n) / len(layers)) + lyr.translate(0, move_y) + return + + # Layer gap would be too small, need to resize text then shift upward + font_size -= step + for lyr in text_layers: + set_text_size_and_leading(layer=lyr, size=font_size, leading=font_size) + + # Space apart planeswalker text evenly + spread_layers_over_reference( + layers=text_layers, + ref=ref, + gap=space if not uniform_gap else 0, + outside_matching=False, + ) + + # Check for another iteration + clear_reference_vertical_multi( + text_layers=text_layers, + ref=ref, + loyalty_ref=loyalty_ref, + space=space, + uniform_gap=uniform_gap, + font_size=font_size, + docsel=docsel, + ) diff --git a/src/templates/planeswalker.py b/src/templates/planeswalker.py index d2b53a17..97d8107a 100644 --- a/src/templates/planeswalker.py +++ b/src/templates/planeswalker.py @@ -14,6 +14,7 @@ import src.text_layers as text_classes from src.enums.layers import LAYERS from src.helpers import scale_text_layers_to_height +from src.helpers.text import clear_reference_vertical_multi from src.layouts import NormalLayout, PlaneswalkerAbility, PlaneswalkerLayout from src.templates._core import StarterTemplate from src.templates._cosmetic import BorderlessMod, FullartMod @@ -222,6 +223,11 @@ def loyalty_reference(self) -> ReferenceLayer | None: """ArtLayer: Reference used to check ability layer collision with the loyalty box.""" return psd.get_reference_layer(LAYERS.LOYALTY_REFERENCE, self.loyalty_group) + @cached_property + def textbox_overflow_reference(self) -> ReferenceLayer | None: + """Rules text is not allowed to go below the top edge of this reference.""" + return None + """ * Methods """ @@ -295,15 +301,15 @@ def pw_layer_positioning(self) -> None: # Adjust text to avoid loyalty badge if self.layout.loyalty and self.loyalty_reference: - psd.clear_reference_vertical_multi( + clear_reference_vertical_multi( text_layers=self.ability_layers, ref=self.textbox_reference, loyalty_ref=self.loyalty_reference, space=spacing, uniform_gap=uniform_gap, font_size=font_size, - docref=self.docref, docsel=self.doc_selection, + bottom_ref=self.textbox_overflow_reference, ) # Align colons and shields to respective text layers diff --git a/src/text_layers.py b/src/text_layers.py index 8bc588e5..8fff0ad5 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -3,7 +3,7 @@ """ from _ctypes import COMError -from contextlib import suppress +from collections.abc import Sequence from functools import cached_property from logging import getLogger from re import Match @@ -35,7 +35,11 @@ from src.helpers import select_layer from src.helpers.bounds import LayerDimensions, get_layer_dimensions, get_layer_width from src.helpers.colors import apply_color, rgb_black -from src.helpers.position import clear_reference_vertical, position_between_layers +from src.helpers.position import ( + RefSide, + clear_reference_vertical, + position_between_layers, +) from src.helpers.selection import select_layer_bounds, select_layer_pixels from src.helpers.text import ( get_text_scale_factor, @@ -226,7 +230,7 @@ def color(self) -> SolidColor: @cached_property def font(self) -> str: """Font provided, or fallback on global constant.""" - return self.kw_font or CON.font_title + return self.kw_font or self.TI.font @cached_property def replace_characters_in_title_font(self) -> bool: @@ -244,14 +248,6 @@ def validate(self): # Layer is valid, select and show it select_layer(self.layer, True) return True - with suppress(Exception): - # Layer provided doesn't exist or isn't a text layer - name = self.layer.name if self.layer else "[Non-Layer]" - print( - f"Text Field class: {self.__class__.__name__}\n" - f"Invalid layer provided: {name}" - ) - self.layer.visible = False return False def execute(self): @@ -284,7 +280,7 @@ def replacer(match: Match[str]) -> str: self.TI.color = self.color # Update font manually if mismatch detected - if self.font != CardFonts.TITLES: + if self.font != self.TI.font: self.TI.font = self.font # Change to English formatting if needed @@ -293,15 +289,33 @@ def replacer(match: Match[str]) -> str: class ScaledTextField(TextField): - """A TextField which automatically scales down its font size until the right bound - no longer overlaps with the `reference` layer's left bound.""" + """A TextField which automatically scales down its font size until it + no longer overlaps with the `reference` layer's `reference_side` bound.""" + + @cached_property + def step_sizes(self) -> Sequence[float]: + return (0.4, 0.2) + + @cached_property + def gap(self) -> float: + return 30 + + @cached_property + def reference_side(self) -> RefSide: + return RefSide.LEFT def execute(self): super().execute() # Scale down the text layer until it doesn't overlap with a reference layer if self.reference: - scale_text_right_overlap(self.layer, self.reference) + scale_text_right_overlap( + self.layer, + self.reference, + reference_side=self.reference_side, + step_sizes=self.step_sizes, + gap=self.gap, + ) class ScaledTextFieldLeft(TextField): @@ -320,13 +334,6 @@ class ScaledWidthTextField(TextField): """A TextField which automatically scales down its font size until the width of the layer is within the horizontal bound of a reference layer.""" - FONT = CardFonts.RULES - - @cached_property - def font(self) -> str: - """str: Font provided, or fallback on global constant.""" - return self.kw_font or CON.font_rules_text - @cached_property def reference_width(self) -> float | int | None: """Union[float, int]: Width of the reference layer provided.""" @@ -356,8 +363,6 @@ class FormattedTextField(TextField): * Formats any italicized or bolded text, as well as line breaks. """ - FONT = CardFonts.RULES - def __init__( self, layer: ArtLayer, contents: str = "", **kwargs: Unpack[TextFieldKwargs] ): @@ -510,11 +515,6 @@ def flavor_color(self) -> SolidColor | None: * Fonts """ - @cached_property - def font(self) -> str: - """Font provided, or fallback on global constant.""" - return self.kw_font or CON.font_rules_text - @cached_property def font_mana(self) -> str: """Mana font provided, or fallback on global constant.""" From 042023440c6f85233653ebca4960a0f9b63e7c45 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 11 Jun 2026 14:38:36 +0300 Subject: [PATCH 177/190] fix(CustomFileDialog.qml): Ignore unsupported folder paths instead of saving and then failing to restore them --- src/gui/qml/dialogs/CustomFileDialog.qml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/qml/dialogs/CustomFileDialog.qml b/src/gui/qml/dialogs/CustomFileDialog.qml index 5a1d5a5f..912b9989 100644 --- a/src/gui/qml/dialogs/CustomFileDialog.qml +++ b/src/gui/qml/dialogs/CustomFileDialog.qml @@ -15,7 +15,10 @@ FileDialog { fileMode: dialogModel.file_mode nameFilters: dialogModel.name_filters onAccepted: { - settings.setValue(dialogId, fileDialog.currentFolder); + const currFold = fileDialog.currentFolder.toString(); + if (!currFold.startsWith("data:")) { + settings.setValue(dialogId, currFold); + } dialogModel.on_accepted(fileDialog.selectedFiles); } onRejected: dialogModel.on_rejected() @@ -25,7 +28,10 @@ FileDialog { target: fileDialog.dialogModel function onSelectFiles(): void { - fileDialog.currentFolder = fileDialog.settings.value(fileDialog.dialogId, fileDialog.dialogModel.current_folder); + const saved = fileDialog.settings.value(fileDialog.dialogId, undefined); + if (saved) { + fileDialog.currentFolder = saved; + } fileDialog.open(); } } From 96a49e4673394be49ec4114e8a0db784adb12e7f Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 11 Jun 2026 14:40:10 +0300 Subject: [PATCH 178/190] docs(env.default.yml): Clarify the format of values that PS_VERSION expects --- src/data/env.default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/env.default.yml b/src/data/env.default.yml index b5bdbb94..9936ac04 100644 --- a/src/data/env.default.yml +++ b/src/data/env.default.yml @@ -22,7 +22,7 @@ # Enable Photoshop error dialogs when executing actions # PS_ERROR_DIALOG: False -# Optionally specify Photoshop version to look for (EXPERIMENTAL) +# Optionally specify Photoshop version to look for, e.g. "2025" (EXPERIMENTAL) # PS_VERSION: null ### From 913890689d6564aada5d55b3dce58f12df8411e8 Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 11 Jun 2026 14:45:29 +0300 Subject: [PATCH 179/190] fix: More accurately offset Planeswalker rules text away from starting loyalty box --- src/helpers/position.py | 16 ++++++++---- src/helpers/text.py | 55 +++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/helpers/position.py b/src/helpers/position.py index 4e369ae6..5f5848e6 100644 --- a/src/helpers/position.py +++ b/src/helpers/position.py @@ -24,7 +24,7 @@ ) from src.helpers.selection import ( check_selection_bounds, - select_bounds, + select_layer_pixels, select_overlapping, ) from src.utils.adobe import ReferenceLayer @@ -190,7 +190,7 @@ def spread_layers_over_reference( gap: float = 0, inside_gap: float = 0, outside_matching: bool = True, -) -> None: +) -> float: """Spread layers apart across a reference layer. Args: @@ -199,6 +199,9 @@ def spread_layers_over_reference( gap: Gap between the top of the reference and the first layer, or between all layers if not provided. inside_gap: Gap between each layer, calculated using leftover space if not provided. outside_matching: If enabled, will enforce top and bottom gap to match. + + Returns: + Calculated or given inside gap """ # Get reference dimensions if not provided height = ref.dims["height"] @@ -231,6 +234,8 @@ def spread_layers_over_reference( # Position the bottom layers relative to the top space_layers_apart(layers, inside_gap) + return inside_gap + def space_layers_apart(layers: Sequence[ArtLayer | LayerSet], gap: int | float) -> None: """Position list of layers apart using a given gap. @@ -404,7 +409,7 @@ def check_bounds_overlap( def check_reference_overlap( layer: ArtLayer, - ref_bounds: tuple[float, float, float, float], + ref: ArtLayer, ref_side: RefSide = RefSide.TOP, docsel: Selection | None = None, ) -> float: @@ -419,10 +424,11 @@ def check_reference_overlap( Amount of overlap between `ref_side` and the opposing side of `layer`. """ selection = docsel or APP.instance.activeDocument.selection - select_bounds(ref_bounds, selection=selection) + select_layer_pixels(ref) select_overlapping(layer) if bounds := check_selection_bounds(selection): selection.deselect() + ref_bounds = ref.bounds if ref_side == RefSide.LEFT: return ref_bounds[0] - bounds[2] if ref_side == RefSide.TOP: @@ -449,7 +455,7 @@ def clear_reference_vertical( """ # Use active layer if not provided docsel = docsel or APP.instance.activeDocument.selection - delta = check_reference_overlap(layer=layer, ref_bounds=ref.bounds, docsel=docsel) + delta = check_reference_overlap(layer=layer, ref=ref, docsel=docsel) # Check if selection is empty, if not translate our layer to clear the reference if delta < 0: diff --git a/src/helpers/text.py b/src/helpers/text.py index 9039404b..065af6ed 100644 --- a/src/helpers/text.py +++ b/src/helpers/text.py @@ -1135,34 +1135,38 @@ def clear_reference_vertical_multi( leftover = (inside_gap - space) * movable # Does the bottom layer overlap with the loyalty box? - delta = check_reference_overlap( - layer=layers[-1], ref_bounds=loyalty_ref.bounds, docsel=docsel - ) + delta = check_reference_overlap(layer=layers[-1], ref=loyalty_ref, docsel=docsel) if delta >= 0: return + outer_gap = space if not uniform_gap else 0 + if APP.instance.supports_uxp_scripts and bottom_ref: keep_adjusting: bool = True + ref_right = ref.bounds[2] + bottom_ref_bounds = bottom_ref.bounds + bottom_layer = text_layers[-1] + bottom_layer_top = bottom_layer.bounds[1] + bottom_layer_contents = bottom_layer.textItem.contents + x, _ = get_text_click_point(bottom_layer) + min_inside_gap = outer_gap while keep_adjusting: # Avoid overlapping the loyalty box using a shaped text layer - bot_layer = text_layers[-1] - bot_layer_bounds = bot_layer.bounds - bottom_ref_bounds = bottom_ref.bounds + bottom_layer = text_layers[-1] # TODO Get the "actual" transform x and y, which are visible in Photoshop UI, # in order to precisely align the shaped text layer with the others. The current # implementation uses text click point which seems to precisely match the x value # but not the y value. Bounds and boundsNoEffects don't give the transform values # and as such can't be used here. - x, _ = get_text_click_point(bot_layer) base_shape = create_shape_layer( ( - {"x": x, "y": bot_layer_bounds[1]}, - {"x": bot_layer_bounds[2], "y": bot_layer_bounds[1]}, - {"x": bot_layer_bounds[2], "y": bottom_ref_bounds[3] + 500}, + {"x": x, "y": bottom_layer_top}, + {"x": ref_right, "y": bottom_layer_top}, + {"x": ref_right, "y": bottom_ref_bounds[3] + 500}, {"x": x, "y": bottom_ref_bounds[3] + 500}, ), - relative_layer=bot_layer, + relative_layer=bottom_layer, placement=ElementPlacement.PlaceBefore, ) loyalty_box_cutout = loyalty_ref.duplicate( @@ -1173,22 +1177,24 @@ def clear_reference_vertical_multi( ) shaped_text = create_text_layer_with_path( reference_path=merged_shape, - reference_text=bot_layer, + reference_text=bottom_layer, size=font_size, leading=font_size, ) - shaped_text.textItem.contents = bot_layer.textItem.contents - bot_layer.visible = False + shaped_text.textItem.contents = bottom_layer_contents + bottom_layer.visible = False text_layers[-1] = shaped_text - spread_layers_over_reference( + inside_gap = spread_layers_over_reference( layers=text_layers, ref=ref, - gap=space if not uniform_gap else 0, - outside_matching=False, + gap=outer_gap, + outside_matching=True, ) - if keep_adjusting := text_layers[-1].bounds[3] >= bottom_ref_bounds[1]: + if keep_adjusting := inside_gap < min_inside_gap or ( + text_layers[-1].bounds[3] >= bottom_ref_bounds[1] + ): font_size -= step for lyr in text_layers: set_text_size_and_leading( @@ -1198,8 +1204,15 @@ def clear_reference_vertical_multi( spread_layers_over_reference( layers=text_layers, ref=ref, - gap=space if not uniform_gap else 0, - outside_matching=False, + gap=outer_gap, + outside_matching=True, + ) + else: + # Ensure uniform gaps between pinlines and all text layers + spread_layers_over_reference( + layers=text_layers, + ref=ref, + outside_matching=True, ) else: # Calculate the total distance needing to be covered @@ -1225,7 +1238,7 @@ def clear_reference_vertical_multi( spread_layers_over_reference( layers=text_layers, ref=ref, - gap=space if not uniform_gap else 0, + gap=outer_gap, outside_matching=False, ) From a36f7dc2ae833adc65438ff164f2b6451728b899 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 23 Jun 2026 06:13:44 +0300 Subject: [PATCH 180/190] feat: Allow running specific render test cases via the GUI --- src/gui/qml/App.qml | 78 +++++++++++++++++++++ src/gui/qml/models/batch_rendering_model.py | 7 +- src/gui/qml/models/template_list_model.py | 6 +- src/gui/qml/models/test_renders_model.py | 30 ++++++-- 4 files changed, 113 insertions(+), 8 deletions(-) diff --git a/src/gui/qml/App.qml b/src/gui/qml/App.qml index 014dd5b9..cbae7241 100644 --- a/src/gui/qml/App.qml +++ b/src/gui/qml/App.qml @@ -251,6 +251,45 @@ ApplicationWindow { onObjectRemoved: (index, object) => quickTestAllLayoutsSubMenu.removeItem(object) } } + CustomMenu { + id: testAllWithSpecificTestCaseSubMenu + + systemPalette: systemPalette + title: "Test all with test case" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenu { + id: testAllWithSpecificLayoutTestCaseSubMenu + + required property int index + required property string modelData + + systemPalette: systemPalette + title: modelData + + Instantiator { + model: testRendersModel.get_test_cases_for_layout(testAllWithSpecificLayoutTestCaseSubMenu.modelData) + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: testRendersModel.test_all(testAllWithSpecificLayoutTestCaseSubMenu.modelData, false, modelData) + } + + onObjectAdded: (index, object) => testAllWithSpecificLayoutTestCaseSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => testAllWithSpecificLayoutTestCaseSubMenu.removeItem(object) + } + } + + onObjectAdded: (index, object) => testAllWithSpecificTestCaseSubMenu.insertMenu(index, object) + onObjectRemoved: (index, object) => testAllWithSpecificTestCaseSubMenu.removeMenu(object) + } + } MenuSeparator {} @@ -307,6 +346,45 @@ ApplicationWindow { onObjectRemoved: (index, object) => selectedTemplateQuickLayoutsSubMenu.removeItem(object) } } + CustomMenu { + id: selectedTemplateSpecificTestCaseSubMenu + + systemPalette: systemPalette + title: "Test selected template(s) with test case" + + Instantiator { + model: testRendersModel.layout_categories + + delegate: CustomMenu { + id: selectedTemplateSpecificLayoutTestCaseSubMenu + + required property int index + required property string modelData + + systemPalette: systemPalette + title: modelData + + Instantiator { + model: testRendersModel.get_test_cases_for_layout(selectedTemplateSpecificLayoutTestCaseSubMenu.modelData) + + delegate: CustomMenuItem { + required property int index + required property string modelData + + systemPalette: systemPalette + text: modelData + onTriggered: appWindow.currentRenderingModel.test_render(selectedTemplateSpecificLayoutTestCaseSubMenu.modelData, false, modelData) + } + + onObjectAdded: (index, object) => selectedTemplateSpecificLayoutTestCaseSubMenu.insertItem(index, object) + onObjectRemoved: (index, object) => selectedTemplateSpecificLayoutTestCaseSubMenu.removeItem(object) + } + } + + onObjectAdded: (index, object) => selectedTemplateSpecificTestCaseSubMenu.insertMenu(index, object) + onObjectRemoved: (index, object) => selectedTemplateSpecificTestCaseSubMenu.removeMenu(object) + } + } } CustomMenu { diff --git a/src/gui/qml/models/batch_rendering_model.py b/src/gui/qml/models/batch_rendering_model.py index cbee7f57..17123dd8 100644 --- a/src/gui/qml/models/batch_rendering_model.py +++ b/src/gui/qml/models/batch_rendering_model.py @@ -243,7 +243,10 @@ def render_targets(self, urls: list[QUrl]) -> None: ) @Slot(str, bool) - def test_render(self, layout: str | None = None, quick: bool = False) -> None: + @Slot(str, bool, str) + def test_render( + self, layout: str | None = None, quick: bool = False, case: str | None = None + ) -> None: async def action() -> None: layout_category = LayoutCategory(layout) if layout else None @@ -279,7 +282,7 @@ async def action() -> None: }tests for batch mode selections." ) - await self._test_renders_model.test_renders(conf, quick) + await self._test_renders_model.test_renders(conf, quick, case=case) cancel_with_render(ensure_future(action()), self._render_queue) diff --git a/src/gui/qml/models/template_list_model.py b/src/gui/qml/models/template_list_model.py index 3cab1fb0..81ed7c3f 100644 --- a/src/gui/qml/models/template_list_model.py +++ b/src/gui/qml/models/template_list_model.py @@ -201,7 +201,10 @@ def render_targets(self, urls: list[QUrl]) -> None: ) @Slot(str, bool) - def test_render(self, layout: str | None = None, quick: bool = False) -> None: + @Slot(str, bool, str) + def test_render( + self, layout: str | None = None, quick: bool = False, case: str | None = None + ) -> None: async def action() -> None: layout_category = LayoutCategory(layout) if layout else None @@ -224,6 +227,7 @@ async def action() -> None: if layout_category else {category: (template,) for category in template.layout_categories}, quick, + case=case, ) cancel_with_render(ensure_future(action()), self._render_queue) diff --git a/src/gui/qml/models/test_renders_model.py b/src/gui/qml/models/test_renders_model.py index fd7a9ee0..84a3f3a7 100644 --- a/src/gui/qml/models/test_renders_model.py +++ b/src/gui/qml/models/test_renders_model.py @@ -57,6 +57,16 @@ def template_render_test_cases(self) -> dict[LayoutType, dict[str, str]]: def layout_categories(self) -> list[LayoutCategory]: return self._layout_categories + @Slot(str, result=list) + def get_test_cases_for_layout(self, layout: str) -> list[str]: + layout_category = LayoutCategory(layout) + all_cases: list[str] = [] + for layout_type in layout_map_category.get(layout_category, []): + all_cases.extend( + self.template_render_test_cases.get(layout_type, {}).keys() + ) + return all_cases + async def _run_action_per_layout_test_case( self, callback: Callable[ @@ -64,6 +74,7 @@ async def _run_action_per_layout_test_case( ], layout_categories: Iterable[LayoutCategory], quick: bool, + case: str | None = None, ) -> None: cards: list[CardDetails] = [] for layout_category in layout_categories: @@ -73,9 +84,13 @@ async def _run_action_per_layout_test_case( for idx, test_case in enumerate(test_cases): if quick and idx > 0: break + if case is not None and test_case != case: + continue cards.append( parse_card_info(PATH.SRC_IMG_TEST, name_override=test_case) ) + if case: + break await get_cards_from_details(cards, callback, self._app_config) def _collect_categories( @@ -92,12 +107,14 @@ async def test_renders( templates: dict[LayoutCategory, Iterable[AssembledTemplate] | None] | None = None, quick: bool = False, + case: str | None = None, ) -> None: """Queues test renders. Args: - templates: The layout categories and templates to queue tests for. Falsy value means that all tests should be queued. A falsy list of templates means that all applicable templates should be tested. - quick: Queue only the first test for each layout category and template combination.""" + templates: The layout categories and templates to queue tests for. Falsy value means that all tests should be queued. A falsy iterable of templates means that all applicable templates should be tested. + quick: Queue only the first test for each layout category and template combination. + case: Test only a specific case.""" if templates: layout_categories_to_test: Iterable[LayoutCategory] = templates.keys() for layout_category, test_templates in templates.items(): @@ -159,11 +176,14 @@ async def queue_test_render( await gather(*operations) await self._run_action_per_layout_test_case( - queue_test_render, layout_categories_to_test, quick + queue_test_render, layout_categories_to_test, quick, case=case ) @Slot(str, bool) - def test_all(self, layout: str | None = None, quick: bool = False) -> None: + @Slot(str, bool, str) + def test_all( + self, layout: str | None = None, quick: bool = False, case: str | None = None + ) -> None: _logger.info( f"Queueing {'quick' if quick else 'all'} test renders{ f' for layout {layout}' if layout else '' @@ -172,7 +192,7 @@ def test_all(self, layout: str | None = None, quick: bool = False) -> None: cancel_with_render( ensure_future( self.test_renders( - {LayoutCategory(layout): None} if layout else None, quick + {LayoutCategory(layout): None} if layout else None, quick, case=case ) ), self._render_queue, From 7f553ffbc2c03aa7d418f8bc0b997da905f7e219 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 23 Jun 2026 06:16:24 +0300 Subject: [PATCH 181/190] feat(apply_mask_to_layer_fx): Allow applying vector mask to layer effects --- src/helpers/masks.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/helpers/masks.py b/src/helpers/masks.py index 40fa4887..cd1fb7b9 100644 --- a/src/helpers/masks.py +++ b/src/helpers/masks.py @@ -89,11 +89,19 @@ def copy_vector_mask( """ -def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: +def apply_mask_to_layer_fx( + layer: ArtLayer | LayerSet | None = None, + apply: bool = True, + raster: bool = True, + vector: bool = False, +) -> None: """Sets the layer mask to apply to layer effects in blending options. Args: layer: ArtLayer or LayerSet object. + apply: Whether to apply or not apply the mask to layer effects. + raster: Apply the setting to raster layer mask. + vector: Apply the setting to vector layer mask. """ if not layer: layer = APP.instance.activeDocument.activeLayer @@ -101,7 +109,10 @@ def apply_mask_to_layer_fx(layer: ArtLayer | LayerSet | None = None) -> None: ref.putIdentifier(APP.instance.sID("layer"), layer.id) desc = APP.instance.executeActionGet(ref) layer_fx = desc.getObjectValue(APP.instance.sID("layerEffects")) - layer_fx.putBoolean(APP.instance.sID("layerMaskAsGlobalMask"), True) + if raster: + layer_fx.putBoolean(APP.instance.sID("layerMaskAsGlobalMask"), apply) + if vector: + layer_fx.putBoolean(APP.instance.sID("vectorMaskAsGlobalMask"), apply) desc = ActionDescriptor() desc.putReference(APP.instance.sID("target"), ref) desc.putObject(APP.instance.sID("to"), APP.instance.sID("layer"), layer_fx) From 24c591969495f1a7ec5d8d3d320f9c0d41c1ad1c Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 23 Jun 2026 06:19:04 +0300 Subject: [PATCH 182/190] feat(get_bounds_no_effects): Propagate the error when getting bounds without effects fails --- src/helpers/bounds.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/helpers/bounds.py b/src/helpers/bounds.py index 82f0d3a1..fcd2e07e 100644 --- a/src/helpers/bounds.py +++ b/src/helpers/bounds.py @@ -13,7 +13,7 @@ from src.helpers.descriptors import get_layer_action_descriptor from src.helpers.document import undo_action from src.helpers.layers import duplicate_group -from src.utils.adobe import PS_EXCEPTIONS, LayerBounds +from src.utils.adobe import LayerBounds """ * Types @@ -147,7 +147,9 @@ def get_card_dimensions(document: Document) -> LayerDimensions: bleed = int(document.resolution / 8) doc_width: float = int(document.width) doc_height: float = int(document.height) - return get_dimensions_from_bounds((bleed, bleed, doc_width - bleed, doc_height - bleed)) + return get_dimensions_from_bounds( + (bleed, bleed, doc_width - bleed, doc_height - bleed) + ) """ @@ -164,22 +166,15 @@ def get_bounds_no_effects(layer: ArtLayer | LayerSet) -> LayerBounds: Returns: list: Pixel location top left, top right, bottom left, bottom right. """ - with suppress(Exception): - d = get_layer_action_descriptor(layer) - try: - # Try getting bounds no effects - bounds = d.getObjectValue(APP.instance.sID("boundsNoEffects")) - except PS_EXCEPTIONS: - # Try getting bounds - bounds = d.getObjectValue(APP.instance.sID("bounds")) - return ( - bounds.getInteger(APP.instance.sID("left")), - bounds.getInteger(APP.instance.sID("top")), - bounds.getInteger(APP.instance.sID("right")), - bounds.getInteger(APP.instance.sID("bottom")), - ) - # Fallback to layer object bounds property - return layer.bounds + d = get_layer_action_descriptor(layer) + # Try getting bounds no effects + bounds = d.getObjectValue(APP.instance.sID("boundsNoEffects")) + return ( + bounds.getInteger(APP.instance.sID("left")), + bounds.getInteger(APP.instance.sID("top")), + bounds.getInteger(APP.instance.sID("right")), + bounds.getInteger(APP.instance.sID("bottom")), + ) def get_dimensions_no_effects(layer: ArtLayer | LayerSet) -> LayerDimensions: From 1ae101eabf0cf4ed8a4100beebf0a1a423af5be9 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 23 Jun 2026 06:20:49 +0300 Subject: [PATCH 183/190] perf(BaseTemplate): Avoid redundantly getting the type text layer twice --- src/templates/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/_core.py b/src/templates/_core.py index 69f9caca..32e3d3f5 100644 --- a/src/templates/_core.py +++ b/src/templates/_core.py @@ -599,7 +599,7 @@ def text_layer_type(self) -> ArtLayer | None: if typeline := psd.getLayer(LAYERS.TYPE_LINE_SHIFT, self.text_group): typeline.visible = True return typeline - return psd.getLayer(LAYERS.TYPE_LINE, self.text_group) + return layer @cached_property def text_layer_rules(self) -> ArtLayer | None: From 977eed733cb76c394bc44594ca1d80b5625e6d8a Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 23 Jun 2026 06:23:17 +0300 Subject: [PATCH 184/190] feat(SagaVectorTemplate): Properly call the super implementation of enable_transform_layers in order to ease modifying transform behaviour --- src/templates/saga.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/templates/saga.py b/src/templates/saga.py index eb1d213a..f2a6f9a3 100644 --- a/src/templates/saga.py +++ b/src/templates/saga.py @@ -598,12 +598,13 @@ def enabled_masks( """ def enable_transform_layers(self): + super().enable_transform_layers() + # Must enable Transform Icon group if self.transform_icon_layer and isinstance( (parent := self.transform_icon_layer.parent), LayerSet ): parent.visible = True - self.transform_icon_layer.visible = True """ * Transform Text Layer Methods From 67b09153ea181746296001feaa87556529781464 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 14 Jul 2026 11:04:55 +0300 Subject: [PATCH 185/190] fix(get_card_data): Correctly extract collector number from the card input --- src/cards.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cards.py b/src/cards.py index 20603896..26060349 100644 --- a/src/cards.py +++ b/src/cards.py @@ -115,7 +115,12 @@ def get_card_data( # Format our query data name, code = card.get("name", ""), card.get("set", "") - number = card.get("number", "").lstrip("0 ") if card.get("number") != "0" else "0" + if "collector_number" in card: + number = card["collector_number"] + else: + number = ( + card.get("number", "").lstrip("0 ") if card.get("number") != "0" else "0" + ) # Establish kwarg search terms kwargs = ( From 5140b421162768f9adb480fb0bb629ba6ac771a7 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 14 Jul 2026 11:05:53 +0300 Subject: [PATCH 186/190] feat(assign_layout): Always manually modify Scryfall data if the setting is on --- src/layouts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts.py b/src/layouts.py index cea9fd98..575f9d05 100644 --- a/src/layouts.py +++ b/src/layouts.py @@ -116,7 +116,7 @@ def assign_layout( _logger.error(f"Scryfall search failed for {name_failed}") return - if not scryfall_override and CFG.manually_edit_card_data: + if CFG.manually_edit_card_data: try: scryfall = manually_modify_model(scryfall, CFG.manual_text_editor) except Exception: From fcf4ff7ba1f72e0652b5c77dc2203088fe7c80a8 Mon Sep 17 00:00:00 2001 From: Luke Perez Date: Mon, 13 Jul 2026 13:17:31 +0200 Subject: [PATCH 187/190] fix: match json data files whose stem still carries artist/set/number tags parse_card_info strips artist/set/number tags from the image filename to get the card name, but data file stems were compared to that name as-is. This made matching fail whenever the .json file's name mirrored the full tagged image filename instead of using the bare card name. --- src/utils/inputs.py | 7 ++- .../test_match_images_with_data_files.py | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_match_images_with_data_files.py diff --git a/src/utils/inputs.py b/src/utils/inputs.py index 1f849ee2..ed2d5b0b 100644 --- a/src/utils/inputs.py +++ b/src/utils/inputs.py @@ -14,6 +14,7 @@ get_card_data, parse_card_info, ) +from src.enums.mtg import CardTextPatterns from src.render_spec import parse_render_spec from src.utils.data_structures import find_index, find_item from src.utils.scryfall import CardIdentifier, ScryfallCard @@ -44,7 +45,11 @@ def log_data_exception(file: Path) -> None: def add_card(card: CardDetails) -> None: card_name = card["name"] - idx = find_index(data_files, lambda item: item.stem == card_name) + idx = find_index( + data_files, + lambda item: CardTextPatterns.PATH_SPLIT.split(item.stem)[0].strip() + == card_name, + ) if idx > -1: data_file = data_files.pop(idx) try: diff --git a/tests/utils/test_match_images_with_data_files.py b/tests/utils/test_match_images_with_data_files.py new file mode 100644 index 00000000..5e9543c5 --- /dev/null +++ b/tests/utils/test_match_images_with_data_files.py @@ -0,0 +1,48 @@ +from pathlib import Path + +from pytest import MonkeyPatch + +from src.utils.inputs import match_images_with_data_files +from src.utils.scryfall import ScryfallCard + + +def test_match_images_with_data_files_json_stem_has_tags( + tmp_path: Path, monkeypatch: MonkeyPatch +) -> None: + """A .json file whose name still carries the same artist/set/number tags as + its paired image should be matched, not just a .json named after the bare + card name.""" + monkeypatch.setattr( + ScryfallCard, "model_validate_json", classmethod(lambda cls, data: object()) + ) + + image = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.jpg" + image.touch() + data_file = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.json" + data_file.write_text("{}") + + results = match_images_with_data_files([image, data_file]) + + assert len(results) == 1 + card, _ = results[0] + assert card["name"] == "Braids, Cabal Minion" + + +def test_match_images_with_data_files_json_stem_is_bare_name( + tmp_path: Path, monkeypatch: MonkeyPatch +) -> None: + """A .json file named after just the card name should still match, as before.""" + monkeypatch.setattr( + ScryfallCard, "model_validate_json", classmethod(lambda cls, data: object()) + ) + + image = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.jpg" + image.touch() + data_file = tmp_path / "Braids, Cabal Minion.json" + data_file.write_text("{}") + + results = match_images_with_data_files([image, data_file]) + + assert len(results) == 1 + card, _ = results[0] + assert card["name"] == "Braids, Cabal Minion" From 435c39fd256de9fd12d1d46ce0ef821ecb354027 Mon Sep 17 00:00:00 2001 From: Luke Perez Date: Tue, 14 Jul 2026 13:11:05 +0200 Subject: [PATCH 188/190] fix: address review feedback (reuse parse_card_info, fix tests types) --- src/utils/inputs.py | 5 +---- .../test_match_images_with_data_files.py | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/utils/inputs.py b/src/utils/inputs.py index ed2d5b0b..82de731e 100644 --- a/src/utils/inputs.py +++ b/src/utils/inputs.py @@ -14,7 +14,6 @@ get_card_data, parse_card_info, ) -from src.enums.mtg import CardTextPatterns from src.render_spec import parse_render_spec from src.utils.data_structures import find_index, find_item from src.utils.scryfall import CardIdentifier, ScryfallCard @@ -46,9 +45,7 @@ def add_card(card: CardDetails) -> None: card_name = card["name"] idx = find_index( - data_files, - lambda item: CardTextPatterns.PATH_SPLIT.split(item.stem)[0].strip() - == card_name, + data_files, lambda item: parse_card_info(item)["name"] == card_name ) if idx > -1: data_file = data_files.pop(idx) diff --git a/tests/utils/test_match_images_with_data_files.py b/tests/utils/test_match_images_with_data_files.py index 5e9543c5..9415f352 100644 --- a/tests/utils/test_match_images_with_data_files.py +++ b/tests/utils/test_match_images_with_data_files.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import cast from pytest import MonkeyPatch @@ -6,6 +7,10 @@ from src.utils.scryfall import ScryfallCard +def _fake_model_validate_json(cls: type[ScryfallCard], data: bytes) -> ScryfallCard: + return cast(ScryfallCard, object()) + + def test_match_images_with_data_files_json_stem_has_tags( tmp_path: Path, monkeypatch: MonkeyPatch ) -> None: @@ -13,18 +18,19 @@ def test_match_images_with_data_files_json_stem_has_tags( its paired image should be matched, not just a .json named after the bare card name.""" monkeypatch.setattr( - ScryfallCard, "model_validate_json", classmethod(lambda cls, data: object()) + ScryfallCard, "model_validate_json", classmethod(_fake_model_validate_json) ) image = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.jpg" - image.touch() data_file = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.json" data_file.write_text("{}") results = match_images_with_data_files([image, data_file]) assert len(results) == 1 - card, _ = results[0] + result = results[0] + assert isinstance(result, tuple) + card, _ = result assert card["name"] == "Braids, Cabal Minion" @@ -33,16 +39,17 @@ def test_match_images_with_data_files_json_stem_is_bare_name( ) -> None: """A .json file named after just the card name should still match, as before.""" monkeypatch.setattr( - ScryfallCard, "model_validate_json", classmethod(lambda cls, data: object()) + ScryfallCard, "model_validate_json", classmethod(_fake_model_validate_json) ) image = tmp_path / "Braids, Cabal Minion (Eric Peterson) [ODY] {117}.jpg" - image.touch() data_file = tmp_path / "Braids, Cabal Minion.json" data_file.write_text("{}") results = match_images_with_data_files([image, data_file]) assert len(results) == 1 - card, _ = results[0] + result = results[0] + assert isinstance(result, tuple) + card, _ = result assert card["name"] == "Braids, Cabal Minion" From c9d28af48a8c69546c6a51ebf6ff61356e9f999c Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 14 Jul 2026 11:34:22 +0300 Subject: [PATCH 189/190] perf(match_images_with_data_files): Parse information from card data filenames only once --- src/utils/inputs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/inputs.py b/src/utils/inputs.py index 82de731e..45e2967e 100644 --- a/src/utils/inputs.py +++ b/src/utils/inputs.py @@ -30,7 +30,7 @@ def match_images_with_data_files( Raises: Pydantic.ValidationError: if some of the data files don't conform to the data model """ - data_files = [pth for pth in paths if pth.suffix == ".json"] + data_files = [parse_card_info(pth) for pth in paths if pth.suffix == ".json"] render_specs = [pth for pth in paths if pth.suffix in (".yaml", ".yml")] image_files = [pth for pth in paths if pth.suffix not in (".json", ".yaml", ".yml")] @@ -44,20 +44,20 @@ def log_data_exception(file: Path) -> None: def add_card(card: CardDetails) -> None: card_name = card["name"] - idx = find_index( - data_files, lambda item: parse_card_info(item)["name"] == card_name - ) + idx = find_index(data_files, lambda item: item["name"] == card_name) if idx > -1: data_file = data_files.pop(idx) try: results.append( ( card, - ScryfallCard.model_validate_json(data_file.read_bytes()), + ScryfallCard.model_validate_json( + data_file["file"].read_bytes() + ), ) ) except ValidationError: - log_data_exception(data_file) + log_data_exception(data_file["file"]) raise else: results.append(card) From 682987775c2d900f72c42eef5ff8ea05aed30bd9 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 15 Jul 2026 07:46:55 +0300 Subject: [PATCH 190/190] feat(FormattedTextArea): Allow disabling vertical centering of text Original implementation by Alex Taxiera --- src/text_layers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/text_layers.py b/src/text_layers.py index 8fff0ad5..35f4528f 100644 --- a/src/text_layers.py +++ b/src/text_layers.py @@ -76,6 +76,7 @@ class TextFieldKwargs(TypedDict): flavor_text_lead_divider: NotRequired[int | float] centered: NotRequired[bool] flavor_centered: NotRequired[bool] + vertically_centered: NotRequired[bool] bold_rules_text: NotRequired[bool] right_align_quote: NotRequired[bool] pt_reference: NotRequired[ReferenceLayer | None] @@ -562,6 +563,10 @@ def contents_centered(self) -> bool: def flavor_centered(self) -> bool: return self.kwargs.get("flavor_centered", self.contents_centered) + @cached_property + def vertically_centered(self) -> bool: + return self.kwargs.get("vertically_centered", True) + @cached_property def bold_rules_text(self) -> bool: return self.kwargs.get("bold_rules_text", False) @@ -908,7 +913,11 @@ def position_within_reference(self): if self.reference_dims: # Ensure the layer is centered vertically dims = get_layer_dimensions(self.layer) - self.layer.translate(0, self.reference_dims["center_y"] - dims["center_y"]) + + if self.vertically_centered: + self.layer.translate( + 0, self.reference_dims["center_y"] - dims["center_y"] + ) # Ensure the layer is centered horizontally if needed if self.contents_centered and self.flavor_centered: