diff --git a/src/speckle_automate/automation_context.py b/src/speckle_automate/automation_context.py index efac6739..d42ead13 100644 --- a/src/speckle_automate/automation_context.py +++ b/src/speckle_automate/automation_context.py @@ -1,3 +1,5 @@ +# ignoring "line too long" check from linter +# ruff: noqa: E501 """This module provides an abstraction layer above the Speckle Automate runtime.""" import time diff --git a/src/specklepy/objects/geometry/__init__.py b/src/specklepy/objects/geometry/__init__.py index 58d8f9f0..5c9a3e36 100644 --- a/src/specklepy/objects/geometry/__init__.py +++ b/src/specklepy/objects/geometry/__init__.py @@ -1,10 +1,34 @@ -from specklepy.objects.geometry.arc import Arc -from specklepy.objects.geometry.line import Line -from specklepy.objects.geometry.mesh import Mesh -from specklepy.objects.geometry.plane import Plane -from specklepy.objects.geometry.point import Point -from specklepy.objects.geometry.polyline import Polyline -from specklepy.objects.geometry.vector import Vector +from .arc import Arc +from .box import Box +from .circle import Circle +from .control_point import ControlPoint +from .ellipse import Ellipse +from .line import Line +from .mesh import Mesh +from .plane import Plane +from .point import Point +from .point_cloud import PointCloud +from .polycurve import Polycurve +from .polyline import Polyline +from .spiral import Spiral +from .surface import Surface +from .vector import Vector # re-export them at the geometry package level -__all__ = ["Arc", "Line", "Mesh", "Plane", "Point", "Polyline", "Vector"] +__all__ = [ + "Arc", + "Line", + "Mesh", + "Plane", + "Point", + "Polyline", + "Vector", + "Box", + "Circle", + "ControlPoint", + "Ellipse", + "PointCloud", + "Polycurve", + "Spiral", + "Surface", +] diff --git a/src/specklepy/objects/geometry/box.py b/src/specklepy/objects/geometry/box.py new file mode 100644 index 00000000..775a0d64 --- /dev/null +++ b/src/specklepy/objects/geometry/box.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass + +from specklepy.objects.base import Base +from specklepy.objects.geometry.plane import Plane +from specklepy.objects.interfaces import IHasArea, IHasUnits, IHasVolume +from specklepy.objects.primitive import Interval + + +@dataclass(kw_only=True) +class Box(Base, IHasUnits, IHasArea, IHasVolume, speckle_type="Objects.Geometry.Box"): + """ + a 3-dimensional box oriented on a plane + """ + + basePlane: Plane + xSize: Interval + ySize: Interval + zSize: Interval + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"basePlane: {self.basePlane}, " + f"xSize: {self.xSize}, " + f"ySize: {self.ySize}, " + f"zSize: {self.zSize}, " + f"units: {self.units})" + ) + + @property + def area(self) -> float: + return 2 * ( + self.xSize.length * self.ySize.length + + self.xSize.length * self.zSize.length + + self.ySize.length * self.zSize.length + ) + + @property + def volume(self) -> float: + return self.xSize.length * self.ySize.length * self.zSize.length diff --git a/src/specklepy/objects/geometry/circle.py b/src/specklepy/objects/geometry/circle.py new file mode 100644 index 00000000..c6f4145c --- /dev/null +++ b/src/specklepy/objects/geometry/circle.py @@ -0,0 +1,35 @@ +import math +from dataclasses import dataclass + +from specklepy.objects.base import Base +from specklepy.objects.geometry.plane import Plane +from specklepy.objects.geometry.point import Point +from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits + + +@dataclass(kw_only=True) +class Circle(Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Circle"): + """ + a circular curve based on a plane + """ + + plane: Plane + center: Point + radius: float + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"plane: {self.plane}, " + f"center: {self.center}, " + f"radius: {self.radius}, " + f"units: {self.units})" + ) + + @property + def length(self) -> float: + return 2 * math.pi * self.radius + + @property + def area(self) -> float: + return math.pi * self.radius**2 diff --git a/src/specklepy/objects/geometry/control_point.py b/src/specklepy/objects/geometry/control_point.py new file mode 100644 index 00000000..e26ebb8f --- /dev/null +++ b/src/specklepy/objects/geometry/control_point.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass + +from specklepy.objects.geometry.point import Point + + +@dataclass(kw_only=True) +class ControlPoint(Point, speckle_type="Objects.Geometry.ControlPoint"): + """ + a single 3-dimensional point with weight + """ + + weight: float + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"x: {self.x}, " + f"y: {self.y}, " + f"z: {self.z}, " + f"weight: {self.weight}, " + f"units: {self.units})" + ) diff --git a/src/specklepy/objects/geometry/ellipse.py b/src/specklepy/objects/geometry/ellipse.py new file mode 100644 index 00000000..afa1efdf --- /dev/null +++ b/src/specklepy/objects/geometry/ellipse.py @@ -0,0 +1,34 @@ +from dataclasses import dataclass + +from specklepy.objects.base import Base +from specklepy.objects.geometry.plane import Plane +from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits + + +@dataclass(kw_only=True) +class Ellipse( + Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Ellipse" +): + """ + an ellipse + """ + + plane: Plane + first_radius: float + second_radius: float + + @property + def length(self) -> float: + return self.__dict__.get("_length", 0.0) + + @length.setter + def length(self, value: float) -> None: + self.__dict__["_length"] = value + + @property + def area(self) -> float: + return self.__dict__.get("_area", 0.0) + + @area.setter + def area(self, value: float) -> None: + self.__dict__["_area"] = value diff --git a/src/specklepy/objects/geometry/mesh.py b/src/specklepy/objects/geometry/mesh.py index 05b94792..42020cd9 100644 --- a/src/specklepy/objects/geometry/mesh.py +++ b/src/specklepy/objects/geometry/mesh.py @@ -23,7 +23,8 @@ class Mesh( serialize_ignore={"vertices_count", "texture_coordinates_count"}, ): """ - a 3D mesh consisting of vertices and faces with optional colors and texture coordinates + a 3D mesh consisting of vertices and faces + with optional colors and texture coordinates """ vertices: List[float] diff --git a/src/specklepy/objects/geometry/point.py b/src/specklepy/objects/geometry/point.py index c1a6f342..f0157a60 100644 --- a/src/specklepy/objects/geometry/point.py +++ b/src/specklepy/objects/geometry/point.py @@ -15,7 +15,13 @@ class Point(Base, IHasUnits, speckle_type="Objects.Geometry.Point"): z: float def __repr__(self) -> str: - return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})" + return ( + f"{self.__class__.__name__}(" + f"x: {self.x}, " + f"y: {self.y}, " + f"z: {self.z}, " + f"units: {self.units})" + ) def distance_to(self, other: "Point") -> float: """ diff --git a/src/specklepy/objects/geometry/point_cloud.py b/src/specklepy/objects/geometry/point_cloud.py new file mode 100644 index 00000000..cffc235f --- /dev/null +++ b/src/specklepy/objects/geometry/point_cloud.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from typing import List + +from specklepy.objects.base import Base +from specklepy.objects.geometry.point import Point +from specklepy.objects.interfaces import IHasUnits + + +@dataclass(kw_only=True) +class PointCloud(Base, IHasUnits, speckle_type="Objects.Geometry.PointCloud"): + """ + a collection of 3-dimensional points + """ + + points: List[Point] + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"points: {len(self.points)}, " + f"units: {self.units})" + ) + + # sizes and colors could be added in the future diff --git a/src/specklepy/objects/geometry/polycurve.py b/src/specklepy/objects/geometry/polycurve.py new file mode 100644 index 00000000..6f0e13ce --- /dev/null +++ b/src/specklepy/objects/geometry/polycurve.py @@ -0,0 +1,97 @@ +from dataclasses import dataclass, field +from typing import List + +from specklepy.objects.base import Base +from specklepy.objects.geometry.line import Line +from specklepy.objects.geometry.point import Point +from specklepy.objects.geometry.polyline import Polyline +from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits + + +@dataclass(kw_only=True) +class Polycurve( + Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Polycurve" +): + """ + a curve that is comprised of multiple curves connected + """ + + segments: List[ICurve] = field(default_factory=list) + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"segments: {len(self.segments)}, " + f"closed: {self.is_closed()}, " + f"units: {self.units})" + ) + + def is_closed(self, tolerance: float = 1e-6) -> bool: + """ + checks if the polycurve is closed + (comparing start of first segment to end of last segment) + """ + if len(self.segments) < 1: + return False + + first_segment = self.segments[0] + last_segment = self.segments[-1] + + if not (hasattr(first_segment, "start") and hasattr(last_segment, "end")): + return False + + start_pt = first_segment.start + end_pt = last_segment.end + + if not (isinstance(start_pt, Point) and isinstance(end_pt, Point)): + return False + + return start_pt.distance_to(end_pt) <= tolerance + + @property + def length(self) -> float: + return self.__dict__.get("_length", 0.0) + + @length.setter + def length(self, value: float) -> None: + self.__dict__["_length"] = value + + def calculate_length(self) -> float: + """ + calculate total length of all segments + """ + total_length = 0.0 + for segment in self.segments: + if hasattr(segment, "length"): + total_length += segment.length + return total_length + + @property + def area(self) -> float: + return self.__dict__.get("_area", 0.0) + + @area.setter + def area(self, value: float) -> None: + self.__dict__["_area"] = value + + @classmethod + def from_polyline(cls, polyline: Polyline) -> "Polycurve": + """ + constructs a new polycurve instance from an existing polyline curve + """ + polycurve = cls(units=polyline.units) + points = polyline.get_points() + for i in range(len(points) - 1): + line = Line(start=points[i], end=points[i + 1], units=polyline.units) + polycurve.segments.append(line) + + if polyline.is_closed(): + line = Line(start=points[-1], end=points[0], units=polyline.units) + polycurve.segments.append(line) + + if hasattr(polyline, "_length"): + polycurve.length = polyline.length + if hasattr(polyline, "_area"): + polycurve.area = polyline.area + + return polycurve diff --git a/src/specklepy/objects/geometry/spiral.py b/src/specklepy/objects/geometry/spiral.py new file mode 100644 index 00000000..83882e95 --- /dev/null +++ b/src/specklepy/objects/geometry/spiral.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass + +from specklepy.objects.base import Base +from specklepy.objects.geometry.plane import Plane +from specklepy.objects.geometry.point import Point +from specklepy.objects.geometry.vector import Vector +from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits + + +@dataclass(kw_only=True) +class Spiral(Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Spiral"): + """ + a spiral + """ + + start_point: Point + end_point: Point + plane: Plane # plane with origin at spiral center + turns: float # total angle of spiral. + pitch: float + pitch_axis: Vector + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"start_point: {self.start_point}, " + f"end_point: {self.end_point}, " + f"plane: {self.plane}, " + f"turns: {self.turns}, " + f"pitch: {self.pitch}, " + f"pitch_axis: {self.pitch_axis}, " + f"units: {self.units})" + ) + + @property + def length(self) -> float: + return self.__dict__.get("_length", 0.0) + + @length.setter + def length(self, value: float) -> None: + self.__dict__["_length"] = value + + @property + def area(self) -> float: + return self.__dict__.get("_area", 0.0) + + @area.setter + def area(self, value: float) -> None: + self.__dict__["_area"] = value diff --git a/src/specklepy/objects/geometry/surface.py b/src/specklepy/objects/geometry/surface.py new file mode 100644 index 00000000..ba59b2dc --- /dev/null +++ b/src/specklepy/objects/geometry/surface.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass +from typing import List + +from specklepy.objects.base import Base +from specklepy.objects.geometry.control_point import ControlPoint +from specklepy.objects.interfaces import IHasArea, IHasUnits +from specklepy.objects.primitive import Interval + + +@dataclass(kw_only=True) +class Surface(Base, IHasArea, IHasUnits, speckle_type="Objects.Geometry.Surface"): + """ + a surface in nurbs form + """ + + degreeU: int + degreeV: int + rational: bool + pointData: List[float] + countU: int + countV: int + knotsU: List[float] + knotsV: List[float] + domainU: Interval + domainV: Interval + closedU: bool + closedV: bool + + @property + def area(self) -> float: + return self.__dict__.get("_area", 0.0) + + @area.setter + def area(self, value: float) -> None: + self.__dict__["_area"] = value + + def get_control_points(self) -> List: + """ + gets the control points of this surface + """ + + matrix = [[] for _ in range(self.countU)] + + for i in range(0, len(self.pointData), 4): + u_index = i // (self.countV * 4) + x, y, z, w = self.pointData[i : i + 4] + matrix[u_index].append( + ControlPoint(x=x, y=y, z=z, weight=w, units=self.units) + ) + + return matrix + + def set_control_points(self, value: List) -> None: + """ + sets the control points of this surface + """ + data = [] + self.countU = len(value) + self.countV = len(value[0]) + + for row in value: + for pt in row: + data.extend([pt.x, pt.y, pt.z, pt.weight]) + + self.pointData = data diff --git a/src/specklepy/objects/geometry/vector.py b/src/specklepy/objects/geometry/vector.py index 830d621b..d7f1bcf7 100644 --- a/src/specklepy/objects/geometry/vector.py +++ b/src/specklepy/objects/geometry/vector.py @@ -17,7 +17,13 @@ class Vector( z: float def __repr__(self) -> str: - return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})" + return ( + f"{self.__class__.__name__}(" + f"x: {self.x}, " + f"y: {self.y}, " + f"z: {self.z}, " + f"units: {self.units})" + ) @property def length(self) -> float: diff --git a/src/specklepy/objects/interfaces.py b/src/specklepy/objects/interfaces.py index 8cf2e962..b8d4be8c 100644 --- a/src/specklepy/objects/interfaces.py +++ b/src/specklepy/objects/interfaces.py @@ -65,12 +65,13 @@ class IHasArea(metaclass=ABCMeta): _area: float = field(init=False, repr=False) @property + @abstractmethod def area(self) -> float: - return self._area + pass @area.setter def area(self, value: float): - if not isinstance(value, (int, float)): + if not isinstance(value, int | float): raise ValueError(f"Area must be a number, got {type(value)}") self._area = float(value) @@ -80,12 +81,13 @@ class IHasVolume(metaclass=ABCMeta): _volume: float = field(init=False, repr=False) @property + @abstractmethod def volume(self) -> float: - return self._volume + pass @volume.setter def volume(self, value: float): - if not isinstance(value, (int, float)): + if not isinstance(value, int | float): raise ValueError(f"Volume must be a number, got {type(value)}") self._volume = float(value) diff --git a/src/specklepy/objects/primitive.py b/src/specklepy/objects/primitive.py index c146e180..0982268b 100644 --- a/src/specklepy/objects/primitive.py +++ b/src/specklepy/objects/primitive.py @@ -15,7 +15,7 @@ def __repr__(self) -> str: @property def length(self) -> float: - abs(self.end - self.start) + return abs(self.end - self.start) @classmethod def unit_interval(cls) -> "Interval": diff --git a/src/specklepy/objects/tests/asdasd.py b/src/specklepy/objects/tests/asdasd.py deleted file mode 100644 index 0b7cb96c..00000000 --- a/src/specklepy/objects/tests/asdasd.py +++ /dev/null @@ -1,9 +0,0 @@ -from specklepy.objects.geometry import Line, Point -from specklepy.objects.models.units import Units - -p_1 = Point(x=0, y=0, z=0, units=Units.m) -p_2 = Point(x=3, y=0, z=0, units=Units.m) - -line = Line(start=p_1, end=p_2, units=Units.m) -line.length = line.calculate_length() -print(line.length) diff --git a/src/specklepy/objects/tests/test_arc.py b/src/specklepy/objects/tests/test_arc.py index 4484fa35..f90259a7 100644 --- a/src/specklepy/objects/tests/test_arc.py +++ b/src/specklepy/objects/tests/test_arc.py @@ -1,8 +1,13 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + import math import pytest from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException from specklepy.objects.geometry import Arc, Plane, Point, Vector from specklepy.objects.models.units import Units from specklepy.objects.primitive import Interval @@ -69,56 +74,77 @@ def test_arc_length(sample_arc): assert sample_arc.length == pytest.approx(math.pi) -def test_arc_units(sample_points, sample_plane): +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "plane", + { + "plane": "not a plane", + "startPoint": None, + "midPoint": None, + "endPoint": None, + }, + "Cannot set 'Arc.plane':it expects type '',but received type 'str'", + ), + ( + "startPoint", + { + "plane": None, + "startPoint": "not a point", + "midPoint": None, + "endPoint": None, + }, + "Cannot set 'Arc.startPoint':it expects type '',but received type 'str'", + ), + ( + "midPoint", + { + "plane": None, + "startPoint": None, + "midPoint": "not a point", + "endPoint": None, + }, + "Cannot set 'Arc.midPoint':it expects type '',but received type 'str'", + ), + ( + "endPoint", + { + "plane": None, + "startPoint": None, + "midPoint": None, + "endPoint": "not a point", + }, + "Cannot set 'Arc.endPoint':it expects type '',but received type 'str'", + ), + ], +) +def test_arc_inval(sample_points, sample_plane, invalid_param, test_params, error_msg): start, mid, end = sample_points - arc = Arc( - plane=sample_plane, startPoint=start, midPoint=mid, endPoint=end, units=Units.m - ) - assert arc.units == Units.m.value + if invalid_param != "plane": + test_params["plane"] = sample_plane + if invalid_param != "startPoint": + test_params["startPoint"] = start + if invalid_param != "midPoint": + test_params["midPoint"] = mid + if invalid_param != "endPoint": + test_params["endPoint"] = end - arc.units = "mm" - assert arc.units == "mm" + with pytest.raises(SpeckleException) as exc_info: + Arc(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" -def test_arc_invalid_construction(sample_points, sample_plane): +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_arc_units(sample_points, sample_plane, new_units): start, mid, end = sample_points - - with pytest.raises(Exception): - Arc( - plane="not a plane", - startPoint=start, - midPoint=mid, - endPoint=end, - units=Units.m, - ) - - with pytest.raises(Exception): - Arc( - plane=sample_plane, - startPoint="not a point", - midPoint=mid, - endPoint=end, - units=Units.m, - ) - - with pytest.raises(Exception): - Arc( - plane=sample_plane, - startPoint=start, - midPoint="not a point", - endPoint=end, - units=Units.m, - ) - - with pytest.raises(Exception): - Arc( - plane=sample_plane, - startPoint=start, - midPoint=mid, - endPoint="not a point", - units=Units.m, - ) + arc = Arc( + plane=sample_plane, startPoint=start, midPoint=mid, endPoint=end, units=Units.m + ) + assert arc.units == Units.m.value + arc.units = new_units + assert arc.units == new_units def test_arc_serialization(sample_arc): diff --git a/src/specklepy/objects/tests/test_box.py b/src/specklepy/objects/tests/test_box.py new file mode 100644 index 00000000..0b628bd7 --- /dev/null +++ b/src/specklepy/objects/tests/test_box.py @@ -0,0 +1,130 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Box, Plane, Point, Vector +from specklepy.objects.models.units import Units +from specklepy.objects.primitive import Interval + + +@pytest.fixture +def sample_plane(): + origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m) + normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) + ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) + return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m) + + +@pytest.fixture +def sample_intervals(): + return ( + Interval(start=0.0, end=1.0), + Interval(start=0.0, end=1.0), + Interval(start=0.0, end=1.0), + ) + + +@pytest.fixture +def sample_box(sample_plane, sample_intervals): + xsize, ysize, zsize = sample_intervals + return Box( + basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m + ) + + +def test_box_creation(sample_plane, sample_intervals): + xsize, ysize, zsize = sample_intervals + box = Box( + basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m + ) + + assert box.basePlane == sample_plane + assert box.xSize == xsize + assert box.ySize == ysize + assert box.zSize == zsize + assert box.units == Units.m.value + + +@pytest.mark.parametrize("expected_area", [6.0]) # 6 faces, each 1x1 +def test_box_area(sample_box, expected_area): + sample_box.area = sample_box.area + assert sample_box.area == pytest.approx(expected_area) + + +@pytest.mark.parametrize("expected_volume", [1.0]) # 1x1x1 cube +def test_box_volume(sample_box, expected_volume): + sample_box.volume = sample_box.volume + assert sample_box.volume == pytest.approx(expected_volume) + + +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_box_units(sample_plane, sample_intervals, new_units): + xsize, ysize, zsize = sample_intervals + box = Box( + basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m + ) + assert box.units == Units.m.value + box.units = new_units + assert box.units == new_units + + +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "basePlane", + {"basePlane": "not a plane", "xSize": None, "ySize": None, "zSize": None}, + "Cannot set 'Box.basePlane':it expects type '',but received type 'str'", + ), + ( + "xSize", + { + "basePlane": None, + "xSize": "not an interval", + "ySize": None, + "zSize": None, + }, + "Cannot set 'Box.xSize':it expects type '',but received type 'str'", + ), + ], +) +def test_box_inval( + sample_plane, sample_intervals, invalid_param, test_params, error_msg +): + xsize, ysize, zsize = sample_intervals + + if invalid_param != "basePlane": + test_params["basePlane"] = sample_plane + if invalid_param != "xSize": + test_params["xSize"] = xsize + if invalid_param != "ySize": + test_params["ySize"] = ysize + if invalid_param != "zSize": + test_params["zSize"] = zsize + + with pytest.raises(SpeckleException) as exc_info: + Box(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" + + +def test_box_serialization(sample_box): + serialized = serialize(sample_box) + deserialized = deserialize(serialized) + + assert deserialized.basePlane.origin.x == sample_box.basePlane.origin.x + assert deserialized.basePlane.origin.y == sample_box.basePlane.origin.y + assert deserialized.basePlane.origin.z == sample_box.basePlane.origin.z + + assert deserialized.xSize.start == sample_box.xSize.start + assert deserialized.xSize.end == sample_box.xSize.end + assert deserialized.ySize.start == sample_box.ySize.start + assert deserialized.ySize.end == sample_box.ySize.end + assert deserialized.zSize.start == sample_box.zSize.start + assert deserialized.zSize.end == sample_box.zSize.end + + assert deserialized.units == sample_box.units diff --git a/src/specklepy/objects/tests/test_circle.py b/src/specklepy/objects/tests/test_circle.py new file mode 100644 index 00000000..ccf84f38 --- /dev/null +++ b/src/specklepy/objects/tests/test_circle.py @@ -0,0 +1,123 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + +import math + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Circle, Plane, Point, Vector +from specklepy.objects.models.units import Units +from specklepy.objects.primitive import Interval + + +@pytest.fixture +def sample_plane(): + origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m) + normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) + ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) + return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m) + + +@pytest.fixture +def sample_center(): + return Point(x=0.0, y=0.0, z=0.0, units=Units.m) + + +@pytest.fixture +def sample_circle(sample_plane, sample_center): + return Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m) + + +def test_circle_creation(sample_plane, sample_center): + circle = Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m) + assert circle.plane == sample_plane + assert circle.center == sample_center + assert circle.radius == 1.0 + assert circle.units == Units.m.value + + +def test_circle_domain(sample_circle): + assert isinstance(sample_circle.domain, Interval) + assert sample_circle.domain.start == 0.0 + assert sample_circle.domain.end == 1.0 + + +@pytest.mark.parametrize( + "radius,expected_length", [(1.0, 2 * math.pi), (2.0, 4 * math.pi), (0.5, math.pi)] +) +def test_circle_length(sample_circle, radius, expected_length): + sample_circle.radius = radius + assert sample_circle.length == pytest.approx(expected_length) + + +@pytest.mark.parametrize( + "radius,expected_area", [(1.0, math.pi), (2.0, 4 * math.pi), (0.5, math.pi * 0.25)] +) +def test_circle_area(sample_circle, radius, expected_area): + sample_circle.radius = radius + assert sample_circle.area == pytest.approx(expected_area) + + +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_circle_units(sample_plane, sample_center, new_units): + circle = Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m) + assert circle.units == Units.m.value + circle.units = new_units + assert circle.units == new_units + + +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "plane", + {"plane": "not a plane", "center": None, "radius": 1.0}, + "Cannot set 'Circle.plane':it expects type '',but received type 'str'", + ), + ( + "center", + {"plane": None, "center": "not a point", "radius": 1.0}, + "Cannot set 'Circle.center':it expects type '',but received type 'str'", + ), + ( + "radius", + {"plane": None, "center": None, "radius": "not a number"}, + "Cannot set 'Circle.radius':it expects type '',but received type 'str'", + ), + ], +) +def test_circle_inval( + sample_plane, sample_center, invalid_param, test_params, error_msg +): + if invalid_param != "plane": + test_params["plane"] = sample_plane + if invalid_param != "center": + test_params["center"] = sample_center + + with pytest.raises(SpeckleException) as exc_info: + Circle(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" + + +def test_circle_serialization(sample_circle): + serialized = serialize(sample_circle) + deserialized = deserialize(serialized) + + assert deserialized.plane.origin.x == sample_circle.plane.origin.x + assert deserialized.plane.origin.y == sample_circle.plane.origin.y + assert deserialized.plane.origin.z == sample_circle.plane.origin.z + + assert deserialized.plane.normal.x == sample_circle.plane.normal.x + assert deserialized.plane.normal.y == sample_circle.plane.normal.y + assert deserialized.plane.normal.z == sample_circle.plane.normal.z + + assert deserialized.center.x == sample_circle.center.x + assert deserialized.center.y == sample_circle.center.y + assert deserialized.center.z == sample_circle.center.z + + assert deserialized.radius == sample_circle.radius + assert deserialized.units == sample_circle.units diff --git a/src/specklepy/objects/tests/test_control_point.py b/src/specklepy/objects/tests/test_control_point.py new file mode 100644 index 00000000..07d66ca2 --- /dev/null +++ b/src/specklepy/objects/tests/test_control_point.py @@ -0,0 +1,77 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import ControlPoint +from specklepy.objects.models.units import Units + + +@pytest.fixture +def sample_control_point(): + return ControlPoint(x=1.0, y=2.0, z=3.0, weight=1.0, units=Units.m) + + +@pytest.mark.parametrize( + "x,y,z,weight,expected_units", [(1.0, 2.0, 3.0, 1.0, Units.m.value)] +) +def test_control_point_creation(x, y, z, weight, expected_units): + control_point = ControlPoint(x=x, y=y, z=z, weight=weight, units=Units.m) + assert control_point.x == x + assert control_point.y == y + assert control_point.z == z + assert control_point.weight == weight + assert control_point.units == expected_units + + +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_control_point_units(new_units): + control_point = ControlPoint(x=1.0, y=2.0, z=3.0, weight=1.0, units=Units.m) + assert control_point.units == Units.m.value + control_point.units = new_units + assert control_point.units == new_units + + +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "x", + {"x": "not a number", "y": 2.0, "z": 3.0, "weight": 1.0}, + "Cannot set 'ControlPoint.x':it expects type '',but received type 'str'", + ), + ( + "y", + {"x": 1.0, "y": "not a number", "z": 3.0, "weight": 1.0}, + "Cannot set 'ControlPoint.y':it expects type '',but received type 'str'", + ), + ( + "z", + {"x": 1.0, "y": 2.0, "z": "not a number", "weight": 1.0}, + "Cannot set 'ControlPoint.z':it expects type '',but received type 'str'", + ), + ( + "weight", + {"x": 1.0, "y": 2.0, "z": 3.0, "weight": "not a number"}, + "Cannot set 'ControlPoint.weight':it expects type '',but received type 'str'", + ), + ], +) +def test_control_point_invalid_construction(invalid_param, test_params, error_msg): + with pytest.raises(SpeckleException) as exc_info: + ControlPoint(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" + + +def test_control_point_serialization(sample_control_point): + serialized = serialize(sample_control_point) + deserialized = deserialize(serialized) + + assert deserialized.x == sample_control_point.x + assert deserialized.y == sample_control_point.y + assert deserialized.z == sample_control_point.z + assert deserialized.weight == sample_control_point.weight + assert deserialized.units == sample_control_point.units diff --git a/src/specklepy/objects/tests/test_ellipse.py b/src/specklepy/objects/tests/test_ellipse.py new file mode 100644 index 00000000..d71edea1 --- /dev/null +++ b/src/specklepy/objects/tests/test_ellipse.py @@ -0,0 +1,113 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + +import math + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Ellipse, Plane, Point, Vector +from specklepy.objects.models.units import Units +from specklepy.objects.primitive import Interval + + +@pytest.fixture +def sample_plane(): + origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m) + normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) + ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) + + return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m) + + +@pytest.fixture +def sample_ellipse(sample_plane): + return Ellipse( + plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m + ) + + +def test_ellipse_creation(sample_plane): + ellipse = Ellipse( + plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m + ) + + assert ellipse.plane == sample_plane + assert ellipse.first_radius == 2.0 + assert ellipse.second_radius == 1.0 + assert ellipse.units == Units.m.value + + +def test_ellipse_domain(sample_ellipse): + assert isinstance(sample_ellipse.domain, Interval) + assert sample_ellipse.domain.start == 0.0 + assert sample_ellipse.domain.end == 1.0 + + +@pytest.mark.parametrize( + "area_value", + [ + 10.0, + math.pi * 2.0, # area for circle with radius 2 + 0.0, + ], +) +def test_ellipse_area(sample_ellipse, area_value): + sample_ellipse.area = area_value + assert sample_ellipse.area == pytest.approx(area_value) + + +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_ellipse_units(sample_plane, new_units): + ellipse = Ellipse( + plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m + ) + assert ellipse.units == Units.m.value + + ellipse.units = new_units + assert ellipse.units == new_units + + +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "plane", + {"plane": "not a plane", "first_radius": 2.0, "second_radius": 1.0}, + "Cannot set 'Ellipse.plane':it expects type '',but received type 'str'", + ), + ( + "first_radius", + {"plane": None, "first_radius": "not a number", "second_radius": 1.0}, + "Cannot set 'Ellipse.first_radius':it expects type '',but received type 'str'", + ), + ( + "second_radius", + {"plane": None, "first_radius": 2.0, "second_radius": "not number"}, + "Cannot set 'Ellipse.second_radius':it expects type '',but received type 'str'", + ), + ], +) +def test_ellipse_invalid(sample_plane, invalid_param, test_params, error_msg): + if invalid_param != "plane": + test_params["plane"] = sample_plane + + with pytest.raises(SpeckleException) as exc_info: + Ellipse(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" + + +def test_ellipse_serialization(sample_ellipse): + serialized = serialize(sample_ellipse) + deserialized = deserialize(serialized) + + assert deserialized.plane.origin.x == sample_ellipse.plane.origin.x + assert deserialized.plane.origin.y == sample_ellipse.plane.origin.y + assert deserialized.plane.origin.z == sample_ellipse.plane.origin.z + + assert deserialized.first_radius == sample_ellipse.first_radius + assert deserialized.second_radius == sample_ellipse.second_radius + assert deserialized.units == sample_ellipse.units diff --git a/src/specklepy/objects/tests/test_line.py b/src/specklepy/objects/tests/test_line.py index 7fbb8329..616facde 100644 --- a/src/specklepy/objects/tests/test_line.py +++ b/src/specklepy/objects/tests/test_line.py @@ -1,6 +1,11 @@ +# ignoring "line too long" check from linter +# to match with SpeckleExceptions +# ruff: noqa: E501 + import pytest from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException from specklepy.objects.geometry import Line, Point from specklepy.objects.models.units import Units from specklepy.objects.primitive import Interval @@ -16,39 +21,62 @@ def sample_points(): @pytest.fixture def sample_line(sample_points): start, end = sample_points - line = Line(start=start, end=end, units=Units.m) - return line + return Line(start=start, end=end, units=Units.m) def test_line_creation(sample_points): start, end = sample_points line = Line(start=start, end=end, units=Units.m) - assert line.start == start assert line.end == end assert line.units == Units.m.value def test_line_domain(sample_line): - # Domain should be automatically initialized to unit interval by ICurve assert isinstance(sample_line.domain, Interval) assert sample_line.domain.start == 0.0 assert sample_line.domain.end == 1.0 -def test_line_length(sample_line): - assert sample_line.length == 5.0 +@pytest.mark.parametrize("expected_length", [5.0]) +def test_line_length(sample_line, expected_length): + assert sample_line.length == expected_length -def test_line_units(sample_points): +@pytest.mark.parametrize("new_units", ["mm", "cm", "in"]) +def test_line_units(sample_points, new_units): start, end = sample_points line = Line(start=start, end=end, units=Units.m) - assert line.units == Units.m.value + line.units = new_units + assert line.units == new_units + + +@pytest.mark.parametrize( + "invalid_param, test_params, error_msg", + [ + ( + "start", + {"start": "not a point", "end": None}, + "Cannot set 'Line.start':it expects type '',but received type 'str'", + ), + ( + "end", + {"start": None, "end": "not a point"}, + "Cannot set 'Line.end':it expects type '',but received type 'str'", + ), + ], +) +def test_line_invalid(sample_points, invalid_param, test_params, error_msg): + start, end = sample_points + if invalid_param != "start": + test_params["start"] = start + if invalid_param != "end": + test_params["end"] = end - # Test setting units with string - line.units = "mm" - assert line.units == "mm" + with pytest.raises(SpeckleException) as exc_info: + Line(**test_params, units=Units.m) + assert str(exc_info.value) == f"SpeckleException: {error_msg}" def test_line_serialization(sample_line): @@ -64,16 +92,3 @@ def test_line_serialization(sample_line): assert deserialized.units == sample_line.units assert deserialized.domain.start == sample_line.domain.start assert deserialized.domain.end == sample_line.domain.end - - -def test_line_invalid_construction(): - """Test error cases""" - p1 = Point(x=0.0, y=0.0, z=0.0, units=Units.m) - - # Test with invalid start point - with pytest.raises(Exception): - Line(start="not a point", end=p1) - - # Test with invalid end point - with pytest.raises(Exception): - Line(start=p1, end="not a point") diff --git a/src/specklepy/objects/tests/test_mesh.py b/src/specklepy/objects/tests/test_mesh.py index b0511766..f54994df 100644 --- a/src/specklepy/objects/tests/test_mesh.py +++ b/src/specklepy/objects/tests/test_mesh.py @@ -224,7 +224,7 @@ def test_mesh_invalid_vertices(): mesh = Mesh(vertices=[0.0, 0.0], faces=[3, 0, 1, 2], units=Units.m) with pytest.raises(ValueError): - mesh.vertices_count + _ = mesh.vertices_count def test_mesh_invalid_faces(): diff --git a/src/specklepy/objects/tests/test_plane.py b/src/specklepy/objects/tests/test_plane.py index 44715e93..ab3b14e4 100644 --- a/src/specklepy/objects/tests/test_plane.py +++ b/src/specklepy/objects/tests/test_plane.py @@ -1,98 +1,132 @@ +from typing import Any, Tuple + import pytest from specklepy.core.api.operations import deserialize, serialize -from specklepy.objects.geometry import Plane, Point, Vector +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Plane, Point, Spiral, Vector from specklepy.objects.models.units import Units @pytest.fixture -def sample_point(): - point = Point(x=1.0, y=2.0, z=3.0, units=Units.m) - return point +def sample_points() -> Tuple[Point, Point]: + return Point(x=0.0, y=0.0, z=0.0, units=Units.m), Point( + x=0.0, y=0.0, z=2.0, units=Units.m + ) @pytest.fixture -def sample_vectors(): +def sample_plane() -> Plane: + origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m) normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) - - return normal, xdir, ydir + return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m) @pytest.fixture -def sample_plane(sample_point, sample_vectors): - normal, xdir, ydir = sample_vectors - plane = Plane( - origin=sample_point, normal=normal, xdir=xdir, ydir=ydir, units=Units.m - ) - return plane - - -def test_plane_creation(sample_point, sample_vectors): - normal, xdir, ydir = sample_vectors - plane = Plane( - origin=sample_point, normal=normal, xdir=xdir, ydir=ydir, units=Units.m +def sample_spiral(sample_points: Tuple[Point, Point], sample_plane: Plane) -> Spiral: + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + return Spiral( + start_point=start, + end_point=end, + plane=sample_plane, + turns=2.0, + pitch=1.0, + pitch_axis=pitch_axis, + units=Units.m, ) - assert plane.origin == sample_point - assert plane.normal == normal - assert plane.xdir == xdir - assert plane.ydir == ydir - assert plane.units == Units.m.value - -def test_plane_units(sample_point, sample_vectors): - normal, xdir, ydir = sample_vectors - plane = Plane( - origin=sample_point, normal=normal, xdir=xdir, ydir=ydir, units=Units.m +def test_spiral_creation(sample_points: Tuple[Point, Point], sample_plane: Plane): + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + spiral = Spiral( + start_point=start, + end_point=end, + plane=sample_plane, + turns=2.0, + pitch=1.0, + pitch_axis=pitch_axis, + units=Units.m, ) - assert plane.units == Units.m.value - - plane.units = "mm" - assert plane.units == "mm" - - -def test_plane_invalid_construction(): - point = Point(x=1.0, y=2.0, z=3.0, units=Units.m) - normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) - xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) - ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) - - with pytest.raises(Exception): - Plane(origin="not a point", normal=normal, xdir=xdir, ydir=ydir) - - with pytest.raises(Exception): - Plane(origin=point, normal="not a vector", xdir=xdir, ydir=ydir) - - with pytest.raises(Exception): - Plane(origin=point, normal=normal, xdir="not a vector", ydir=ydir) - - # Test with invalid ydir vector - with pytest.raises(Exception): - Plane(origin=point, normal=normal, xdir=xdir, ydir="not a vector") - - -def test_plane_serialization(sample_plane): - serialized = serialize(sample_plane) + assert spiral.start_point == start + assert spiral.end_point == end + assert spiral.plane == sample_plane + assert spiral.turns == 2.0 + assert spiral.pitch == 1.0 + assert spiral.pitch_axis == pitch_axis + assert spiral.units == Units.m.value + + +@pytest.mark.parametrize( + "invalid_param,invalid_value", + [ + ("start_point", "not a point"), + ("end_point", "not a point"), + ("plane", "not a plane"), + ("turns", "not a number"), + ], +) +def test_spiral_invalid_construction( + sample_points: Tuple[Point, Point], + sample_plane: Plane, + invalid_param: str, + invalid_value: Any, +): + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + + valid_params = { + "start_point": start, + "end_point": end, + "plane": sample_plane, + "turns": 2.0, + "pitch": 1.0, + "pitch_axis": pitch_axis, + "units": Units.m, + } + + valid_params[invalid_param] = invalid_value + + with pytest.raises(SpeckleException): + Spiral(**valid_params) + + +@pytest.mark.parametrize("test_value", [10.0]) +def test_spiral_length(sample_spiral: Spiral, test_value: float): + sample_spiral.length = test_value + assert sample_spiral.length == test_value + + +@pytest.mark.parametrize("test_value", [15.0]) +def test_spiral_area(sample_spiral: Spiral, test_value: float): + sample_spiral.area = test_value + assert sample_spiral.area == test_value + + +def test_spiral_serialization(sample_spiral: Spiral): + serialized = serialize(sample_spiral) deserialized = deserialize(serialized) - # Check all properties are preserved - assert deserialized.origin.x == sample_plane.origin.x - assert deserialized.origin.y == sample_plane.origin.y - assert deserialized.origin.z == sample_plane.origin.z + assert deserialized.start_point.x == sample_spiral.start_point.x + assert deserialized.start_point.y == sample_spiral.start_point.y + assert deserialized.start_point.z == sample_spiral.start_point.z - assert deserialized.normal.x == sample_plane.normal.x - assert deserialized.normal.y == sample_plane.normal.y - assert deserialized.normal.z == sample_plane.normal.z + assert deserialized.end_point.x == sample_spiral.end_point.x + assert deserialized.end_point.y == sample_spiral.end_point.y + assert deserialized.end_point.z == sample_spiral.end_point.z - assert deserialized.xdir.x == sample_plane.xdir.x - assert deserialized.xdir.y == sample_plane.xdir.y - assert deserialized.xdir.z == sample_plane.xdir.z + assert deserialized.plane.origin.x == sample_spiral.plane.origin.x + assert deserialized.plane.origin.y == sample_spiral.plane.origin.y + assert deserialized.plane.origin.z == sample_spiral.plane.origin.z - assert deserialized.ydir.x == sample_plane.ydir.x - assert deserialized.ydir.y == sample_plane.ydir.y - assert deserialized.ydir.z == sample_plane.ydir.z + assert deserialized.turns == sample_spiral.turns + assert deserialized.pitch == sample_spiral.pitch + assert deserialized.pitch_axis.x == sample_spiral.pitch_axis.x + assert deserialized.pitch_axis.y == sample_spiral.pitch_axis.y + assert deserialized.pitch_axis.z == sample_spiral.pitch_axis.z - assert deserialized.units == sample_plane.units + assert deserialized.units == sample_spiral.units diff --git a/src/specklepy/objects/tests/test_point_cloud.py b/src/specklepy/objects/tests/test_point_cloud.py new file mode 100644 index 00000000..e79ddcd2 --- /dev/null +++ b/src/specklepy/objects/tests/test_point_cloud.py @@ -0,0 +1,60 @@ +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.objects.geometry import Point, PointCloud +from specklepy.objects.models.units import Units + + +@pytest.fixture +def sample_points(): + return [ + Point(x=0.0, y=0.0, z=0.0, units=Units.m), + Point(x=1.0, y=0.0, z=0.0, units=Units.m), + Point(x=0.0, y=1.0, z=0.0, units=Units.m), + Point(x=1.0, y=1.0, z=0.0, units=Units.m), + ] + + +@pytest.fixture +def sample_point_cloud(sample_points): + return PointCloud(points=sample_points, units=Units.m) + + +def test_point_cloud_creation(sample_points): + point_cloud = PointCloud(points=sample_points, units=Units.m) + + assert len(point_cloud.points) == 4 + assert isinstance(point_cloud.points, list) + assert all(isinstance(p, Point) for p in point_cloud.points) + assert point_cloud.units == Units.m.value + + +def test_point_cloud_units(sample_points): + point_cloud = PointCloud(points=sample_points, units=Units.m) + assert point_cloud.units == Units.m.value + + point_cloud.units = "mm" + assert point_cloud.units == "mm" + + +def test_point_cloud_empty_points(): + point_cloud = PointCloud(points=[], units=Units.m) + assert len(point_cloud.points) == 0 + assert isinstance(point_cloud.points, list) + + +def test_point_cloud_serialization(sample_point_cloud): + serialized = serialize(sample_point_cloud) + deserialized = deserialize(serialized) + + assert len(deserialized.points) == len(sample_point_cloud.points) + + for orig_point, deserial_point in zip( + sample_point_cloud.points, deserialized.points, strict=True + ): + assert deserial_point.x == orig_point.x + assert deserial_point.y == orig_point.y + assert deserial_point.z == orig_point.z + assert deserial_point.units == orig_point.units + + assert deserialized.units == sample_point_cloud.units diff --git a/src/specklepy/objects/tests/test_polycurve.py b/src/specklepy/objects/tests/test_polycurve.py new file mode 100644 index 00000000..7fc44edc --- /dev/null +++ b/src/specklepy/objects/tests/test_polycurve.py @@ -0,0 +1,108 @@ +from typing import List, Tuple + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Line, Point, Polycurve, Polyline +from specklepy.objects.models.units import Units + + +@pytest.fixture +def sample_points() -> Tuple[Point, Point, Point]: + return ( + Point(x=0.0, y=0.0, z=0.0, units=Units.m), + Point(x=1.0, y=0.0, z=0.0, units=Units.m), + Point(x=1.0, y=1.0, z=0.0, units=Units.m), + ) + + +@pytest.fixture +def sample_lines(sample_points: Tuple[Point, Point, Point]) -> List[Line]: + p1, p2, p3 = sample_points + return [ + Line(start=p1, end=p2, units=Units.m), + Line(start=p2, end=p3, units=Units.m), + ] + + +@pytest.fixture +def sample_polycurve(sample_lines: List[Line]) -> Polycurve: + return Polycurve(segments=sample_lines, units=Units.m) + + +def test_polycurve_creation(sample_lines: List[Line]): + polycurve = Polycurve(segments=sample_lines, units=Units.m) + assert len(polycurve.segments) == 2 + assert polycurve.units == Units.m.value + assert isinstance(polycurve.segments[0], Line) + + +def test_polycurve_is_closed(sample_points: Tuple[Point, Point, Point]): + p1, p2, p3 = sample_points + lines = [ + Line(start=p1, end=p2, units=Units.m), + Line(start=p2, end=p3, units=Units.m), + Line(start=p3, end=p1, units=Units.m), + ] + closed_polycurve = Polycurve(segments=lines, units=Units.m) + assert closed_polycurve.is_closed() + + +def test_polycurve_not_closed(sample_polycurve: Polycurve): + assert not sample_polycurve.is_closed() + + +@pytest.mark.parametrize("expected_length", [2.0]) +def test_polycurve_length(sample_polycurve: Polycurve, expected_length: float): + sample_polycurve.length = sample_polycurve.calculate_length() + assert sample_polycurve.length == pytest.approx(expected_length) + + +@pytest.mark.parametrize( + "points,expected_segments,expected_closed", + [ + ([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0], 2, False), + ( + [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0], + 4, + True, + ), + ], +) +def test_polycurve_from_polyline( + points: List[float], expected_segments: int, expected_closed: bool +): + polyline = Polyline(value=points, units=Units.m) + polycurve = Polycurve.from_polyline(polyline) + + assert len(polycurve.segments) == expected_segments + assert polycurve.units == Units.m.value + assert isinstance(polycurve.segments[0], Line) + assert polycurve.is_closed() == expected_closed + + +def test_polycurve_serialization(sample_polycurve: Polycurve): + serialized = serialize(sample_polycurve) + deserialized = deserialize(serialized) + + assert len(deserialized.segments) == len(sample_polycurve.segments) + assert deserialized.units == sample_polycurve.units + + assert deserialized.segments[0].start.x == sample_polycurve.segments[0].start.x + assert deserialized.segments[0].start.y == sample_polycurve.segments[0].start.y + assert deserialized.segments[0].start.z == sample_polycurve.segments[0].start.z + assert deserialized.segments[0].end.x == sample_polycurve.segments[0].end.x + assert deserialized.segments[0].end.y == sample_polycurve.segments[0].end.y + assert deserialized.segments[0].end.z == sample_polycurve.segments[0].end.z + + +def test_polycurve_empty(): + polycurve = Polycurve(segments=[], units=Units.m) + assert not polycurve.is_closed() + assert polycurve.calculate_length() == 0.0 + + +def test_polycurve_invalid_segment(): + with pytest.raises(SpeckleException): + Polycurve(segments=["not a curve"], units=Units.m) diff --git a/src/specklepy/objects/tests/test_spiral.py b/src/specklepy/objects/tests/test_spiral.py new file mode 100644 index 00000000..39cfbce3 --- /dev/null +++ b/src/specklepy/objects/tests/test_spiral.py @@ -0,0 +1,130 @@ +from typing import Any, Tuple + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import Plane, Point, Spiral, Vector +from specklepy.objects.models.units import Units + + +@pytest.fixture +def sample_points() -> Tuple[Point, Point]: + return ( + Point(x=0.0, y=0.0, z=0.0, units=Units.m), + Point(x=0.0, y=0.0, z=2.0, units=Units.m), + ) + + +@pytest.fixture +def sample_plane() -> Plane: + origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m) + normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m) + ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m) + return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m) + + +@pytest.fixture +def sample_spiral(sample_points: Tuple[Point, Point], sample_plane: Plane) -> Spiral: + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + return Spiral( + start_point=start, + end_point=end, + plane=sample_plane, + turns=2.0, + pitch=1.0, + pitch_axis=pitch_axis, + units=Units.m, + ) + + +@pytest.mark.parametrize("units", [Units.m]) +def test_spiral_creation( + sample_points: Tuple[Point, Point], sample_plane: Plane, units: Units +): + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=units) + spiral = Spiral( + start_point=start, + end_point=end, + plane=sample_plane, + turns=2.0, + pitch=1.0, + pitch_axis=pitch_axis, + units=units, + ) + + assert spiral.start_point == start + assert spiral.end_point == end + assert spiral.plane == sample_plane + assert spiral.turns == 2.0 + assert spiral.pitch == 1.0 + assert spiral.pitch_axis == pitch_axis + assert spiral.units == units.value + + +@pytest.mark.parametrize( + "invalid_param,invalid_value", + [ + ("start_point", "not a point"), + ("end_point", "not a point"), + ("plane", "not a plane"), + ("turns", "not a number"), + ], +) +def test_spiral_invalid_construction( + sample_points: Tuple[Point, Point], + sample_plane: Plane, + invalid_param: str, + invalid_value: Any, +): + start, end = sample_points + pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m) + valid_params = { + "start_point": start, + "end_point": end, + "plane": sample_plane, + "turns": 2.0, + "pitch": 1.0, + "pitch_axis": pitch_axis, + "units": Units.m, + } + valid_params[invalid_param] = invalid_value + + with pytest.raises(SpeckleException): + Spiral(**valid_params) + + +@pytest.mark.parametrize("test_value", [10.0]) +def test_spiral_length(sample_spiral: Spiral, test_value: float): + sample_spiral.length = test_value + assert sample_spiral.length == test_value + + +@pytest.mark.parametrize("test_value", [15.0]) +def test_spiral_area(sample_spiral: Spiral, test_value: float): + sample_spiral.area = test_value + assert sample_spiral.area == test_value + + +def test_spiral_serialization(sample_spiral: Spiral): + serialized = serialize(sample_spiral) + deserialized = deserialize(serialized) + + assert deserialized.start_point.x == sample_spiral.start_point.x + assert deserialized.start_point.y == sample_spiral.start_point.y + assert deserialized.start_point.z == sample_spiral.start_point.z + assert deserialized.end_point.x == sample_spiral.end_point.x + assert deserialized.end_point.y == sample_spiral.end_point.y + assert deserialized.end_point.z == sample_spiral.end_point.z + assert deserialized.plane.origin.x == sample_spiral.plane.origin.x + assert deserialized.plane.origin.y == sample_spiral.plane.origin.y + assert deserialized.plane.origin.z == sample_spiral.plane.origin.z + assert deserialized.turns == sample_spiral.turns + assert deserialized.pitch == sample_spiral.pitch + assert deserialized.pitch_axis.x == sample_spiral.pitch_axis.x + assert deserialized.pitch_axis.y == sample_spiral.pitch_axis.y + assert deserialized.pitch_axis.z == sample_spiral.pitch_axis.z + assert deserialized.units == sample_spiral.units diff --git a/src/specklepy/objects/tests/test_surface.py b/src/specklepy/objects/tests/test_surface.py new file mode 100644 index 00000000..5690e3e2 --- /dev/null +++ b/src/specklepy/objects/tests/test_surface.py @@ -0,0 +1,189 @@ +from typing import Any, List, Tuple + +import pytest + +from specklepy.core.api.operations import deserialize, serialize +from specklepy.logging.exceptions import SpeckleException +from specklepy.objects.geometry import ControlPoint, Surface +from specklepy.objects.models.units import Units +from specklepy.objects.primitive import Interval + + +@pytest.fixture +def sample_intervals() -> Tuple[Interval, Interval]: + return (Interval(start=0.0, end=1.0), Interval(start=0.0, end=1.0)) + + +@pytest.fixture +def sample_point_data() -> List[float]: + return [ + 0.0, + 0.0, + 0.0, + 1.0, # point 1 + 1.0, + 0.0, + 0.0, + 1.0, # point 2 + 0.0, + 1.0, + 0.0, + 1.0, # point 3 + 1.0, + 1.0, + 0.0, + 1.0, # point 4 + ] + + +@pytest.fixture +def sample_surface( + sample_intervals: Tuple[Interval, Interval], sample_point_data: List[float] +) -> Surface: + domain_u, domain_v = sample_intervals + return Surface( + degreeU=1, + degreeV=1, + rational=True, + pointData=sample_point_data, + countU=2, + countV=2, + knotsU=[0.0, 0.0, 1.0, 1.0], + knotsV=[0.0, 0.0, 1.0, 1.0], + domainU=domain_u, + domainV=domain_v, + closedU=False, + closedV=False, + units=Units.m, + ) + + +@pytest.mark.parametrize("units", [Units.m]) +def test_surface_creation( + sample_intervals: Tuple[Interval, Interval], + sample_point_data: List[float], + units: Units, +): + domain_u, domain_v = sample_intervals + surface = Surface( + degreeU=1, + degreeV=1, + rational=True, + pointData=sample_point_data, + countU=2, + countV=2, + knotsU=[0.0, 0.0, 1.0, 1.0], + knotsV=[0.0, 0.0, 1.0, 1.0], + domainU=domain_u, + domainV=domain_v, + closedU=False, + closedV=False, + units=units, + ) + + assert surface.degreeU == 1 + assert surface.degreeV == 1 + assert surface.rational + assert surface.pointData == sample_point_data + assert surface.countU == 2 + assert surface.countV == 2 + assert surface.knotsU == [0.0, 0.0, 1.0, 1.0] + assert surface.knotsV == [0.0, 0.0, 1.0, 1.0] + assert surface.domainU == domain_u + assert surface.domainV == domain_v + assert not surface.closedU + assert not surface.closedV + assert surface.units == units.value + + +@pytest.mark.parametrize("test_value", [1.0]) +def test_surface_area(sample_surface: Surface, test_value: float): + sample_surface.area = test_value + assert sample_surface.area == test_value + + +def test_surface_get_control_points(sample_surface: Surface): + control_points = sample_surface.get_control_points() + + assert len(control_points) == 2 + assert len(control_points[0]) == 2 + assert isinstance(control_points[0][0], ControlPoint) + assert control_points[0][0].x == 0.0 + assert control_points[0][0].y == 0.0 + assert control_points[0][0].z == 0.0 + assert control_points[0][0].weight == 1.0 + assert control_points[0][0].units == Units.m.value + + +@pytest.mark.parametrize("units", [Units.m]) +def test_surface_set_control_points(sample_surface: Surface, units: Units): + control_points = [ + [ + ControlPoint(x=0.0, y=0.0, z=0.0, weight=1.0, units=units), + ControlPoint(x=1.0, y=0.0, z=0.0, weight=1.0, units=units), + ], + [ + ControlPoint(x=0.0, y=1.0, z=0.0, weight=1.0, units=units), + ControlPoint(x=1.0, y=1.0, z=0.0, weight=1.0, units=units), + ], + ] + + sample_surface.set_control_points(control_points) + + assert sample_surface.countU == 2 + assert sample_surface.countV == 2 + assert len(sample_surface.pointData) == 16 + assert sample_surface.pointData[0:4] == [0.0, 0.0, 0.0, 1.0] + + +def test_surface_serialization(sample_surface: Surface): + serialized = serialize(sample_surface) + deserialized = deserialize(serialized) + + assert deserialized.degreeU == sample_surface.degreeU + assert deserialized.degreeV == sample_surface.degreeV + assert deserialized.rational == sample_surface.rational + assert deserialized.pointData == sample_surface.pointData + assert deserialized.countU == sample_surface.countU + assert deserialized.countV == sample_surface.countV + assert deserialized.knotsU == sample_surface.knotsU + assert deserialized.knotsV == sample_surface.knotsV + assert deserialized.domainU.start == sample_surface.domainU.start + assert deserialized.domainU.end == sample_surface.domainU.end + assert deserialized.domainV.start == sample_surface.domainV.start + assert deserialized.domainV.end == sample_surface.domainV.end + assert deserialized.closedU == sample_surface.closedU + assert deserialized.closedV == sample_surface.closedV + assert deserialized.units == sample_surface.units + + +@pytest.mark.parametrize( + "invalid_param,invalid_value", + [("degreeU", "not a number")], +) +def test_surface_invalid_construction( + sample_intervals: Tuple[Interval, Interval], + invalid_param: str, + invalid_value: Any, +): + domain_u, domain_v = sample_intervals + + valid_params = { + "degreeU": 1, + "degreeV": 1, + "rational": True, + "pointData": [0.0, 0.0, 0.0, 1.0], + "countU": 1, + "countV": 1, + "knotsU": [0.0, 1.0], + "knotsV": [0.0, 1.0], + "domainU": domain_u, + "domainV": domain_v, + "closedU": False, + "closedV": False, + "units": Units.m, + } + valid_params[invalid_param] = invalid_value + + with pytest.raises(SpeckleException): + Surface(**valid_params) diff --git a/uv.lock b/uv.lock index d98f4d4d..103c5973 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.10.0, <4.0" [[package]] name = "annotated-types" version = "0.7.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "anyio" version = "4.8.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, @@ -28,7 +28,7 @@ wheels = [ [[package]] name = "appdirs" version = "1.4.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470 } wheels = [ { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566 }, @@ -37,7 +37,7 @@ wheels = [ [[package]] name = "argcomplete" version = "3.5.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/0c/be/6c23d80cb966fb8f83fb1ebfb988351ae6b0554d0c3a613ee4531c026597/argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392", size = 72999 } wheels = [ { url = "https://files.pythonhosted.org/packages/c4/08/2a4db06ec3d203124c967fc89295e85a202e5cbbcdc08fd6a64b65217d1e/argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61", size = 43569 }, @@ -46,7 +46,7 @@ wheels = [ [[package]] name = "asttokens" version = "2.4.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "six" }, ] @@ -58,7 +58,7 @@ wheels = [ [[package]] name = "attrs" version = "24.3.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 } wheels = [ { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, @@ -67,7 +67,7 @@ wheels = [ [[package]] name = "backoff" version = "2.2.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } wheels = [ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, @@ -76,7 +76,7 @@ wheels = [ [[package]] name = "backports-tarfile" version = "1.2.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406 } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181 }, @@ -85,7 +85,7 @@ wheels = [ [[package]] name = "certifi" version = "2024.12.14" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } wheels = [ { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, @@ -94,7 +94,7 @@ wheels = [ [[package]] name = "cffi" version = "1.17.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "pycparser" }, ] @@ -151,7 +151,7 @@ wheels = [ [[package]] name = "cfgv" version = "3.4.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, @@ -160,7 +160,7 @@ wheels = [ [[package]] name = "charset-normalizer" version = "3.4.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, @@ -221,7 +221,7 @@ wheels = [ [[package]] name = "click" version = "8.1.8" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] @@ -233,7 +233,7 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, @@ -242,7 +242,7 @@ wheels = [ [[package]] name = "commitizen" version = "4.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "argcomplete" }, { name = "charset-normalizer" }, @@ -264,7 +264,7 @@ wheels = [ [[package]] name = "coverage" version = "7.6.10" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/12/2a2a923edf4ddabdffed7ad6da50d96a5c126dae7b80a33df7310e329a1e/coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78", size = 207982 }, @@ -328,7 +328,7 @@ toml = [ [[package]] name = "cryptography" version = "44.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -357,7 +357,7 @@ wheels = [ [[package]] name = "decli" version = "0.6.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/3d/a0/a4658f93ecb589f479037b164dc13c68d108b50bf6594e54c820749f97ac/decli-0.6.2.tar.gz", hash = "sha256:36f71eb55fd0093895efb4f416ec32b7f6e00147dda448e3365cf73ceab42d6f", size = 7424 } wheels = [ { url = "https://files.pythonhosted.org/packages/bf/70/3ea48dc9e958d7d66c44c9944809181f1ca79aaef25703c023b5092d34ff/decli-0.6.2-py3-none-any.whl", hash = "sha256:2fc84106ce9a8f523ed501ca543bdb7e416c064917c12a59ebdc7f311a97b7ed", size = 7854 }, @@ -366,7 +366,7 @@ wheels = [ [[package]] name = "deprecated" version = "1.2.15" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "wrapt" }, ] @@ -378,7 +378,7 @@ wheels = [ [[package]] name = "devtools" version = "0.12.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "asttokens" }, { name = "executing" }, @@ -392,7 +392,7 @@ wheels = [ [[package]] name = "distlib" version = "0.3.9" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, @@ -401,7 +401,7 @@ wheels = [ [[package]] name = "exceptiongroup" version = "1.2.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, @@ -410,7 +410,7 @@ wheels = [ [[package]] name = "executing" version = "2.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } wheels = [ { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, @@ -419,7 +419,7 @@ wheels = [ [[package]] name = "filelock" version = "3.16.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, @@ -428,7 +428,7 @@ wheels = [ [[package]] name = "gql" version = "3.5.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "anyio" }, { name = "backoff" }, @@ -452,7 +452,7 @@ websockets = [ [[package]] name = "graphql-core" version = "3.2.5" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/2e/b5/ebc6fe3852e2d2fdaf682dddfc366934f3d2c9ef9b6d1b0e6ca348d936ba/graphql_core-3.2.5.tar.gz", hash = "sha256:e671b90ed653c808715645e3998b7ab67d382d55467b7e2978549111bbabf8d5", size = 504664 } wheels = [ { url = "https://files.pythonhosted.org/packages/e3/dc/078bd6b304de790618ebb95e2aedaadb78f4527ac43a9ad8815f006636b6/graphql_core-3.2.5-py3-none-any.whl", hash = "sha256:2f150d5096448aa4f8ab26268567bbfeef823769893b39c1a2e1409590939c8a", size = 203189 }, @@ -461,7 +461,7 @@ wheels = [ [[package]] name = "h11" version = "0.14.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, @@ -470,7 +470,7 @@ wheels = [ [[package]] name = "hatch" version = "1.14.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "click" }, { name = "hatchling" }, @@ -497,7 +497,7 @@ wheels = [ [[package]] name = "hatch-vcs" version = "0.4.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "hatchling" }, { name = "setuptools-scm" }, @@ -510,7 +510,7 @@ wheels = [ [[package]] name = "hatchling" version = "1.27.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "packaging" }, { name = "pathspec" }, @@ -526,7 +526,7 @@ wheels = [ [[package]] name = "httpcore" version = "1.0.7" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "certifi" }, { name = "h11" }, @@ -539,7 +539,7 @@ wheels = [ [[package]] name = "httpx" version = "0.28.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "anyio" }, { name = "certifi" }, @@ -554,7 +554,7 @@ wheels = [ [[package]] name = "hyperlink" version = "21.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "idna" }, ] @@ -566,7 +566,7 @@ wheels = [ [[package]] name = "identify" version = "2.6.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/82/bf/c68c46601bacd4c6fb4dd751a42b6e7087240eaabc6487f2ef7a48e0e8fc/identify-2.6.6.tar.gz", hash = "sha256:7bec12768ed44ea4761efb47806f0a41f86e7c0a5fdf5950d4648c90eca7e251", size = 99217 } wheels = [ { url = "https://files.pythonhosted.org/packages/74/a1/68a395c17eeefb04917034bd0a1bfa765e7654fa150cca473d669aa3afb5/identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881", size = 99083 }, @@ -575,7 +575,7 @@ wheels = [ [[package]] name = "idna" version = "3.10" -source = { registry = "https://pypi.org/simple" } +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 }, @@ -584,7 +584,7 @@ wheels = [ [[package]] name = "importlib-metadata" version = "8.6.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "zipp" }, ] @@ -596,7 +596,7 @@ wheels = [ [[package]] name = "iniconfig" version = "2.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, @@ -605,7 +605,7 @@ wheels = [ [[package]] name = "jaraco-classes" version = "3.4.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "more-itertools" }, ] @@ -617,7 +617,7 @@ wheels = [ [[package]] name = "jaraco-context" version = "6.0.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] @@ -629,7 +629,7 @@ wheels = [ [[package]] name = "jaraco-functools" version = "4.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "more-itertools" }, ] @@ -641,7 +641,7 @@ wheels = [ [[package]] name = "jeepney" version = "0.8.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d6/f4/154cf374c2daf2020e05c3c6a03c91348d59b23c5366e968feb198306fdf/jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806", size = 106005 } wheels = [ { url = "https://files.pythonhosted.org/packages/ae/72/2a1e2290f1ab1e06f71f3d0f1646c9e4634e70e1d37491535e19266e8dc9/jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755", size = 48435 }, @@ -650,7 +650,7 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.5" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "markupsafe" }, ] @@ -662,7 +662,7 @@ wheels = [ [[package]] name = "keyring" version = "25.6.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, { name = "jaraco-classes" }, @@ -680,7 +680,7 @@ wheels = [ [[package]] name = "markdown-it-py" version = "3.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "mdurl" }, ] @@ -692,7 +692,7 @@ wheels = [ [[package]] name = "markupsafe" version = "3.0.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } wheels = [ { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, @@ -750,7 +750,7 @@ wheels = [ [[package]] name = "mdurl" version = "0.1.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, @@ -759,7 +759,7 @@ wheels = [ [[package]] name = "more-itertools" version = "10.6.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/88/3b/7fa1fe835e2e93fd6d7b52b2f95ae810cf5ba133e1845f726f5a992d62c2/more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", size = 125009 } wheels = [ { url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 }, @@ -768,7 +768,7 @@ wheels = [ [[package]] name = "multidict" version = "6.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] @@ -840,7 +840,7 @@ wheels = [ [[package]] name = "nodeenv" version = "1.9.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, @@ -849,7 +849,7 @@ wheels = [ [[package]] name = "packaging" version = "24.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, @@ -858,7 +858,7 @@ wheels = [ [[package]] name = "pathspec" version = "0.12.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, @@ -867,7 +867,7 @@ wheels = [ [[package]] name = "pexpect" version = "4.9.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "ptyprocess" }, ] @@ -879,7 +879,7 @@ wheels = [ [[package]] name = "platformdirs" version = "4.3.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, @@ -888,7 +888,7 @@ wheels = [ [[package]] name = "pluggy" version = "1.5.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, @@ -897,7 +897,7 @@ wheels = [ [[package]] name = "pre-commit" version = "4.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cfgv" }, { name = "identify" }, @@ -913,7 +913,7 @@ wheels = [ [[package]] name = "prompt-toolkit" version = "3.0.50" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "wcwidth" }, ] @@ -925,7 +925,7 @@ wheels = [ [[package]] name = "propcache" version = "0.2.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a5/0ea64c9426959ef145a938e38c832fc551843481d356713ececa9a8a64e8/propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6", size = 79296 }, @@ -998,7 +998,7 @@ wheels = [ [[package]] name = "ptyprocess" version = "0.7.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, @@ -1007,7 +1007,7 @@ wheels = [ [[package]] name = "pycparser" version = "2.22" -source = { registry = "https://pypi.org/simple" } +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 }, @@ -1016,7 +1016,7 @@ wheels = [ [[package]] name = "pydantic" version = "2.10.5" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, @@ -1030,7 +1030,7 @@ wheels = [ [[package]] name = "pydantic-core" version = "2.27.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "typing-extensions" }, ] @@ -1105,7 +1105,7 @@ wheels = [ [[package]] name = "pydantic-settings" version = "2.7.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, @@ -1118,7 +1118,7 @@ wheels = [ [[package]] name = "pygments" version = "2.19.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, @@ -1127,7 +1127,7 @@ wheels = [ [[package]] name = "pytest" version = "8.3.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, @@ -1144,7 +1144,7 @@ wheels = [ [[package]] name = "pytest-asyncio" version = "0.25.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "pytest" }, ] @@ -1156,7 +1156,7 @@ wheels = [ [[package]] name = "pytest-cov" version = "6.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, @@ -1169,7 +1169,7 @@ wheels = [ [[package]] name = "pytest-ordering" version = "0.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "pytest" }, ] @@ -1181,7 +1181,7 @@ wheels = [ [[package]] name = "python-dotenv" version = "1.0.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } wheels = [ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, @@ -1190,7 +1190,7 @@ wheels = [ [[package]] name = "pywin32-ctypes" version = "0.2.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } wheels = [ { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, @@ -1199,7 +1199,7 @@ wheels = [ [[package]] name = "pyyaml" version = "6.0.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, @@ -1243,7 +1243,7 @@ wheels = [ [[package]] name = "questionary" version = "2.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "prompt-toolkit" }, ] @@ -1255,7 +1255,7 @@ wheels = [ [[package]] name = "requests" version = "2.32.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, @@ -1270,7 +1270,7 @@ wheels = [ [[package]] name = "requests-toolbelt" version = "1.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "requests" }, ] @@ -1282,7 +1282,7 @@ wheels = [ [[package]] name = "rich" version = "13.9.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, @@ -1296,7 +1296,7 @@ wheels = [ [[package]] name = "ruff" version = "0.9.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/80/63/77ecca9d21177600f551d1c58ab0e5a0b260940ea7312195bd2a4798f8a8/ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0", size = 3553799 } wheels = [ { url = "https://files.pythonhosted.org/packages/af/b9/0e168e4e7fb3af851f739e8f07889b91d1a33a30fca8c29fa3149d6b03ec/ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347", size = 11652408 }, @@ -1321,7 +1321,7 @@ wheels = [ [[package]] name = "secretstorage" version = "3.3.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cryptography" }, { name = "jeepney" }, @@ -1334,7 +1334,7 @@ wheels = [ [[package]] name = "setuptools" version = "75.8.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/92/ec/089608b791d210aec4e7f97488e67ab0d33add3efccb83a056cbafe3a2a6/setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6", size = 1343222 } wheels = [ { url = "https://files.pythonhosted.org/packages/69/8a/b9dc7678803429e4a3bc9ba462fa3dd9066824d3c607490235c6a796be5a/setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3", size = 1228782 }, @@ -1343,7 +1343,7 @@ wheels = [ [[package]] name = "setuptools-scm" version = "8.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "packaging" }, { name = "setuptools" }, @@ -1357,7 +1357,7 @@ wheels = [ [[package]] name = "shellingham" version = "1.5.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, @@ -1366,7 +1366,7 @@ wheels = [ [[package]] name = "six" version = "1.17.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, @@ -1375,7 +1375,7 @@ wheels = [ [[package]] name = "sniffio" version = "1.3.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, @@ -1383,6 +1383,7 @@ wheels = [ [[package]] name = "specklepy" +version = "2.21.3.dev74" source = { editable = "." } dependencies = [ { name = "appdirs" }, @@ -1446,13 +1447,13 @@ dev = [ [[package]] name = "stringcase" version = "1.2.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/f3/1f/1241aa3d66e8dc1612427b17885f5fcd9c9ee3079fc0d28e9a3aeeb36fa3/stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008", size = 2958 } [[package]] name = "termcolor" version = "2.5.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } wheels = [ { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, @@ -1461,7 +1462,7 @@ wheels = [ [[package]] name = "tomli" version = "2.2.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, @@ -1500,7 +1501,7 @@ wheels = [ [[package]] name = "tomli-w" version = "1.2.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184 } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675 }, @@ -1509,7 +1510,7 @@ wheels = [ [[package]] name = "tomlkit" version = "0.13.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, @@ -1518,7 +1519,7 @@ wheels = [ [[package]] name = "trove-classifiers" version = "2025.1.15.22" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/f3/cb/8f6a91c74049180e395590901834d68bef5d6a2ce4c9ca9792cfadc1b9b4/trove_classifiers-2025.1.15.22.tar.gz", hash = "sha256:90af74358d3a01b3532bc7b3c88d8c6a094c2fd50a563d13d9576179326d7ed9", size = 16236 } wheels = [ { url = "https://files.pythonhosted.org/packages/2b/c5/6422dbc59954389b20b2aba85b737ab4a552e357e7ea14b52f40312e7c84/trove_classifiers-2025.1.15.22-py3-none-any.whl", hash = "sha256:5f19c789d4f17f501d36c94dbbf969fb3e8c2784d008e6f5164dd2c3d6a2b07c", size = 13610 }, @@ -1527,7 +1528,7 @@ wheels = [ [[package]] name = "types-deprecated" version = "1.2.15.20241117" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/a8/76/d3735190891b12533115e73ac835cfdd1f28378b6b39fd50dfe2fbd63143/types-Deprecated-1.2.15.20241117.tar.gz", hash = "sha256:924002c8b7fddec51ba4949788a702411a2e3636cd9b2a33abd8ee119701d77e", size = 3377 } wheels = [ { url = "https://files.pythonhosted.org/packages/27/ed/9091bd7a90d3e2e08ee8c0bdbf0c826d3d9e3730ddd9b15cb64f4ae51b9b/types_Deprecated-1.2.15.20241117-py3-none-any.whl", hash = "sha256:a0cc5e39f769fc54089fd8e005416b55d74aa03f6964d2ed1a0b0b2e28751884", size = 3779 }, @@ -1536,7 +1537,7 @@ wheels = [ [[package]] name = "types-requests" version = "2.32.0.20241016" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "urllib3" }, ] @@ -1548,7 +1549,7 @@ wheels = [ [[package]] name = "types-ujson" version = "5.10.0.20240515" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/12/49/abb4bcb9f2258f785edbf236b517c3e7ba8a503a8cbce6b5895930586cc0/types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7", size = 3571 } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/1f/9d018cee3d09ab44a5211f0b5ed9b0422ad9a8c226bf3967f5884498d8f0/types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310", size = 2757 }, @@ -1557,7 +1558,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.12.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, @@ -1566,7 +1567,7 @@ wheels = [ [[package]] name = "ujson" version = "5.10.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/f0/00/3110fd566786bfa542adb7932d62035e0c0ef662a8ff6544b6643b3d6fd7/ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1", size = 7154885 } wheels = [ { url = "https://files.pythonhosted.org/packages/7d/91/91678e49a9194f527e60115db84368c237ac7824992224fac47dcb23a5c6/ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd", size = 55354 }, @@ -1620,7 +1621,7 @@ wheels = [ [[package]] name = "urllib3" version = "2.3.0" -source = { registry = "https://pypi.org/simple" } +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 }, @@ -1629,7 +1630,7 @@ wheels = [ [[package]] name = "userpath" version = "1.9.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "click" }, ] @@ -1641,7 +1642,7 @@ wheels = [ [[package]] name = "uv" version = "0.5.29" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/66/7c/1b7673d336f6d5f5d8ce568638cf9961303322a35d3d491ab3eb368302ef/uv-0.5.29.tar.gz", hash = "sha256:05b6c8132b4054a83596aa0d85720649c6c8029188ea03f014c4bcfa77003c74", size = 2726211 } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/4a/db8f6f994fdfcf2239f5c03c783a90ac916ef233611389f12f2c3410b1a8/uv-0.5.29-py3-none-linux_armv6l.whl", hash = "sha256:9f5fc05f3848e16a90fc9cebe2897d3d4de42f8cf2ec312b4efef45a520d54e9", size = 15399105 }, @@ -1666,7 +1667,7 @@ wheels = [ [[package]] name = "virtualenv" version = "20.29.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "distlib" }, { name = "filelock" }, @@ -1680,7 +1681,7 @@ wheels = [ [[package]] name = "wcwidth" version = "0.2.13" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, @@ -1689,7 +1690,7 @@ wheels = [ [[package]] name = "websockets" version = "11.0.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/d8/3b/2ed38e52eed4cf277f9df5f0463a99199a04d9e29c9e227cfafa57bd3993/websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016", size = 104235 } wheels = [ { url = "https://files.pythonhosted.org/packages/e1/76/88640f8aeac7eb0d058b913e7bb72682f8d569db44c7d30e576ec4777ce1/websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac", size = 123714 }, @@ -1720,7 +1721,7 @@ wheels = [ [[package]] name = "wrapt" version = "1.17.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } wheels = [ { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307 }, @@ -1784,7 +1785,7 @@ wheels = [ [[package]] name = "yarl" version = "1.18.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "idna" }, { name = "multidict" }, @@ -1862,7 +1863,7 @@ wheels = [ [[package]] name = "zipp" version = "3.21.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, @@ -1871,7 +1872,7 @@ wheels = [ [[package]] name = "zstandard" version = "0.23.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, ]