diff --git a/blroots/__init__.py b/blroots/__init__.py index 32208d3..fb588e1 100644 --- a/blroots/__init__.py +++ b/blroots/__init__.py @@ -5,17 +5,17 @@ from .gather import BLExtInfo, blext_info from .utils import bl_logger as log __all__ = [ + 'BLExtInfo', + 'BLField', 'bl_prop', + 'blext_dir', + 'blext_info', + 'blext_prefs', 'gather', 'init', + 'log', + 'log', 'prefs', 'registration', 'typ', - 'blext_dir', - 'blext_prefs', - 'BLField', - 'BLExtInfo', - 'log', - 'blext_info', - 'bl_logger', ] diff --git a/blroots/bl_prop/__init__.py b/blroots/bl_prop/__init__.py index 7ada836..7efd746 100644 --- a/blroots/bl_prop/__init__.py +++ b/blroots/bl_prop/__init__.py @@ -5,10 +5,10 @@ from .bl_prop import BLProp, SupportedBLPropType from .signal import Signal __all__ = [ - 'types', 'BLField', 'BLIDProps', 'BLProp', - 'SupportedBLPropType', 'Signal', + 'SupportedBLPropType', + 'types', ] diff --git a/blroots/bl_prop/bl_field.py b/blroots/bl_prop/bl_field.py index 7b962b5..093037a 100644 --- a/blroots/bl_prop/bl_field.py +++ b/blroots/bl_prop/bl_field.py @@ -23,12 +23,9 @@ In particular, import functools import typing as typ -import bpy - # import griffe from .bl_id_props import BLIDProps from .bl_prop import BLProp, SupportedBLPropType -from .signal import Signal #################### @@ -44,7 +41,7 @@ class BLField: display_name: str | None = None, description: str | None = None, **bl_prop_info: dict[str, typ.Any], - ): + ) -> None: """Initializes and sets the attribute to a given default value. The attribute **must** declare a type annotation, and it **must** match the type of `default_value`. @@ -63,35 +60,6 @@ class BLField: #################### # - Initialization #################### - def init_on_bl_class(self, bl_class: type[SupportedBLPropType]) -> None: - # Add 'draw_blfield' Method - ## - This enables the use of BLProp-specific draw() methods. - if not hasattr(bl_class, f'blfield__{self.name}'): - setattr( - bl_class, - f'blfield__{self.name}', - property(lambda bl_instance: self), - ) - if not hasattr(bl_class, 'draw_blfield'): - - def draw_blfield( - bl_instance: SupportedBLPropType, - layout: bpy.types.UILayout, - bl_field_name: str, - **kwargs: dict[str, typ.Any], - ) -> None: - # Find BLField Descriptor on BLInstance - bl_field = getattr(bl_instance, f'blfield__{bl_field_name}', None) - - # Call draw() of BLProp - if bl_field is not None: - bl_field.bl_prop.draw(bl_instance, layout, **kwargs) - else: - msg = f"A BLField with the name '{bl_field_name}' could not be found on the BLInstance '{bl_instance!s}'" - raise ValueError(msg) - - bl_class.draw_blfield = draw_blfield - def __set_name__(self, owner: type[SupportedBLPropType], name: str) -> None: """Sets up this descriptor on the class, preparing it for per-instance use. @@ -106,8 +74,10 @@ class BLField: owner: The class that contains an attribute assigned to an instance of this descriptor. name: The name of the attribute that an instance of descriptor was assigned to. """ - # Parse BLProp Information - ## - self.bl_prop is only available after this is done. + #################### + # - Parse Information for BLProp + #################### + ## *self.bl_prop is only valid to use after this section runs. if issubclass( owner, BLIDProps | SupportedBLPropType, @@ -139,12 +109,13 @@ class BLField: msg = f"Cannot declare 'BLField' (name='{name}') on unsupported class '{owner}'." raise TypeError(msg) - # Initialize BLProp on Owner - ## - Don't initialize a BLField on BLIDProps, since it modifies a global bpy type. - ## - Instead, its register() method must do the same sequence. + #################### + # - Initialize Owner + #################### + ## This is only done if the owner should directly accessed at this point. + ## Ex. BLIDProps has its own register() method. if issubclass(owner, SupportedBLPropType): self.bl_prop.init_on_bl_class(owner) - self.init_on_bl_class(owner) @functools.cached_property def bl_prop(self) -> BLProp: @@ -194,27 +165,7 @@ class BLField: bl_instance: Instance that is accessing the attribute. owner: The class that owns the instance. """ - # Read the Non-Persistent Cached Value - ## - The value must exist in the thread-safe memory cache. - cached_value = self.bl_prop.read_nonpersist(bl_instance) - - # Check Non-Persistent Cache Status - ## - Signals are guaranteed not to overlap with any other object. - if cached_value is Signal.CacheNotReady or cached_value is Signal.CacheEmpty: - # Check if Blender Class Instance Exists - if bl_instance is not None: - # Read Persisted Value from Blender Class Instance - ## - The value on the BLInstance can persist across runs. - persisted_value = self.bl_prop.read_persist(bl_instance) - - # Fill Non-Persistent Cache - ## - The next read will be from a thread-safe memory cache. - ## - This aggressive caching minimizes traffic on `bpy`. - self.bl_prop.write_nonpersist(bl_instance, persisted_value) - - return persisted_value - return self.bl_prop.default_value ## TODO: Good idea? - return cached_value + return self.bl_prop.read(bl_instance) def __set__(self, bl_instance: SupportedBLPropType | None, value: typ.Any) -> None: """Sets the value described by the BLField. @@ -230,23 +181,4 @@ class BLField: bl_instance: Instance that is accessing the attribute. owner: The class that owns the instance. """ - match value: - # Signal: Invalidate Non-Persistent Cache - case Signal.InvalidateCache: - self.bl_prop.invalidate_nonpersist(bl_instance) - - # Signal: BLProp-Specific - case ( - signal - ) if value is Signal.ResetEnumItems or value is Signal.ResetStrSearch: - self.bl_prop.handle_signal(signal) - - # Signal: Invalid - case Signal.CacheNotReady | Signal.CacheEmpty: - msg = 'Cannot set BLField to `CacheEmpty` or `CacheNotReady` signal.' - raise ValueError(msg) - - # General Case: - case _: - # Invalidate Persistent AND Non-Persistent Cache - self.bl_prop.write_persist(bl_instance, value) + self.bl_prop.write(bl_instance, value) diff --git a/blroots/bl_prop/bl_prop.py b/blroots/bl_prop/bl_prop.py index 31c0c54..6ddf006 100644 --- a/blroots/bl_prop/bl_prop.py +++ b/blroots/bl_prop/bl_prop.py @@ -32,9 +32,11 @@ SupportedBLPropType: typ.TypeAlias = ( ) -#################### -# - Blender Property (Abstraction) -#################### +def bl_prop_instance_property_name(bl_prop_name: str) -> str: + """Deduces the internal attribute name where a reference to this class instance will be persisted.""" + return f'blprop_instance__{bl_prop_name}' + + class BLProp(pyd.BaseModel, frozen=True): """Convenient, fast, and safe interface to a property on a Blender class. @@ -76,7 +78,9 @@ class BLProp(pyd.BaseModel, frozen=True): default_value: typ.Any - # TODO: Support certain property flags universally. + use_property_group: bool = False + + # TODO: Support certain property flags universally? #################### # - Computed @@ -86,70 +90,10 @@ class BLProp(pyd.BaseModel, frozen=True): """Deduces the internal attribute name where the raw Blender property data will be persisted.""" return f'blprop__{self.name}' - #################### - # - Type Support - #################### - @classmethod - def supports_type_hint(cls, field_type_hint: type) -> bool: - """Cheaply deduce whether this BLProp implements the given type.""" - raise NotImplementedError - - #################### - # - bpy.props Interface - #################### - @property - def bpy_prop_type(self) -> type[bpy.props.FloatProperty]: - """The underlying `bpy.props` class backing this property.""" - raise NotImplementedError - - @property - def bpy_prop_kwargs(self) -> dict[str, typ.Any]: - """Keyword arguments to pass to `self.bpy_prop_type`.""" - raise NotImplementedError - - #################### - # - Encode/Decode - #################### - def encode(self, value: typ.Any) -> typ.Any: - """Encode a value for compatibility with this Blender property, using the encapsulated types.""" - raise NotImplementedError - - def decode(self, value: typ.Any) -> typ.Any: - """Encode a value for compatibility with this Blender property, using the encapsulated types.""" - raise NotImplementedError - - def handle_signal(self, signal: Signal) -> None: - """Handle a signal in a manner specific to a Blender property.""" - raise NotImplementedError - - #################### - # - UI - #################### - def draw( - self, - bl_instance: SupportedBLPropType, - layout: bpy.types.UILayout, - ) -> None: - """Draw this property to the given Blender `UILayout`. - - Notes: - Can and should be overridden by subclasses. - Consider this a sensible default. - """ - ## TODO: - ## - Universal support for icons? - layout.prop(bl_instance, self.bl_name, text=self.display_name) - - #################### - # - Selection - #################### - @classmethod - def priority(cls) -> int: - """The priority with which a type hint will resolve to this implementation in case of a conflict. - - Lower implies higher priority. - """ - raise NotImplementedError + @functools.cached_property + def instance_bl_name(self): + """Deduces the internal attribute name where a reference to this class instance will be persisted.""" + return bl_prop_instance_property_name(self.name) #################### # - Creation @@ -207,6 +151,7 @@ class BLProp(pyd.BaseModel, frozen=True): self, bl_class: type[SupportedBLPropType], ) -> None: + """Prepare a Blender class to use this BLField""" """Declare the Blender property on a Blender class, ensuring that the property will be available to all `SupportedBLPropType` respecting instances of that class. Parameters: @@ -214,22 +159,34 @@ class BLProp(pyd.BaseModel, frozen=True): **Must** be chosen such that `BLPropType.from_type(obj_type) == self`. """ - # Add Method: Reset Instance ID - def reset_instance_id(bl_instance: SupportedBLPropType) -> None: - bl_instance.instance_id = str(uuid.uuid4()) + #################### + # - Add Method: reset_instance_id + #################### + if not hasattr(bl_class, 'reset_instance_id'): - bl_class.reset_instance_id = reset_instance_id + def reset_instance_id(bl_instance: SupportedBLPropType) -> None: + bl_instance.instance_id = str(uuid.uuid4()) - # ID: Dynamic Assignment to Class Variable + bl_class.reset_instance_id = reset_instance_id + + #################### + # - [bpy.types.ID] Add Properties: instance_id & self.bpy_prop + #################### if issubclass(bl_class, bpy.types.ID): # Set Instance ID if not hasattr(bl_class, 'instance_id'): bl_class.instance_id = bpy.props.StringProperty() # Set Property - setattr(bl_class, self.bl_name, self.bpy_prop) + if not hasattr(bl_class, self.bl_name): + setattr(bl_class, self.bl_name, self.bpy_prop) + else: + msg = f'Tried to set attribute {self.bl_name} on `bl_class={bl_class}`, but the attribute already exists.' + raise RuntimeError(msg) - # bpy_struct: Static Assignment to Class Annotations + #################### + # - [bpy_struct] Add Properties: instance_id & self.bpy_prop + #################### elif issubclass( bl_class, bpy.types.AddonPreferences @@ -244,8 +201,116 @@ class BLProp(pyd.BaseModel, frozen=True): # Set Annotation bl_class.__annotations__[self.bl_name] = self.bpy_prop + #################### + # - Add Attribute: {self.class_bl_name} + #################### + if not hasattr(bl_class, self.instance_bl_name): + setattr( + bl_class, + self.instance_bl_name, + classmethod(lambda _bl_class: self), + ) + ## This may look a little funky. That's because it is a little funky. + ## This trick lets gets the actual 'BLProp' instance 'self' from a 'bl_instance' + ## 'self' gets bound within the lambda returned by the classmethod. + ## In this way, we can access the BLProp underlying any BLField. + else: + msg = 'Cannot register two identically named BLProps on the same BLClass.' + raise ValueError(msg) + + #################### + # - Add Method: draw_blprop + #################### + if not hasattr(bl_class, 'draw_blprop'): + + def draw_blprop( + bl_instance: SupportedBLPropType, + layout: bpy.types.UILayout, + bl_prop_name: str, + **kwargs: dict[str, typ.Any], + ) -> None: + """Find a `BLProp` defined on a Blender instance, and call its `.draw()` method. + + Parameters: + bl_instance: An instance of a Blender class, which has been given a `BLProp`. + Since this is a method, this takes the place of `self`; that is to say, calling `self.draw_blprop` will set this parameter. + layout: A Blender layout to draw to. + class_bl_name: A Blender layout to draw to. + """ + # Find 'self' on BLInstance + ## The same draw_blprop must work for ALL BLProps with the same logic. + ## Thus, 'self.*' would absolutely not work. + bl_prop = getattr( + bl_instance, bl_prop_instance_property_name(bl_prop_name), None + ) + if bl_prop is not None and isinstance(bl_prop, BLProp): + bl_prop.draw(bl_instance, layout, **kwargs) + else: + msg = f"A BLProp instance could not be found at '{bl_prop_name}' on the BLInstance: '{bl_instance!s}'" + raise ValueError(msg) + + bl_class.draw_blprop = draw_blprop + #################### - # - Instance Methods + # - High-Level Cache Semantics + #################### + def read(self, bl_instance: SupportedBLPropType | None) -> typ.Any: + """A sensible choice of cache semantics for retrieving fields described by this `BLProp`. + + Parameters: + bl_instance: Instance that is accessing the attribute. + """ + # Read the Non-Persistent Cached Value + ## - The value must exist in the thread-safe memory cache. + cached_value = self.read_nonpersist(bl_instance) + + # Check Non-Persistent Cache Status + ## - Signals are guaranteed not to overlap with any other object. + if cached_value is Signal.CacheNotReady or cached_value is Signal.CacheEmpty: + # Check if Blender Class Instance Exists + if bl_instance is not None: + # Read Persisted Value from Blender Class Instance + ## - The value on the BLInstance can persist across runs. + persisted_value = self.read_persist(bl_instance) + + # Fill Non-Persistent Cache + ## - The next read will be from a thread-safe memory cache. + ## - This aggressive caching minimizes traffic on `bpy`. + self.write_nonpersist(bl_instance, persisted_value) + + return persisted_value + return self.default_value ## TODO: Good idea? + return cached_value + + def write(self, bl_instance: SupportedBLPropType | None, value: typ.Any) -> None: + """A sensible choice of cache semantics for writing fields described by this `BLProp`. + + Parameters: + bl_instance: Instance that is accessing the attribute. + """ + match value: + # Signal: Invalidate Non-Persistent Cache + case Signal.InvalidateCache: + self.invalidate_nonpersist(bl_instance) + + # Signal: BLProp-Specific + case signal if ( + value is Signal.ResetEnumItems or value is Signal.ResetStrSearch + ): + self.handle_signal(signal) + + # Signal: Invalid + case Signal.CacheNotReady | Signal.CacheEmpty: + msg = 'Cannot set BLProp to `CacheEmpty` or `CacheNotReady` signal.' + raise ValueError(msg) + + # General Case: + case _: + # Invalidate Persistent AND Non-Persistent Cache + self.write_persist(bl_instance, value) + + #################### + # - Low-Level Cache Semantics #################### def read_nonpersist(self, bl_instance: SupportedBLPropType | None) -> typ.Any: """Read the non-persistent cache value for this property. @@ -336,18 +401,34 @@ class BLProp(pyd.BaseModel, frozen=True): bl_instance: The Blender object to **NOTE**: `bl_instance` must not be `None`, as neighboring methods sometimes allow. """ - signal = bl_prop_cache.write( - bl_instance, - key=self.bl_name, - value=self.encode(value), - use_nonpersist=False, - use_persist=True, - ) + if self.use_property_group: + for subkey, subvalue in value.items(): + signal = bl_prop_cache.write( + getattr(bl_instance, self.bl_name), + key=subkey, + value=subvalue, + use_nonpersist=False, + use_persist=True, + ) - if signal is Signal.CacheNotReady: - msg = f"Tried to write value '{self.bl_name}={value}' to persistent cache of bl_instance '{bl_instance}', but the cache was not yet ready." - raise ValueError(msg) + if signal is Signal.CacheNotReady: + msg = f"Tried to write value '{self.bl_name}={value}' to persistent cache of bl_instance '{bl_instance}', but the cache was not yet ready." + raise ValueError(msg) + else: + signal = bl_prop_cache.write( + bl_instance, + key=self.bl_name, + value=self.encode(value), + use_nonpersist=False, + use_persist=True, + use_group=self.use_property_group, + ) + if signal is Signal.CacheNotReady: + msg = f"Tried to write value '{self.bl_name}={value}' to persistent cache of bl_instance '{bl_instance}', but the cache was not yet ready." + raise ValueError(msg) + + # Write Entire Structure to Non-Persistent Cache self.write_nonpersist(bl_instance, value) def invalidate_nonpersist(self, bl_instance: SupportedBLPropType | None) -> None: @@ -361,3 +442,73 @@ class BLProp(pyd.BaseModel, frozen=True): bl_instance, self.bl_name, ) + + #################### + # - Overridable: UI + #################### + def draw( + self, + bl_instance: SupportedBLPropType, + layout: bpy.types.UILayout, + text: str | None = None, + ) -> None: + """Draw this property to the given Blender `UILayout`. + + Notes: + Can and should be overridden by subclasses. + Consider this a sensible default. + """ + ## TODO: + ## - Universal support for icons? + layout.prop( + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + ) + + #################### + # - Abstract: Type Hint Support + #################### + @classmethod + def supports_type_hint(cls, field_type_hint: type) -> bool: + """Cheaply deduce whether this BLProp implements the given type.""" + raise NotImplementedError + + #################### + # - Abstract: bpy.props Interface + #################### + @property + def bpy_prop_type(self) -> type[bpy.props.FloatProperty]: + """The underlying `bpy.props` class backing this property.""" + raise NotImplementedError + + @property + def bpy_prop_kwargs(self) -> dict[str, typ.Any]: + """Keyword arguments to pass to `self.bpy_prop_type`.""" + raise NotImplementedError + + #################### + # - Abstract: Encode/Decode + #################### + def encode(self, value: typ.Any) -> typ.Any: + """Encode a value for compatibility with this Blender property, using the encapsulated types.""" + raise NotImplementedError + + def decode(self, value: typ.Any) -> typ.Any: + """Encode a value for compatibility with this Blender property, using the encapsulated types.""" + raise NotImplementedError + + def handle_signal(self, signal: Signal) -> None: + """Handle a signal in a manner specific to a Blender property.""" + raise NotImplementedError + + #################### + # - Selection + #################### + @classmethod + def priority(cls) -> int: + """The priority with which a type hint will resolve to this implementation in case of a conflict. + + Lower implies higher priority. + """ + raise NotImplementedError diff --git a/blroots/bl_prop/types/__init__.py b/blroots/bl_prop/types/__init__.py index ace3f59..55f6ae1 100644 --- a/blroots/bl_prop/types/__init__.py +++ b/blroots/bl_prop/types/__init__.py @@ -1,11 +1,15 @@ from .bool_prop import BoolProp from .float_prop import FloatProp from .int_prop import IntProp +from .path_prop import PathProp +from .str_enum_prop import StrEnumProp from .str_prop import StrProp __all__ = [ 'BoolProp', 'FloatProp', 'IntProp', + 'PathProp', + 'StrEnumProp', 'StrProp', ] diff --git a/blroots/bl_prop/types/bool_prop.py b/blroots/bl_prop/types/bool_prop.py index eb5179a..a359a19 100644 --- a/blroots/bl_prop/types/bool_prop.py +++ b/blroots/bl_prop/types/bool_prop.py @@ -91,6 +91,7 @@ class BoolProp(BLProp, frozen=True): self, bl_instance: SupportedBLPropType, layout: bpy.types.UILayout, + text: str | None = None, toggle: bool = False, expand: bool = False, ) -> None: @@ -98,7 +99,7 @@ class BoolProp(BLProp, frozen=True): layout.prop( bl_instance, self.bl_name, - text=self.display_name, + text=self.display_name if text is None else text, toggle=toggle, expand=expand, ) diff --git a/blroots/bl_prop/types/class_prop.py b/blroots/bl_prop/types/class_prop.py index f8152cf..e0bc9fa 100644 --- a/blroots/bl_prop/types/class_prop.py +++ b/blroots/bl_prop/types/class_prop.py @@ -24,25 +24,62 @@ import bpy from blroots.utils import bl_instance, logger -from ..bl_prop import BLProp +from ..bl_prop import BLProp, bl_prop_instance_property_name log = logger.get(__name__) PRIORITY = 11 +## TODO: Main thing, I think, at this point, is to find a way to register those ProeprtyGroups mindfully, then go play with it! + + +@typ.runtime_checkable +class BLPropClass(typ.Protocol): + @classmethod + def bl_prop_group(cls) -> type[bpy.types.PropertyGroup]: ... + + ## TODO: The bl_prop_group should be checked for: + ## - That it is a pydantic BaseModel. + ## - That the BaseModel has frozen=True. + ## - That data attributes are identical between the BL_ class and this version. + ## - That type annotations on data attributes are identical. + ## TODO: USAGE + ## - This class can be used freely without a backing BL_ class. + ## - This class can also be used to type-annotate a BLField. + ## -- The BL_ class must be registered before being assigned to anything. + ## -- Since frozen=True, only the entire instance can be replaced at a time. + ## --- Underneath, a backing PropertyGroup will be mutated as needed. + ## -- bl_events must be propagated to the backing PropertyGroup. + ## --- Use bl_event.propagate with source='parent' to get data from the PropertyGroup's parent. + ## --- Let's make sure to make it easy for instances of PropertyGroup to get that parent! + ## --- We can later do something similar for data pipelines between nodes and sockets. + + +# def to_bl_prop_group( +# self, bl_prop_group: BL_LoggingPrefs +# ) -> type[bpy.types.PropertyGroup]: +# bl_prop_group.use_log_file = self.use_log_file +# bl_prop_group.log_file_level = self.log_file_level +# bl_prop_group.log_file_path = self.log_file_path +# +# bl_prop_group.use_log_console = self.use_log_console +# bl_prop_group.log_console_level = self.log_console_level +# +# @classmethod +# def from_bl_prop_group(cls, bl_prop_group: BL_LoggingPrefs) -> typ.Self: +# return cls( +# use_log_file=bl_prop_group.use_log_file, +# log_file_level=bl_prop_group.log_file_level, +# log_file_path=bl_prop_group.log_file_path, +# use_log_console=bl_prop_group.use_log_console, +# log_console_level=bl_prop_group.log_console_level, +# ) + class ClassProp(BLProp, frozen=True): - """A single constrained floating-point number. + """A single class; in particular, an appropriately configured `pydantic` BaseModel.""" - Parameters: - abs_min: The absolute minimum value of the floating point number. - abs_max: The absolute maximum value of the floating point number. - soft_min: The lowest value that can be set using the UI slider. - soft_max: The highest value that can be set using the UI slider. - float_prec: The number of decimal places displayed in the UI. - float_step: The size of each mouse-move increment when using the UI slider. - _Divide this integer by `100` for actual change per increment._ - """ + use_property_group = True default_value: None @@ -51,17 +88,23 @@ class ClassProp(BLProp, frozen=True): #################### @classmethod def supports_type_hint(cls, field_type_hint: type) -> bool: - """Support only the explicit type `float` aka. `builtins.float`.""" - return inspect.isclass(field_type_hint) and hasattr( - field_type_hint, 'property_group' + """Support classes that conform to the `BLPropClass` protocol.""" + return inspect.isclass(field_type_hint) and isinstance( + field_type_hint, BLPropClass ) @property def bpy_property_group(self) -> type[bpy.props.PropertyGroup]: """Dynamically create a PropertyGroup.""" - ## TODO: We can make it; the trouble is, who registers it, and when? - ## - Also, there's an underlying class instance - pass + return self.type_hint.bl_prop_group() + ## TODO: Who registers it, and when? + ## TODO: I don't like having the "two-class" paradigm. + ## - On the plus side, "outside classes" can be adapted. + ## - On the minus side, it's two things now. Two parameter documentations to parse, etc. . + ## - If we did go with an all-in-one, typ.Annotated w/partial BLField constructor? + ## - Or somehow get pydantic to understand setting it to a BLField? + ## - I honestly don't know what to do exactly... + ## - On the other hand hand, look how mindful and simple this method is. #################### # - bpy.props Interface @@ -69,7 +112,7 @@ class ClassProp(BLProp, frozen=True): @property def bpy_prop_type(self) -> type[bpy.props.PointerProperty]: """Use `bpy.props.PointerProperty` to reference a particular pre-made `bpy.types.PropertyGroup` subclass.""" - return bpy.props.PointerProperty # type: ignore[no-any-return] + return bpy.props.PointerProperty @functools.cached_property def bpy_prop_kwargs(self) -> dict[str, typ.Any]: @@ -81,17 +124,48 @@ class ClassProp(BLProp, frozen=True): #################### # - Encode/Decode #################### - def encode(self, value: typ.Any) -> float: - """Encode a value that supports `float()` to a Blender float.""" + def encode(self, value: typ.Any) -> dict[str, typ.Any]: + """Encode a value to a dictionary that will be written to a `PropertyGroup`. + + Notes: + The pickle is this: + + - On the PropertyGroup, each field is associated with a unique BLProp instance. + - During BLProp initialization, a class method is created to allow accessing the BLProp underlying any BLField. + - Why do we need to do this? BLProp instance uses encode()/decode() to translate between user-facing and Blender-facing representations of data. + - Therefore,for each subproperty, retrieve the `BLProp` instance, then use its `.encode()` method to transform `value` fields down to the Blender representation that will be expected by the `PropertyGroup`. + - This naturally provides correct recursion, too, since `.encode()` will simply chain its way all the way down to the bottom. + + """ if isinstance(value, self.type_hint): - # TODO: Dump the model to a dictionary? How to handle the nested BLProps? Better to delegate somehow to the PropertyGroup? - pass + return { + bl_prop_name: ( + bl_prop := getattr( # noqa: F841 + self.bpy_property_group, + bl_prop_instance_property_name(bl_prop_name), + )() + ).encode(getattr(value, bl_prop_name)) + for bl_prop_name in self.bpy_property_group.bl_props + } raise NotImplementedError - def decode(self, raw_bl_value: float) -> float: - """Decode a Blender float by doing nothing.""" - return raw_bl_value + def decode(self, raw_bl_value: bpy.types.PropertyGroup) -> typ.Any: + """Decode the subclass of `bpy.types.PropertyGroup` subclass into the `pydantic.BaseModel` contained within `self.type_hint`. + + Notes: + Simply reading the `BLField`s invokes `.decode()` recursively and correctly. + Thus, decoding ends up being comparatively simpler than `encode()`. + """ + if isinstance(raw_bl_value, self.bpy_property_group): + return self.type_hint( + **{ + bl_prop_name: getattr(raw_bl_value, bl_prop_name) + for bl_prop_name in self.bpy_property_group.bl_props + } + ) + + raise NotImplementedError #################### # - UI diff --git a/blroots/bl_prop/types/enum_prop.py b/blroots/bl_prop/types/enum_prop.py deleted file mode 100644 index e69de29..0000000 diff --git a/blroots/bl_prop/types/float_prop.py b/blroots/bl_prop/types/float_prop.py index 9957c05..abcfacf 100644 --- a/blroots/bl_prop/types/float_prop.py +++ b/blroots/bl_prop/types/float_prop.py @@ -122,10 +122,16 @@ class FloatProp(BLProp, frozen=True): self, bl_instance: SupportedBLPropType, layout: bpy.types.UILayout, + text: str | None = None, slider: bool = False, ) -> None: """Draw this property to the given Blender `UILayout`.""" - layout.prop(bl_instance, self.bl_name, text=self.display_name, slider=slider) + layout.prop( + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + slider=slider, + ) #################### # - Selection diff --git a/blroots/bl_prop/types/int_prop.py b/blroots/bl_prop/types/int_prop.py index df8e179..c9749b7 100644 --- a/blroots/bl_prop/types/int_prop.py +++ b/blroots/bl_prop/types/int_prop.py @@ -111,12 +111,16 @@ class IntProp(BLProp, frozen=True): self, bl_instance: SupportedBLPropType, layout: bpy.types.UILayout, + text: str | None = None, slider: bool = False, ) -> None: """Draw this property to the given Blender `UILayout`.""" - ## TODO: - ## - Support for slider - layout.prop(bl_instance, self.bl_name, text=self.display_name, slider=slider) + layout.prop( + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + slider=slider, + ) #################### # - Selection diff --git a/blroots/bl_prop/types/path_prop.py b/blroots/bl_prop/types/path_prop.py index e69de29..b05da46 100644 --- a/blroots/bl_prop/types/path_prop.py +++ b/blroots/bl_prop/types/path_prop.py @@ -0,0 +1,130 @@ +# blroots +# Copyright (C) 2025 blroots Project Contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Implements `PathProp`.""" + +import builtins +import functools +import typing as typ +from pathlib import Path + +import bpy + +from ..bl_prop import BLProp, SupportedBLPropType + +PRIORITY = 20 + + +class PathProp(BLProp, frozen=True): + """A single constrained floating-point number. + + Parameters: + abs_min: The absolute minimum value of the floating point number. + abs_max: The absolute maximum value of the floating point number. + soft_min: The lowest value that can be set using the UI slider. + soft_max: The highest value that can be set using the UI slider. + float_prec: The number of decimal places displayed in the UI. + float_step: The size of each mouse-move increment when using the UI slider. + _Divide this integer by `100` for actual change per increment._ + """ + + default_value: Path + ## TODO: Use 'before' field validator to select a default value when None is specified, then an 'after' field validator to constrain it to abs_min and abs_max. + + # Constraints + is_file: bool = False + is_dir: bool = False + ## TODO: Validator that disallows both is_file and is_dir + + ## TODO: A "relative" constraint that uses '//' + + #################### + # - Type Support + #################### + @classmethod + def supports_type_hint(cls, field_type_hint: type) -> bool: + """Support only the explicit type `float` aka. `builtins.float`.""" + return field_type_hint is Path + + #################### + # - bpy.props Interface + #################### + @property + def bpy_prop_type(self) -> type[bpy.props.StringProperty]: + """The underlying `bpy.props` class backing this property.""" + return bpy.props.StringProperty # type: ignore[no-any-return] + + @functools.cached_property + def bpy_prop_kwargs(self) -> dict[str, typ.Any]: + """Keyword arguments to pass to `self.bpy_prop_type`.""" + kwargs: dict[str, typ.Any] = { + 'default': self.encode(self.default_value), + } + + if self.is_file: + kwargs |= {'subtype': 'FILE_PATH'} + + elif self.is_dir: + kwargs |= {'subtype': 'DIR_PATH'} + + else: + kwargs |= {'subtype': 'FILE_PATH'} + + return kwargs + + #################### + # - Encode/Decode + #################### + def encode(self, value: typ.Any) -> str: + """Encode a value that supports `float()` to a Blender float.""" + # Parse the Value + if isinstance(value, Path): + parsed_value = str(value.resolve()) + else: + msg = f"Value '{value}' must be a Path." + raise TypeError(msg) + + return parsed_value + + def decode(self, raw_bl_value: str) -> Path: + """Decode a Blender float by doing nothing.""" + return Path(bpy.path.abspath(raw_bl_value)) + + #################### + # - UI + #################### + def draw( + self, + bl_instance: SupportedBLPropType, + layout: bpy.types.UILayout, + text: str | None = None, + placeholder: str = '', + ) -> None: + """Draw this property to the given Blender `UILayout`.""" + layout.prop( + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + placeholder=placeholder, + ) + + #################### + # - Selection + #################### + @classmethod + def priority(cls) -> int: + """Priority with which to prefer this implementation over others compatible with the same type hint.""" + return PRIORITY diff --git a/blroots/bl_prop/types/str_enum_prop.py b/blroots/bl_prop/types/str_enum_prop.py new file mode 100644 index 0000000..b32c423 --- /dev/null +++ b/blroots/bl_prop/types/str_enum_prop.py @@ -0,0 +1,123 @@ +# blroots +# Copyright (C) 2025 blroots Project Contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Implements `StrEnumProp`.""" + +import enum +import functools +import typing as typ + +import bpy + +from ..bl_prop import BLProp, SupportedBLPropType + +PRIORITY = 5 + + +class StrEnumProp(BLProp, frozen=True): + """A static enumeration.""" + + default_value: enum.StrEnum + ## TODO: Use 'before' field validator to select a default value when None is specified, then an 'after' field validator to constrain it to abs_min and abs_max. + + #################### + # - Type Support + #################### + @classmethod + def supports_type_hint(cls, field_type_hint: type) -> bool: + """Support only the explicit type `float` aka. `builtins.float`.""" + return ( + issubclass(field_type_hint, enum.StrEnum) + and hasattr(field_type_hint, 'bl_display_name') + and hasattr(field_type_hint, 'bl_icon') + ) + + #################### + # - bpy.props Interface + #################### + @property + def bpy_prop_type(self) -> type[bpy.props.EnumProperty]: + """The underlying `bpy.props` class backing this property.""" + return bpy.props.EnumProperty # type: ignore[no-any-return] + + @functools.cached_property + def bpy_prop_kwargs(self) -> dict[str, typ.Any]: + """Keyword arguments to pass to `self.bpy_prop_type`.""" + kwargs: dict[str, typ.Any] = { + 'items': [ + ( + el.name, + el.bl_display_name, + el.bl_display_name, + el.bl_icon, + idx, + ) + for idx, el in enumerate(self.type_hint) + ], + 'default': self.encode(self.default_value), + ## TODO: Parse Enum.__doc__ to get description from the Attributes section. + } + + return kwargs + + #################### + # - Encode/Decode + #################### + def encode(self, value: typ.Any) -> enum.StrEnum: + """Encode a value that supports `float()` to a Blender float.""" + if isinstance(value, self.type_hint): + parsed_value = value + + elif isinstance(value, str): + parsed_value = getattr(self.type_hint, value, None) + if parsed_value is None: + msg = f"Object '{value}' could not be coerced to the enum {self.type_hint} during encoding." + raise ValueError(msg) + + return parsed_value.name + + def decode(self, raw_bl_value: str) -> str: + """Decode a Blender float by doing nothing.""" + parsed_value = getattr(self.type_hint, raw_bl_value, None) + if parsed_value is not None: + return raw_bl_value + + msg = f"String '{raw_bl_value}' could not be coerced to the enum {self.type_hint} during decoding." + raise ValueError(msg) + + #################### + # - UI + #################### + def draw( + self, + bl_instance: SupportedBLPropType, + layout: bpy.types.UILayout, + text: str | None = None, + ) -> None: + """Draw this property to the given Blender `UILayout`.""" + layout.prop( + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + ) + + #################### + # - Selection + #################### + @classmethod + def priority(cls) -> int: + """Priority with which to prefer this implementation over others compatible with the same type hint.""" + return PRIORITY diff --git a/blroots/bl_prop/types/str_prop.py b/blroots/bl_prop/types/str_prop.py index faad954..e45f806 100644 --- a/blroots/bl_prop/types/str_prop.py +++ b/blroots/bl_prop/types/str_prop.py @@ -28,16 +28,11 @@ PRIORITY = 20 class StrProp(BLProp, frozen=True): - """A single constrained floating-point number. + """A single constrained string. Parameters: - abs_min: The absolute minimum value of the floating point number. - abs_max: The absolute maximum value of the floating point number. - soft_min: The lowest value that can be set using the UI slider. - soft_max: The highest value that can be set using the UI slider. - float_prec: The number of decimal places displayed in the UI. - float_step: The size of each mouse-move increment when using the UI slider. - _Divide this integer by `100` for actual change per increment._ + is_secret: Whether the string should be considered a secret value. + Secrets display as dots in the interface, and aren't saved in the `.blend` on save. """ default_value: str @@ -101,11 +96,15 @@ class StrProp(BLProp, frozen=True): self, bl_instance: SupportedBLPropType, layout: bpy.types.UILayout, + text: str | None = None, placeholder: str = '', ) -> None: """Draw this property to the given Blender `UILayout`.""" layout.prop( - bl_instance, self.bl_name, text=self.display_name, placeholder=placeholder + bl_instance, + self.bl_name, + text=self.display_name if text is None else text, + placeholder=placeholder, ) #################### diff --git a/blroots/prefs.py b/blroots/prefs.py deleted file mode 100644 index 7319fee..0000000 --- a/blroots/prefs.py +++ /dev/null @@ -1,164 +0,0 @@ -import logging -import typing as typ -from pathlib import Path - -import bpy - -from blroots import gather -from blroots.bl_prop import BLField -from blroots.utils import bl_logger - - -#################### -# - User Preferences Mixin -#################### -def LoggingPrefs(blext_pkg: str) -> type[type]: - class _LoggingPrefs(bpy.types.AddonPreferences): - """Implements the `bl_logger` user preferences integration. - - Notes: - To check for the presence of this integration, use: - ```python - getattr(addon_prefs, 'enable_integration__bl_logger', False) - ``` - """ - - enable_integration__bl_logger: bool = True - - #################### - # - Properties - #################### - # Logging - ## File Logging - use_log_file: bool = BLField( - gather.blext_info(blext_pkg).use_log_file, - display_name='Log to File', - ) - # log_file_level: bl_logger.BLExtLogLevel = BLField( - # gather.blext_info(blext_pkg).log_file_level, - # display_name='File Log Level', - # ) - # log_file_path: Path = BLField( - # gather.blext_info(blext_pkg).log_file_path, - # display_name='File Log Path', - # ) - - ## Console Logging - use_log_console: bool = BLField( - gather.blext_info(blext_pkg).use_log_console, - display_name='Log to Console', - ) - # log_console_level: bl_logger.BLExtLogLevel = BLField( - # gather.blext_info(blext_pkg).log_console_level, - # display_name='Console Log Level', - # ) - - #################### - # - Logging - #################### - def conform_logger(self, logger: logging.Logger) -> None: - """Conform a logger's settings to the extension preferences. - - Parameters: - logger: The logger to configure using settings in the addon preferences. - """ - bl_logger.update_logger( - logger, - cb_console_handler=bl_logger.sensible_console_handler, - cb_file_handler=bl_logger.sensible_file_handler, - console_level=( - self.log_console_level.log_level if self.use_log_console else None - ), - file_path=(self.log_file_path if self.use_log_file else None), - file_level=( - self.log_file_level.log_level if self.use_log_file else None - ), - ) - - # @bl_event.run_on_event(bl_event.Event.BLClassRegistered) - # @bl_event.run_on_event( - # bl_event.Event.PropChanged, - # on_props={ - # 'use_log_file', - # 'log_file_level', - # 'log_file_path', - # 'use_log_console', - # 'log_console_level', - # }, - # ) - def conform_all_loggers_to_prefs(self) -> None: - """Called to reconfigure all loggers to match newly-altered addon preferences. - - This causes ex. changes to desired console log level to immediately be applied, but only the this addon's loggers. - - Parameters: - single_logger_to_setup: When set, only this logger will be setup. - Otherwise, **all addon loggers will be setup**. - """ - blext_pkg = self.bl_idname - for logger in bl_logger.all_blext_loggers(blext_pkg): - self.setup_logger(logger) - - #################### - # - UI - #################### - def draw_logging_prefs(self, layout: bpy.types.UILayout) -> None: - """Draw the standardized addon preferences into a UILayout. - - Examples: - Run this within the `draw()` method of the addon preferences class like so: - ```python - layout = self.layout - self.draw_standard_prefs(layout) - ``` - - Parameters: - context: The Blender context object. - """ - #################### - # - Logging - #################### - # Box: Log Level - box = layout.box() - row = box.row() - row.alignment = 'CENTER' - row.label(text='Logging') - - # Split - split = box.split(factor=0.5) - - ## Split Col: Console Logging - col = split.column() - row = col.row() - self.draw_blfield(row, 'use_log_console', toggle=True) - - row = col.row() - row.enabled = self.use_log_console - # self.draw_blfield(row, 'log_level_console') - - ## Split Col: File Logging - col = split.column() - row = col.row() - self.draw_blfield(row, 'use_log_file', toggle=True) - - row = col.row() - row.enabled = self.use_log_file - # self.draw_blfield(row, 'log_file_path') - - row = col.row() - row.enabled = self.use_log_file - # self.draw_blfield(row, 'log_level_file') - - return _LoggingPrefs - - -def SensiblePrefs(blext_pkg: str) -> type[type]: - class _SensiblePrefs(LoggingPrefs(blext_pkg)): - """Sensible set of baseline preferences fields, for use in extensions. - - Implements the following `blroots` integration: - - - `bl_logger`: Enable the integration of `blroots` logging with extension preferences. - """ - - return _SensiblePrefs diff --git a/blroots/utils/__init__.py b/blroots/utils/__init__.py index 5ef9514..4bf583a 100644 --- a/blroots/utils/__init__.py +++ b/blroots/utils/__init__.py @@ -1,4 +1,4 @@ -# blext +# blroots # Copyright (C) 2025 blext Project Contributors # # This program is free software: you can redistribute it and/or modify diff --git a/blroots/utils/bl_logger.py b/blroots/utils/bl_logger.py index e939220..5d49c19 100644 --- a/blroots/utils/bl_logger.py +++ b/blroots/utils/bl_logger.py @@ -23,11 +23,12 @@ import typing as typ from pathlib import Path import bpy +import pydantic as pyd import rich.console import rich.logging import rich.traceback -from blroots import gather +from blroots import bl_prop, gather #################### # - Configuration @@ -53,12 +54,12 @@ LogLevel: typ.TypeAlias = int class BLExtLogLevel(enum.StrEnum): """Log level of a Blender extension. - Attributes: - Debug: Low-level messages for debugging. - Info: Communications about routine operations. - Warning: Something that might be wrong. - Error: Something wrong that can be recovered from. - Critical: Something wrong that cannot be recovered from. + Attributes: + Debug: Low-level messages for debugging. + Info: Communications about routine operations. + Warning: Something that might be wrong. + Error: Something wrong that can be recovered from. + Critical: Something wrong that cannot be recovered from. """ Debug = enum.auto() @@ -292,3 +293,165 @@ def sensible_file_handler( file_handler.setFormatter(file_formatter) file_handler.setLevel(level) return file_handler + + +#################### +# - Logging Preferences +#################### +class BL_LoggingPrefs(bpy.types.PropertyGroup): + bl_props = frozenset( + { + 'use_log_file', + 'log_file_level', + 'log_file_path', + 'use_log_console', + 'log_console_level', + } + ) + + # File Logging + use_log_file: bool = BLField( + gather.blext_info(blext_pkg).use_log_file, + display_name='Log to File', + ) + log_file_level: BLExtLogLevel = BLField( + gather.blext_info(blext_pkg).log_file_level, + display_name='File Log Level', + ) + log_file_path: Path = BLField( + gather.blext_info(blext_pkg).log_file_path, + display_name='File Log Path', + ) + + # Console Logging + use_log_console: bool = BLField( + gather.blext_info(blext_pkg).use_log_console, + display_name='Log to Console', + is_file=True, + ) + log_console_level: BLExtLogLevel = BLField( + gather.blext_info(blext_pkg).log_console_level, + display_name='Console Log Level', + ) + + +class LoggingPrefs(pyd.BaseModel, frozen=True): + @classmethod + def bl_prop_group(cls) -> type[bpy.types.PropertyGroup]: + return BL_LoggingPrefs + + bl_idname: str + + # Console Logging + use_log_console: bool = gather.blext_info(blext_pkg).use_log_console + log_console_level: BLExtLogLevel = gather.blext_info(blext_pkg).log_console_level + + # File Logging + use_log_file: bool = gather.blext_info(blext_pkg).use_log_file + log_file_level: BLExtLogLevel = gather.blext_info(blext_pkg).log_file_level + log_file_path: Path = gather.blext_info(blext_pkg).log_file_path + + # Console Logging + use_log_console: bool = gather.blext_info(blext_pkg).use_log_console + log_console_level: BLExtLogLevel = gather.blext_info(blext_pkg).log_console_level + + #################### + # - Logging + #################### + def conform_logger(self, logger: logging.Logger) -> None: + """Conform a logger's settings to the extension preferences. + + Parameters: + logger: The logger to configure using settings in the addon preferences. + """ + update_logger( + logger, + cb_console_handler=sensible_console_handler, + cb_file_handler=sensible_file_handler, + console_level=( + self.log_console_level.log_level if self.use_log_console else None + ), + file_path=(self.log_file_path if self.use_log_file else None), + file_level=(self.log_file_level.log_level if self.use_log_file else None), + ) + + @bl_event.run_on_event(bl_event.Event.BLClassRegistered) + @bl_event.run_on_event( + bl_event.Event.PropChanged, + on_props={ + 'use_log_file', + 'log_file_level', + 'log_file_path', + 'use_log_console', + 'log_console_level', + }, + ) + @bl_event.propagate(source='parent', props={'bl_idname': 'blext_pkg'}) + def conform_all_loggers_to_prefs(self, blext_pkg: str) -> None: + ## TODO: Do some kind of event magic to get the blext_pkg in here frombl_idname of the parent bpy.types.AddonPreferences object containing this class. + """Called to reconfigure all loggers to match newly-altered addon preferences. + + This causes ex. changes to desired console log level to immediately be applied, but only the this addon's loggers. + + Parameters: + single_logger_to_setup: When set, only this logger will be setup. + Otherwise, **all addon loggers will be setup**. + """ + # Retrieve extension __package__ from bl_idname of bpy.types.AddonPreferences + for logger in all_blext_loggers(blext_pkg): + self.conform_logger(logger) + + #################### + # - UI + #################### + def draw( + self, + bl_instance: bl_instance.BLInstance, + layout: bpy.types.UILayout, + _: bpy.types.Context, + ) -> None: + """Draw the standardized addon preferences into a UILayout. + + Examples: + Run this within the `draw()` method of the addon preferences class like so: + ```python + layout = self.layout + self.draw_standard_prefs(layout) + ``` + + Parameters: + context: The Blender context object. + """ + #################### + # - Logging + #################### + # Box: Log Level + box = layout.box() + row = box.row() + row.alignment = 'CENTER' + row.label(text='Logging') + + # Split + split = box.split(factor=0.5) + + ## Split Col: Console Logging + col = split.column() + row = col.row() + bl_instance.draw_blprop(row, 'use_log_console', toggle=True) + + row = col.row() + row.enabled = self.use_log_console + bl_instance.draw_blprop(row, 'log_console_level', text='Level') + + ## Split Col: File Logging + col = split.column() + row = col.row() + bl_instance.draw_blprop(row, 'use_log_file', toggle=True) + + row = col.row() + row.enabled = self.use_log_file + bl_instance.draw_blprop(row, 'log_file_level', text='Level') + + row = col.row() + row.enabled = self.use_log_file + bl_instance.draw_blprop(row, 'log_file_path', text='') diff --git a/examples/simple/blroots_simple_example/contracts/operator_types.py b/examples/simple/blroots_simple_example/contracts.py similarity index 75% rename from examples/simple/blroots_simple_example/contracts/operator_types.py rename to examples/simple/blroots_simple_example/contracts.py index c5471cc..911eafd 100644 --- a/examples/simple/blroots_simple_example/contracts/operator_types.py +++ b/examples/simple/blroots_simple_example/contracts.py @@ -1,5 +1,5 @@ -# blext -# Copyright (C) 2025 blext Project Contributors +# blroots +# Copyright (C) 2025 blroots Project Contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -13,9 +13,6 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - -"""Provides identifiers for Blender operators defined by this addon.""" - import enum import blroots as blr @@ -23,7 +20,17 @@ import blroots as blr BLEXT_INFO = blr.blext_info(__name__) +class Icon(enum.StrEnum): + """Identifiers for icons used throughout this addon.""" + + class OperatorType(enum.StrEnum): """Identifiers for addon-defined `bpy.types.Operator`.""" SimpleOperator = BLEXT_INFO.operator_name('simple_operator') + + +class PanelType(enum.StrEnum): + """Identifiers for addon-defined `bpy.types.Panel`.""" + + SimplePanel = BLEXT_INFO.panel_name('simple_panel') diff --git a/examples/simple/blroots_simple_example/contracts/__init__.py b/examples/simple/blroots_simple_example/contracts/__init__.py deleted file mode 100644 index 94347ad..0000000 --- a/examples/simple/blroots_simple_example/contracts/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -# blext -# Copyright (C) 2025 blext Project Contributors -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Independent constants and types, which represent a kind of 'social contract' governing communication between all components of the addon.""" - -from .icons import Icon -from .operator_types import ( - OperatorType, -) -from .panel_types import ( - PanelType, -) - -__all__ = [ - 'Icon', - 'OperatorType', - 'PanelType', -] diff --git a/examples/simple/blroots_simple_example/contracts/icons.py b/examples/simple/blroots_simple_example/contracts/icons.py deleted file mode 100644 index ef2be61..0000000 --- a/examples/simple/blroots_simple_example/contracts/icons.py +++ /dev/null @@ -1,23 +0,0 @@ -# blext -# Copyright (C) 2025 blext Project Contributors -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Provides an enum that semantically constrains the use of icons throughout the addon.""" - -import enum - - -class Icon(enum.StrEnum): - """Identifiers for icons used throughout this addon.""" diff --git a/examples/simple/blroots_simple_example/contracts/panel_types.py b/examples/simple/blroots_simple_example/contracts/panel_types.py deleted file mode 100644 index 7f6ec94..0000000 --- a/examples/simple/blroots_simple_example/contracts/panel_types.py +++ /dev/null @@ -1,29 +0,0 @@ -# blext -# Copyright (C) 2025 blext Project Contributors -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -"""Provides identifiers for Blender panels defined by this addon.""" - -import enum - -import blroots as blr - -BLEXT_INFO = blr.blext_info(__name__) - - -class PanelType(enum.StrEnum): - """Identifiers for addon-defined `bpy.types.Panel`.""" - - SimplePanel = BLEXT_INFO.panel_name('simple_panel') diff --git a/examples/simple/blroots_simple_example/operators/simple_operator.py b/examples/simple/blroots_simple_example/operators/simple_operator.py index d95b774..098e5c4 100644 --- a/examples/simple/blroots_simple_example/operators/simple_operator.py +++ b/examples/simple/blroots_simple_example/operators/simple_operator.py @@ -40,10 +40,9 @@ class SimpleOperator(bpy.types.Operator): def execute(self, context: bpy.types.Context) -> blr.typ.BLOperatorStatus: """Display a simple message on execution.""" - scene = context.scene self.report( {'INFO'}, - f'The simple integer was set to {scene.simple_integer}.', + f'The simple integer was set to {context.scene.simple_integer}.', ) return {'FINISHED'} @@ -53,5 +52,3 @@ class SimpleOperator(bpy.types.Operator): # - Blender Registration #################### BL_REGISTER = [SimpleOperator] -BL_HANDLERS = None -BL_KEYMAP_ITEMS = None diff --git a/examples/simple/blroots_simple_example/panels/simple_panel.py b/examples/simple/blroots_simple_example/panels/simple_panel.py index cb31b0a..25c10a3 100644 --- a/examples/simple/blroots_simple_example/panels/simple_panel.py +++ b/examples/simple/blroots_simple_example/panels/simple_panel.py @@ -52,7 +52,7 @@ class SimplePanel(bpy.types.Panel): layout = self.layout # Property - scene.draw_blfield(layout, 'simple_integer') + scene.draw_blprop(layout, 'simple_integer') # Operator layout.label(text='Look ma, so many hands!') @@ -63,5 +63,3 @@ class SimplePanel(bpy.types.Panel): # - Blender Registration #################### BL_REGISTER = [SimplePanel] -BL_HANDLERS = None -BL_KEYMAP_ITEMS = None diff --git a/examples/simple/blroots_simple_example/preferences.py b/examples/simple/blroots_simple_example/preferences.py index e58955a..d6a1d9a 100644 --- a/examples/simple/blroots_simple_example/preferences.py +++ b/examples/simple/blroots_simple_example/preferences.py @@ -20,7 +20,7 @@ import blroots as blr import bpy -class BLExtPrefs(blr.prefs.SensiblePrefs(__package__)): +class BLExtPrefs(bpy.types.AddonPreferences): """Manages user preferences and settings. Attributes: @@ -33,6 +33,16 @@ class BLExtPrefs(blr.prefs.SensiblePrefs(__package__)): bl_idname = __package__ + # Logging + logging_prefs: blr.utils.bl_logger.LoggingPrefs = blr.BLField( + blr.gather.blext_info.logging_prefs, + display_name='Logging Settings', + ) + ## Methods on this class should include: + ## - conform_logger + ## - conform_all_loggers_to_prefs + ## - draw: Draw these prefs into a layout. + #################### # - UI #################### @@ -46,8 +56,7 @@ class BLExtPrefs(blr.prefs.SensiblePrefs(__package__)): context: The Blender context object. """ layout = self.layout - - self.draw_logging_prefs(layout) + self.draw_blprop(layout, 'logging_prefs') #################### diff --git a/examples/simple/uv.lock b/examples/simple/uv.lock index 130eeff..98932aa 100644 --- a/examples/simple/uv.lock +++ b/examples/simple/uv.lock @@ -146,7 +146,32 @@ dev = [ ] [[package]] -name = "blext-simple-example" +name = "blroots" +version = "0.1.0" +source = { editable = "../../" } +dependencies = [ + { name = "griffe" }, + { name = "pydantic" }, + { name = "rich" }, +] + +[package.metadata] +requires-dist = [ + { name = "griffe", specifier = ">=1.5.5" }, + { name = "pydantic", specifier = ">=2.10.5" }, + { name = "rich", specifier = ">=13.9.4" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "commitizen", specifier = ">=4.1.0" }, + { name = "mypy", specifier = ">=1.14.1" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "ruff", specifier = ">=0.9.1" }, +] + +[[package]] +name = "blroots-simple-example" version = "0.1.0" source = { virtual = "." } dependencies = [ @@ -178,31 +203,6 @@ dev = [ { name = "typer", specifier = ">=0.15.1" }, ] -[[package]] -name = "blroots" -version = "0.1.0" -source = { editable = "../../" } -dependencies = [ - { name = "griffe" }, - { name = "pydantic" }, - { name = "rich" }, -] - -[package.metadata] -requires-dist = [ - { name = "griffe", specifier = ">=1.5.5" }, - { name = "pydantic", specifier = ">=2.10.5" }, - { name = "rich", specifier = ">=13.9.4" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "commitizen", specifier = ">=4.1.0" }, - { name = "mypy", specifier = ">=1.14.1" }, - { name = "pytest", specifier = ">=8.3.4" }, - { name = "ruff", specifier = ">=0.9.1" }, -] - [[package]] name = "click" version = "8.1.8" diff --git a/pyproject.toml b/pyproject.toml index 3167141..c34f84a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ Homepage = "https://codeberg.org/so-rose/blroots" [dependency-groups] dev = [ + "bpy~=4.3.0", "commitizen>=4.1.0", "mypy>=1.14.1", "pytest>=8.3.4", @@ -155,33 +156,23 @@ quote-style = "single" indent-style = "tab" docstring-code-format = false + #################### -# - Tooling: MyPy +# - Tool: basedpyright #################### -[tool.mypy] -python_version = '3.11' -python_executable="./.venv/bin/python" +[tool.basedpyright] +defineConstant = { DEBUG = true } -warn_redundant_casts = true -warn_unused_ignores = true -warn_return_any = true +#include = ["blext"] +#strict = ["blext"] -strict_optional = true -no_implicit_optional = true +reportMissingImports = "error" +reportMissingTypeStubs = true -disallow_subclassing_any = false -disallow_any_generics = true -disallow_untyped_calls = true -disallow_incomplete_defs = true - -check_untyped_defs = true -disallow_untyped_decorators = true - -ignore_missing_imports = true - -plugins = [ -# 'pydantic.mypy', -# 'typing_protocol_intersection.mypy_plugin', +executionEnvironments = [ + { root = "blroots", pythonVersion = "3.11", extraPaths = [ ".venv/lib/python3.11/site-packages", ".venv/lib/python3.11/site-packages/bpy/4.3/scripts/modules" ] }, + #{ root = ".", pythonVersion = "3.11", extraPaths = [ ".venv/lib/python3.11/site-packages" ] }, + { root = "examples/simple", pythonVersion = "3.11", extraPaths = [ ".venv/lib/python3.11/site-packages", ".venv/lib/python3.11/site-packages/bpy/4.3/scripts/modules" ] }, ] diff --git a/uv.lock b/uv.lock index 0a8aff5..cc0c327 100644 --- a/uv.lock +++ b/uv.lock @@ -31,6 +31,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "bpy" }, { name = "commitizen" }, { name = "mypy" }, { name = "pytest" }, @@ -46,12 +47,84 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "bpy", specifier = ">=4.3.0" }, { name = "commitizen", specifier = ">=4.1.0" }, { name = "mypy", specifier = ">=1.14.1" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "ruff", specifier = ">=0.9.1" }, ] +[[package]] +name = "bpy" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cython" }, + { name = "numpy" }, + { name = "requests" }, + { name = "zstandard" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fc/5902fa508a13f4f1669bc2e2cd8f37adf1caeeed0a344f627c0ffc3bdc40/bpy-4.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82c3486e9762e8cfce4bbc1d6752f5625da8515f6b217e02810350627d693897", size = 217245993 }, + { url = "https://files.pythonhosted.org/packages/81/39/4e763b150ff05412ab25d5eae91da7f0ade474b5d66509b39a67066d35ec/bpy-4.3.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:490aa94699664a99eee755047485a3ad70470fc6267c55a73459660f52299ade", size = 230465428 }, + { url = "https://files.pythonhosted.org/packages/9c/4d/897bf3c247d7c6a4e75397f4c5dd5d201fb147981d2271ac35ac955959ab/bpy-4.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1620199392a7e7aab58672aa47c50b3c5b1e4d39d46af5a7f9bbe2af561ea942", size = 377434308 }, + { url = "https://files.pythonhosted.org/packages/7f/da/c255b626cab58c69c49f4a65d0410b36aa1086dc63862d1c3de653f0a5a0/bpy-4.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a7b2c516aa7a95c31fcaae8842addd07fa86c1ce4768e88a34b480d17e2e93a", size = 333159335 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + [[package]] name = "charset-normalizer" version = "3.4.1" @@ -130,6 +203,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/f7/7f70adfbf3553ffdbe391eaacde72b21dbc1b4226ae56ca32e8ded1bf70b/commitizen-4.1.0-py3-none-any.whl", hash = "sha256:2e6c5fbd442cab4bcc5a04bc86ef2196ef84bcf611317d6c596e87f5bb4c09f5", size = 72282 }, ] +[[package]] +name = "cython" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/25/886e197c97a4b8e254173002cdc141441e878ff29aaa7d9ba560cd6e4866/cython-3.0.12.tar.gz", hash = "sha256:b988bb297ce76c671e28c97d017b95411010f7c77fa6623dd0bb47eed1aee1bc", size = 2757617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/60/3d27abd940f7b80a6aeb69dc093a892f04828e1dd0b243dd81ff87d7b0e9/Cython-3.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feb86122a823937cc06e4c029d80ff69f082ebb0b959ab52a5af6cdd271c5dc3", size = 3277430 }, + { url = "https://files.pythonhosted.org/packages/c7/49/f17b0541b317d11f1d021a580643ee2481685157cded92efb32e2fb4daef/Cython-3.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfdbea486e702c328338314adb8e80f5f9741f06a0ae83aaec7463bc166d12e8", size = 3444055 }, + { url = "https://files.pythonhosted.org/packages/6b/7f/c57791ba6a1c934b6f1ab51371e894e3b4bfde0bc35e50046c8754a9d215/Cython-3.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563de1728c8e48869d2380a1b76bbc1b1b1d01aba948480d68c1d05e52d20c92", size = 3597874 }, + { url = "https://files.pythonhosted.org/packages/23/24/803a0db3681b3a2ef65a4bebab201e5ae4aef5e6127ae03683476a573aa9/Cython-3.0.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398d4576c1e1f6316282aa0b4a55139254fbed965cba7813e6d9900d3092b128", size = 3644129 }, + { url = "https://files.pythonhosted.org/packages/27/13/9b53ba8336e083ece441af8d6d182b8ca83ad523e87c07b3190af379ebc3/Cython-3.0.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e5eadef80143026944ea8f9904715a008f5108d1d644a89f63094cc37351e73", size = 3504936 }, + { url = "https://files.pythonhosted.org/packages/a9/d2/d11104be6992a9fe256860cae6d1a79f7dcf3bdb12ae00116fac591f677d/Cython-3.0.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a93cbda00a5451175b97dea5a9440a3fcee9e54b4cba7a7dbcba9a764b22aec", size = 3713066 }, + { url = "https://files.pythonhosted.org/packages/d9/8c/1fe49135296efa3f460c760a4297f6a5b387f3e69ac5c9dcdbd620295ab3/Cython-3.0.12-cp311-cp311-win32.whl", hash = "sha256:3109e1d44425a2639e9a677b66cd7711721a5b606b65867cb2d8ef7a97e2237b", size = 2579935 }, + { url = "https://files.pythonhosted.org/packages/02/4e/5ac0b5b9a239cd3fdae187dda8ff06b0b812f671e2501bf253712278f0ac/Cython-3.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:d4b70fc339adba1e2111b074ee6119fe9fd6072c957d8597bce9a0dd1c3c6784", size = 2787337 }, + { url = "https://files.pythonhosted.org/packages/e6/6c/3be501a6520a93449b1e7e6f63e598ec56f3b5d1bc7ad14167c72a22ddf7/Cython-3.0.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe030d4a00afb2844f5f70896b7f2a1a0d7da09bf3aa3d884cbe5f73fff5d310", size = 3311717 }, + { url = "https://files.pythonhosted.org/packages/ee/ab/adfeb22c85491de18ae10932165edd5b6f01e4c5e3e363638759d1235015/Cython-3.0.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7fec4f052b8fe173fe70eae75091389955b9a23d5cec3d576d21c5913b49d47", size = 3344337 }, + { url = "https://files.pythonhosted.org/packages/0d/72/743730d7c46b4c85abefb93187cbbcb7aae8de288d7722b990db3d13499e/Cython-3.0.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0faa5e39e5c8cdf6f9c3b1c3f24972826e45911e7f5b99cf99453fca5432f45e", size = 3517692 }, + { url = "https://files.pythonhosted.org/packages/09/a1/29a4759a02661f8c8e6b703f62bfbc8285337e6918cc90f55dc0fadb5eb3/Cython-3.0.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d53de996ed340e9ab0fc85a88aaa8932f2591a2746e1ab1c06e262bd4ec4be7", size = 3577057 }, + { url = "https://files.pythonhosted.org/packages/d6/f8/03d74e98901a7cc2f21f95231b07dd54ec2f69477319bac268b3816fc3a8/Cython-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea3a0e19ab77266c738aa110684a753a04da4e709472cadeff487133354d6ab8", size = 3396493 }, + { url = "https://files.pythonhosted.org/packages/50/ea/ac33c5f54f980dbc23dd8f1d5c51afeef26e15ac1a66388e4b8195af83b7/Cython-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151082884be468f2f405645858a857298ac7f7592729e5b54788b5c572717ba", size = 3603859 }, + { url = "https://files.pythonhosted.org/packages/a2/4e/91fc1d6b5e678dcf2d1ecd8dce45b014b4b60d2044d376355c605831c873/Cython-3.0.12-cp312-cp312-win32.whl", hash = "sha256:3083465749911ac3b2ce001b6bf17f404ac9dd35d8b08469d19dc7e717f5877a", size = 2610428 }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a7fdec227b9f0bb07edbeb016c7b18ed6a8e6ce884d08b2e397cda2c0168/Cython-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:c0b91c7ebace030dd558ea28730de8c580680b50768e5af66db2904a3716c3e3", size = 2794755 }, + { url = "https://files.pythonhosted.org/packages/67/ad/550ddcb8b5a5d9949fe6606595cce36984c1d42309f1e04af98f5933a7ea/Cython-3.0.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4ee6f1ea1bead8e6cbc4e64571505b5d8dbdb3b58e679d31f3a84160cebf1a1a", size = 3393574 }, + { url = "https://files.pythonhosted.org/packages/34/de/ade0a80bea17197662e23d39d3d3fbf89e9e99e6ad91fd95ab87120edb3a/Cython-3.0.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57aefa6d3341109e46ec1a13e3a763aaa2cbeb14e82af2485b318194be1d9170", size = 3367198 }, + { url = "https://files.pythonhosted.org/packages/a8/30/7f48207ea13dab46604db0dd388e807d53513ba6ad1c34462892072f8f8c/Cython-3.0.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:879ae9023958d63c0675015369384642d0afb9c9d1f3473df9186c42f7a9d265", size = 3535849 }, + { url = "https://files.pythonhosted.org/packages/81/ab/f61c79fa14bd433a7dfd1548c5e00d9bd18b557c2f836aaece4fb1b22f34/Cython-3.0.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36fcd584dae547de6f095500a380f4a0cce72b7a7e409e9ff03cb9beed6ac7a1", size = 3559079 }, + { url = "https://files.pythonhosted.org/packages/d0/d1/1dbf17061229ccd35d5c0eed659fab60c2e50d2eadfa2a5729e753b6f4d0/Cython-3.0.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62b79dcc0de49efe9e84b9d0e2ae0a6fc9b14691a65565da727aa2e2e63c6a28", size = 3436649 }, + { url = "https://files.pythonhosted.org/packages/2d/d4/9ce42fff6de5550f870cdde9a1482d69ea66a1249a88fa0d0df9adebfb1a/Cython-3.0.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4aa255781b093a8401109d8f2104bbb2e52de7639d5896aefafddc85c30e0894", size = 3644025 }, + { url = "https://files.pythonhosted.org/packages/e3/89/b0c847f9df92af3ef11281b6811c000bd6f8ce0db02e4374397f8d67f829/Cython-3.0.12-cp313-cp313-win32.whl", hash = "sha256:77d48f2d4bab9fe1236eb753d18f03e8b2619af5b6f05d51df0532a92dfb38ab", size = 2604911 }, + { url = "https://files.pythonhosted.org/packages/a6/5f/bbfaf2b5f7bf78854ecbc82f8473a3892ae5580e0c5bd0d4a82580b39ed3/Cython-3.0.12-cp313-cp313-win_amd64.whl", hash = "sha256:86c304b20bd57c727c7357e90d5ba1a2b6f1c45492de2373814d7745ef2e63b4", size = 2786786 }, + { url = "https://files.pythonhosted.org/packages/27/6b/7c87867d255cbce8167ed99fc65635e9395d2af0f0c915428f5b17ec412d/Cython-3.0.12-py2.py3-none-any.whl", hash = "sha256:0038c9bae46c459669390e53a1ec115f8096b2e4647ae007ff1bf4e6dee92806", size = 1171640 }, +] + [[package]] name = "decli" version = "0.6.2" @@ -151,6 +257,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/88/52c9422bc853cd7c2b6122090e887d17b5fad29b67f930e4277c9c557357/griffe-1.5.5-py3-none-any.whl", hash = "sha256:2761b1e8876c6f1f9ab1af274df93ea6bbadd65090de5f38f4cb5cc84897c7dd", size = 128221 }, ] +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -281,6 +396,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, ] +[[package]] +name = "numpy" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, + { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, + { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, + { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, + { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, + { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, + { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, + { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, + { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, + { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, + { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, + { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, + { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, + { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, + { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, + { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, + { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, + { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, + { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, + { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, + { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, + { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, + { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, + { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, + { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, + { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, + { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, +] + [[package]] name = "packaging" version = "24.2" @@ -311,6 +474,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, ] +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + [[package]] name = "pydantic" version = "2.10.5" @@ -449,6 +621,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/3f/11dd4cd4f39e05128bfd20138faea57bec56f9ffba6185d276e3107ba5b2/questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec", size = 36747 }, ] +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + [[package]] name = "rich" version = "13.9.4" @@ -514,6 +701,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + [[package]] name = "wcwidth" version = "0.2.13" @@ -522,3 +718,62 @@ sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, ] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699 }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681 }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328 }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955 }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944 }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927 }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544 }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094 }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440 }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091 }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682 }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707 }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792 }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586 }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420 }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713 }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459 }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707 }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545 }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533 }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510 }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973 }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968 }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179 }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577 }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899 }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964 }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398 }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313 }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877 }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595 }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975 }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448 }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269 }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228 }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891 }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946 }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994 }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681 }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239 }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149 }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392 }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299 }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862 }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578 }, +]