Compare commits

...

6 Commits

Author SHA1 Message Date
Jedd Morgan f4863a89d8 Trying my best to clean up the mess 2026-04-14 18:06:03 +01:00
Jedd Morgan 856f12e57c Don't type check unions recursivly 2026-04-14 16:05:27 +01:00
Jedd Morgan 10e639d19a fail fast false 2026-04-07 20:24:50 +01:00
Jedd Morgan c209cdaec4 test public too 2026-04-07 18:51:17 +01:00
Jedd Morgan 169dd00fac Skip broken test 2026-04-07 18:50:34 +01:00
Jonathon Broughton 58190c378a Add Python version 3.14 to CI workflow 2026-03-30 18:18:31 +01:00
6 changed files with 44 additions and 39 deletions
+4
View File
@@ -13,12 +13,14 @@ jobs:
name: Test (internal) name: Test (internal)
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
python-version: python-version:
- "3.10" - "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13" - "3.13"
- "3.14"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -62,12 +64,14 @@ jobs:
env: env:
IS_PUBLIC: "true" IS_PUBLIC: "true"
strategy: strategy:
fail-fast: false
matrix: matrix:
python-version: python-version:
- "3.10" - "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13" - "3.13"
- "3.14"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+31 -30
View File
@@ -1,7 +1,7 @@
import contextlib
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from inspect import isclass from inspect import isclass
from types import UnionType
from typing import ( from typing import (
Any, Any,
ClassVar, ClassVar,
@@ -13,11 +13,13 @@ from typing import (
Tuple, Tuple,
Type, Type,
Union, Union,
get_origin,
get_type_hints, get_type_hints,
) )
from warnings import warn from warnings import warn
from pydantic.alias_generators import to_pascal from pydantic.alias_generators import to_pascal
from typing_extensions import get_args
from specklepy.logging.exceptions import SpeckleException from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.memory import MemoryTransport from specklepy.transports.memory import MemoryTransport
@@ -220,32 +222,28 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
if value in t._value2member_map_: if value in t._value2member_map_:
return True, t(value) return True, t(value)
if getattr(t, "__module__", None) == "typing": if isinstance(t, ForwardRef):
if isinstance(t, ForwardRef): return True, value
return True, value
origin = t.__origin__ if getattr(t, "__module__", None) in ["typing", "types"]:
# below is what in nicer for >= py38 origin = get_origin(t)
# origin = get_origin(t) args = get_args(t)
# recursive validation for Unions on both types preferring the fist type # recursive validation for Unions on both types preferring the fist type
if origin is Union: if origin is Union or isinstance(t, UnionType):
# below is what in nicer for >= py38
# t_1, t_2 = get_args(t)
args = t.__args__ # type: ignore
for arg_t in args: for arg_t in args:
t_success, t_value = _validate_type(arg_t, value) ok, v = _validate_type(arg_t, value)
if t_success: if ok:
return True, t_value return True, v
return False, value return False, value
if origin is dict: if origin is dict:
if not isinstance(value, dict): if not isinstance(value, dict):
return False, value return False, value
if value == {}: if not value:
return True, value return True, value
if not getattr(t, "__args__", None): if not args:
return True, value return True, value
t_key, t_value = t.__args__ # type: ignore t_key, t_value = args
if ( if (
getattr(t_key, "__name__", None), getattr(t_key, "__name__", None),
@@ -265,11 +263,11 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
if origin is list: if origin is list:
if not isinstance(value, list): if not isinstance(value, list):
return False, value return False, value
if value == []: if not value:
return True, value return True, value
if not hasattr(t, "__args__"): if not args:
return True, value return True, value
t_items = t.__args__[0] # type: ignore t_items = args[0]
if getattr(t_items, "__name__", None) == "T": if getattr(t_items, "__name__", None) == "T":
return True, value return True, value
first_item_valid, _ = _validate_type(t_items, value[0]) first_item_valid, _ = _validate_type(t_items, value[0])
@@ -280,10 +278,10 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
if origin is tuple: if origin is tuple:
if not isinstance(value, tuple): if not isinstance(value, tuple):
return False, value return False, value
if not hasattr(t, "__args__"): if not args:
return True, value return True, value
args = t.__args__ # type: ignore args = t.__args__ # type: ignore
if args == tuple(): if not args:
return True, value return True, value
# we're not checking for empty tuple, cause tuple lengths must match # we're not checking for empty tuple, cause tuple lengths must match
if len(args) != len(value): if len(args) != len(value):
@@ -299,7 +297,7 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
if origin is set: if origin is set:
if not isinstance(value, set): if not isinstance(value, set):
return False, value return False, value
if not hasattr(t, "__args__"): if not args:
return True, value return True, value
t_items = t.__args__[0] # type: ignore t_items = t.__args__[0] # type: ignore
first_item_valid, _ = _validate_type(t_items, next(iter(value))) first_item_valid, _ = _validate_type(t_items, next(iter(value)))
@@ -310,13 +308,16 @@ def _validate_type(t: Optional[type], value: Any) -> Tuple[bool, Any]:
if isinstance(value, t): if isinstance(value, t):
return True, value return True, value
with contextlib.suppress(ValueError, TypeError): if t is float and type(value) is int:
if t is float and value is not None: return True, float(value)
return True, float(value)
# TODO: dafuq, i had to add this not list check # with contextlib.suppress(ValueError, TypeError):
# but it would also fail for objects and other complex values # if t is float and value is not None:
if t is str and value and not isinstance(value, list): # return True, float(value)
return True, str(value) # # TODO: dafuq, i had to add this not list check
# # but it would also fail for objects and other complex values
# if t is str and value and not isinstance(value, list):
# return True, str(value)
return False, value return False, value
@@ -264,6 +264,7 @@ class TestIngestionResource:
with pytest.raises(GraphQLException): with pytest.raises(GraphQLException):
_ = client.model_ingestion.fail_with_error(input) _ = client.model_ingestion.fail_with_error(input)
@pytest.mark.skip(reason="TEST FAILS - server behaviour was changed")
def test_complete_non_existent_root_object( def test_complete_non_existent_root_object(
self, client: SpeckleClient, ingestion: ModelIngestion, project: Project self, client: SpeckleClient, ingestion: ModelIngestion, project: Project
): ):
+2 -2
View File
@@ -35,7 +35,7 @@ def sample_text_all_properties(sample_point: Point, sample_plane: Plane) -> Text
alignmentH=AlignmentHorizontal.Center, alignmentH=AlignmentHorizontal.Center,
alignmentV=AlignmentVertical.Center, alignmentV=AlignmentVertical.Center,
plane=sample_plane, plane=sample_plane,
maxWidth=20, maxWidth=20.0,
units=Units.m, units=Units.m,
) )
@@ -56,7 +56,7 @@ def test_text_creation_minimal(sample_point: Point):
def test_text_creation_extended(sample_point: Point, sample_plane: Plane): def test_text_creation_extended(sample_point: Point, sample_plane: Plane):
text_value = "text" text_value = "text"
max_width = 20 max_width = 20.0
text_obj = Text( text_obj = Text(
value=text_value, value=text_value,
+1 -1
View File
@@ -143,7 +143,7 @@ def test_type_checking() -> None:
order = FrozenYoghurt() order = FrozenYoghurt()
order.servings = 2 order.servings = 2
order.price = "7" # type: ignore - it will get converted order.price = 7
order.customer = "izzy" order.customer = "izzy"
order.dietary = DietaryRestrictions.VEGAN order.dietary = DietaryRestrictions.VEGAN
order.tag = "preorder" order.tag = "preorder"
+5 -6
View File
@@ -31,13 +31,13 @@ fake_bases = [FakeBase("foo"), FakeBase("bar")]
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input_type, value, is_valid, return_value", "input_type, value, is_valid, return_value",
[ [
(str, 10, True, "10"), (str, 10, False, 10),
(str, "foo_bar", True, "foo_bar"), (str, "foo_bar", True, "foo_bar"),
( (
str, str,
{"foo": "bar"}, {"foo": "bar"},
True, False,
"{'foo': 'bar'}", {"foo": "bar"},
), ),
(float, 1, True, 1), (float, 1, True, 1),
# why are we allowing this??? We're lying to our users and ourselves too. # why are we allowing this??? We're lying to our users and ourselves too.
@@ -85,9 +85,8 @@ fake_bases = [FakeBase("foo"), FakeBase("bar")]
(Dict[int, Base], {1: test_base}, True, {1: test_base}), (Dict[int, Base], {1: test_base}, True, {1: test_base}),
(Tuple[int, str, str], (1, "foo", "bar"), True, (1, "foo", "bar")), (Tuple[int, str, str], (1, "foo", "bar"), True, (1, "foo", "bar")),
(Tuple, (1, "foo", "bar"), True, (1, "foo", "bar")), (Tuple, (1, "foo", "bar"), True, (1, "foo", "bar")),
# given our current rules, this is the reality. Its just sad... (Tuple[str, str, str], (1, "foo", "bar"), False, (1, "foo", "bar")),
(Tuple[str, str, str], (1, "foo", "bar"), True, ("1", "foo", "bar")), (Tuple[str, Optional[str], str], (1, None, "bar"), False, (1, None, "bar")),
(Tuple[str, Optional[str], str], (1, None, "bar"), True, ("1", None, "bar")),
(Set[bool], set([1, 2]), False, set([1, 2])), (Set[bool], set([1, 2]), False, set([1, 2])),
(Set[int], set([1, 2]), True, set([1, 2])), (Set[int], set([1, 2]), True, set([1, 2])),
(Set[int], set([None, 2]), True, set([None, 2])), (Set[int], set([None, 2]), True, set([None, 2])),