ducktools-classbuilder 0.10.1__tar.gz → 0.10.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ducktools-classbuilder might be problematic. Click here for more details.

Files changed (79) hide show
  1. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/PKG-INFO +1 -1
  2. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/__init__.py +26 -5
  3. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/__init__.pyi +8 -4
  4. ducktools_classbuilder-0.10.2/src/ducktools/classbuilder/_version.py +2 -0
  5. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/annotations.py +32 -2
  6. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/annotations.pyi +5 -0
  7. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/prefab.py +18 -23
  8. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools_classbuilder.egg-info/PKG-INFO +1 -1
  9. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools_classbuilder.egg-info/SOURCES.txt +3 -1
  10. ducktools_classbuilder-0.10.2/tests/py314_tests/test_init_signature.py +122 -0
  11. ducktools_classbuilder-0.10.2/tests/py314_tests/test_init_signature_future.py +105 -0
  12. ducktools_classbuilder-0.10.1/src/ducktools/classbuilder/_version.py +0 -2
  13. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.github/dependabot.yml +0 -0
  14. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.github/workflows/auto_test.yml +0 -0
  15. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.github/workflows/publish_to_pypi.yml +0 -0
  16. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.github/workflows/publish_to_testpypi.yml +0 -0
  17. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.gitignore +0 -0
  18. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/.readthedocs.yaml +0 -0
  19. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/LICENSE +0 -0
  20. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/MANIFEST.in +0 -0
  21. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/README.md +0 -0
  22. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/Makefile +0 -0
  23. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/api.md +0 -0
  24. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/approach_vs_tool.md +0 -0
  25. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/conf.py +0 -0
  26. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/extension_examples.md +0 -0
  27. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/generated_code.md +0 -0
  28. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/index.md +0 -0
  29. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/make.bat +0 -0
  30. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/perf/performance_tests.md +0 -0
  31. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/prefab/index.md +0 -0
  32. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs/tutorial.md +0 -0
  33. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex10_frozen_attributes.py +0 -0
  34. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex1_basic.py +0 -0
  35. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex2_register.py +0 -0
  36. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex3_iterable.py +0 -0
  37. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex5_frozen.py +0 -0
  38. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex7_posonly.py +0 -0
  39. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex8_converters.py +0 -0
  40. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/docs_ex9_annotated.py +0 -0
  41. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/index_example.py +0 -0
  42. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/docs_code/tutorial_code.py +0 -0
  43. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/pyproject.toml +0 -0
  44. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/setup.cfg +0 -0
  45. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/prefab.pyi +0 -0
  46. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools/classbuilder/py.typed +0 -0
  47. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools_classbuilder.egg-info/dependency_links.txt +0 -0
  48. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools_classbuilder.egg-info/requires.txt +0 -0
  49. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/src/ducktools_classbuilder.egg-info/top_level.txt +0 -0
  50. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/annotations/test_annotated.py +0 -0
  51. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/annotations/test_annotations_module.py +0 -0
  52. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/annotations/test_future_annotations.py +0 -0
  53. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/conftest.py +0 -0
  54. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/helpers/utils.py +0 -0
  55. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_compare_attrib.py +0 -0
  56. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_construction.py +0 -0
  57. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_creation.py +0 -0
  58. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_dunders.py +0 -0
  59. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_frozen.py +0 -0
  60. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_funcs.py +0 -0
  61. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_hint_syntax.py +0 -0
  62. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_inheritance.py +0 -0
  63. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_init.py +0 -0
  64. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_internals_dict.py +0 -0
  65. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_kw_only.py +0 -0
  66. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_pre_post_init.py +0 -0
  67. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_private.py +0 -0
  68. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_replace.py +0 -0
  69. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_repr.py +0 -0
  70. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_slots_novalues.py +0 -0
  71. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_slotted_class.py +0 -0
  72. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/prefab/test_subclass_implementation.py +0 -0
  73. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/py312_tests/test_generic_annotations.py +0 -0
  74. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/py314_tests/_test_support.py +0 -0
  75. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/py314_tests/test_forwardref_annotations.py +0 -0
  76. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/test_core.py +0 -0
  77. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/test_field_flags.py +0 -0
  78. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/tests/test_slotmakermeta.py +0 -0
  79. {ducktools_classbuilder-0.10.1 → ducktools_classbuilder-0.10.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.10.1
3
+ Version: 0.10.2
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
@@ -31,8 +31,9 @@
31
31
  # Field itself sidesteps this by defining __slots__ to avoid that branch.
32
32
 
33
33
  import os
34
+ import sys
34
35
 
35
- from .annotations import get_ns_annotations, is_classvar
36
+ from .annotations import get_ns_annotations, is_classvar, make_annotate_func
36
37
  from ._version import __version__, __version_tuple__ # noqa: F401
37
38
 
38
39
  # Change this name if you make heavy modifications
@@ -131,19 +132,25 @@ class GeneratedCode:
131
132
  This class provides a return value for the generated output from source code
132
133
  generators.
133
134
  """
134
- __slots__ = ("source_code", "globs")
135
+ __slots__ = ("source_code", "globs", "annotations")
135
136
 
136
- def __init__(self, source_code, globs):
137
+ def __init__(self, source_code, globs, annotations=None):
137
138
  self.source_code = source_code
138
139
  self.globs = globs
140
+ self.annotations = annotations
139
141
 
140
142
  def __repr__(self):
141
143
  first_source_line = self.source_code.split("\n")[0]
142
- return f"GeneratorOutput(source_code='{first_source_line} ...', globs={self.globs!r})"
144
+ return (
145
+ f"GeneratorOutput(source_code='{first_source_line} ...', "
146
+ f"globs={self.globs!r}, annotations={self.annotations!r})"
147
+ )
143
148
 
144
149
  def __eq__(self, other):
145
150
  if self.__class__ is other.__class__:
146
- return (self.source_code, self.globs) == (other.source_code, other.globs)
151
+ return (self.source_code, self.globs, self.annotations) == (
152
+ other.source_code, other.globs, other.annotations
153
+ )
147
154
  return NotImplemented
148
155
 
149
156
 
@@ -199,6 +206,20 @@ class MethodMaker:
199
206
  # descriptor. Don't try to rename.
200
207
  pass
201
208
 
209
+ # Apply annotations
210
+ if gen.annotations is not None:
211
+ if sys.version_info >= (3, 14):
212
+ # If __annotations__ exists on the class, either they
213
+ # are user defined or they are using __future__ annotations.
214
+ # In this case, just write __annotations__
215
+ if "__annotations__" in gen_cls.__dict__:
216
+ method.__annotations__ = gen.annotations
217
+ else:
218
+ anno_func = make_annotate_func(gen.annotations)
219
+ method.__annotate__ = anno_func
220
+ else:
221
+ method.__annotations__ = gen.annotations
222
+
202
223
  # Replace this descriptor on the class with the generated function
203
224
  setattr(gen_cls, self.funcname, method)
204
225
 
@@ -40,16 +40,20 @@ class KW_ONLY(metaclass=_KW_ONLY_META): ...
40
40
  class _CodegenType(typing.Protocol):
41
41
  def __call__(self, cls: type, funcname: str = ...) -> GeneratedCode: ...
42
42
 
43
-
44
43
  class GeneratedCode:
45
- __slots__: tuple[str, str]
44
+ __slots__: tuple[str, ...]
46
45
  source_code: str
47
46
  globs: dict[str, typing.Any]
47
+ annotations: dict[str, typing.Any]
48
48
 
49
- def __init__(self, source_code: str, globs: dict[str, typing.Any]) -> None: ...
49
+ def __init__(
50
+ self,
51
+ source_code: str,
52
+ globs: dict[str, typing.Any],
53
+ annotations: dict[str, typing.Any] | None = ...,
54
+ ) -> None: ...
50
55
  def __repr__(self) -> str: ...
51
56
 
52
-
53
57
  class MethodMaker:
54
58
  funcname: str
55
59
  code_generator: _CodegenType
@@ -0,0 +1,2 @@
1
+ __version__ = "0.10.2"
2
+ __version_tuple__ = (0, 10, 2)
@@ -24,9 +24,9 @@ import sys
24
24
 
25
25
  class _LazyAnnotationLib:
26
26
  def __getattr__(self, item):
27
- global _lazyannotationlib
27
+ global _lazy_annotationlib
28
28
  import annotationlib # type: ignore
29
- _lazyannotationlib = annotationlib
29
+ _lazy_annotationlib = annotationlib
30
30
  return getattr(annotationlib, item)
31
31
 
32
32
 
@@ -83,6 +83,36 @@ def get_ns_annotations(ns):
83
83
  return annotations
84
84
 
85
85
 
86
+ def make_annotate_func(annos):
87
+ # Only used in 3.14 or later so no sys.version_info gate
88
+
89
+ type_repr = _lazy_annotationlib.type_repr
90
+ Format = _lazy_annotationlib.Format
91
+ ForwardRef = _lazy_annotationlib.ForwardRef
92
+ # Construct an annotation function from __annotations__
93
+ def annotate_func(format, /):
94
+ match format:
95
+ case Format.VALUE | Format.FORWARDREF:
96
+ return {
97
+ k: v.evaluate(format=format)
98
+ if isinstance(v, ForwardRef) else v
99
+ for k, v in annos.items()
100
+ }
101
+ case Format.STRING:
102
+ string_annos = {}
103
+ for k, v in annos.items():
104
+ if isinstance(v, str):
105
+ string_annos[k] = v
106
+ elif isinstance(v, ForwardRef):
107
+ string_annos[k] = v.evaluate(format=Format.STRING)
108
+ else:
109
+ string_annos[k] = type_repr(v)
110
+ return string_annos
111
+ case _:
112
+ raise NotImplementedError(format)
113
+ return annotate_func
114
+
115
+
86
116
  def is_classvar(hint):
87
117
  if isinstance(hint, str):
88
118
  # String annotations, just check if the string 'ClassVar' is in there
@@ -1,3 +1,4 @@
1
+ from collections.abc import Callable
1
2
  import typing
2
3
  import types
3
4
 
@@ -11,6 +12,10 @@ def get_ns_annotations(
11
12
  ns: _CopiableMappings,
12
13
  ) -> dict[str, typing.Any]: ...
13
14
 
15
+ def make_annotate_func(
16
+ annos: dict[str, typing.Any]
17
+ ) -> Callable[[int], dict[str, typing.Any]]: ...
18
+
14
19
  def is_classvar(
15
20
  hint: object,
16
21
  ) -> bool: ...
@@ -64,6 +64,7 @@ def get_attributes(cls):
64
64
  # Method Generators
65
65
  def init_generator(cls, funcname="__init__"):
66
66
  globs = {}
67
+ annotations = {}
67
68
  # Get the internals dictionary and prepare attributes
68
69
  attributes = get_attributes(cls)
69
70
  flags = get_flags(cls)
@@ -106,41 +107,29 @@ def init_generator(cls, funcname="__init__"):
106
107
  kw_only_arglist = []
107
108
  for name, attrib in attributes.items():
108
109
  # post_init annotations can be used to broaden types.
109
- if name in post_init_annotations:
110
- globs[f"_{name}_type"] = post_init_annotations[name]
111
- elif attrib.type is not NOTHING:
112
- globs[f"_{name}_type"] = attrib.type
113
-
114
110
  if attrib.init:
111
+ if name in post_init_annotations:
112
+ annotations[name] = post_init_annotations[name]
113
+ elif attrib.type is not NOTHING:
114
+ annotations[name] = attrib.type
115
+
115
116
  if attrib.default is not NOTHING:
116
117
  if isinstance(attrib.default, (str, int, float, bool)):
117
118
  # Just use the literal in these cases
118
- if attrib.type is NOTHING:
119
- arg = f"{name}={attrib.default!r}"
120
- else:
121
- arg = f"{name}: _{name}_type = {attrib.default!r}"
119
+ arg = f"{name}={attrib.default!r}"
122
120
  else:
123
121
  # No guarantee repr will work for other objects
124
122
  # so store the value in a variable and put it
125
123
  # in the globals dict for eval
126
- if attrib.type is NOTHING:
127
- arg = f"{name}=_{name}_default"
128
- else:
129
- arg = f"{name}: _{name}_type = _{name}_default"
124
+ arg = f"{name}=_{name}_default"
130
125
  globs[f"_{name}_default"] = attrib.default
131
126
  elif attrib.default_factory is not NOTHING:
132
127
  # Use NONE here and call the factory later
133
128
  # This matches the behaviour of compiled
134
- if attrib.type is NOTHING:
135
- arg = f"{name}=None"
136
- else:
137
- arg = f"{name}: _{name}_type = None"
129
+ arg = f"{name}=None"
138
130
  globs[f"_{name}_factory"] = attrib.default_factory
139
131
  else:
140
- if attrib.type is NOTHING:
141
- arg = name
142
- else:
143
- arg = f"{name}: _{name}_type"
132
+ arg = name
144
133
  if attrib.kw_only or kw_only:
145
134
  kw_only_arglist.append(arg)
146
135
  else:
@@ -206,13 +195,19 @@ def init_generator(cls, funcname="__init__"):
206
195
  post_init_call = ""
207
196
 
208
197
  code = (
209
- f"def {funcname}(self, {args}) -> None:\n"
198
+ f"def {funcname}(self, {args}):\n"
210
199
  f"{pre_init_call}\n"
211
200
  f"{body}\n"
212
201
  f"{post_init_call}\n"
213
202
  )
214
203
 
215
- return GeneratedCode(code, globs)
204
+ if annotations:
205
+ annotations["return"] = None
206
+ else:
207
+ # If there are no annotations, return an unannotated init function
208
+ annotations = None
209
+
210
+ return GeneratedCode(code, globs, annotations)
216
211
 
217
212
 
218
213
  def iter_generator(cls, funcname="__iter__"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ducktools-classbuilder
3
- Version: 0.10.1
3
+ Version: 0.10.2
4
4
  Summary: Toolkit for creating class boilerplate generators
5
5
  Author: David C Ellis
6
6
  Project-URL: Homepage, https://github.com/davidcellis/ducktools-classbuilder
@@ -71,4 +71,6 @@ tests/prefab/test_slotted_class.py
71
71
  tests/prefab/test_subclass_implementation.py
72
72
  tests/py312_tests/test_generic_annotations.py
73
73
  tests/py314_tests/_test_support.py
74
- tests/py314_tests/test_forwardref_annotations.py
74
+ tests/py314_tests/test_forwardref_annotations.py
75
+ tests/py314_tests/test_init_signature.py
76
+ tests/py314_tests/test_init_signature_future.py
@@ -0,0 +1,122 @@
1
+ from annotationlib import get_annotations, Format
2
+
3
+ import pytest
4
+
5
+
6
+ from ducktools.classbuilder.prefab import Prefab, prefab
7
+ from _test_support import EqualToForwardRef
8
+
9
+
10
+ # Aliases for alias test
11
+ assign_int = int
12
+ type type_str = str
13
+
14
+
15
+ @pytest.mark.parametrize(
16
+ ["format", "expected"],
17
+ [
18
+ (Format.VALUE, {"return": None, "x": str, "y": int}),
19
+ (Format.FORWARDREF, {"return": None, "x": str, "y": int}),
20
+ (Format.STRING, {"return": "None", "x": "str", "y": "int"}),
21
+ ]
22
+ )
23
+ def test_resolvable_annotations(format, expected):
24
+ @prefab
25
+ class Example:
26
+ x: str
27
+ y: int
28
+
29
+ annos = get_annotations(Example.__init__, format=format)
30
+
31
+ assert annos == expected
32
+
33
+ class Example(Prefab):
34
+ x: str
35
+ y: int
36
+
37
+ annos = get_annotations(Example.__init__, format=format)
38
+
39
+ assert annos == expected
40
+
41
+
42
+ @pytest.mark.parametrize(
43
+ ["format", "expected"],
44
+ [
45
+ (Format.VALUE, {"return": None, "x": str, "y": int}),
46
+ (Format.FORWARDREF, {"return": None, "x": str, "y": int}),
47
+ (Format.STRING, {"return": "None", "x": "str", "y": "late_definition"}),
48
+ ]
49
+ )
50
+ def test_late_defined_annotations(format, expected):
51
+ # Test where the annotation is a forwardref at processing time
52
+ @prefab
53
+ class Example:
54
+ x: str
55
+ y: late_definition
56
+
57
+ late_definition = int
58
+
59
+ annos = get_annotations(Example.__init__, format=format)
60
+
61
+ assert annos == expected
62
+
63
+
64
+ @pytest.mark.parametrize(
65
+ ["format", "expected"],
66
+ [
67
+ (Format.VALUE, {"return": None, "x": int, "y": type_str}),
68
+ (Format.FORWARDREF, {"return": None, "x": int, "y": type_str}),
69
+ (Format.STRING, {"return": "None", "x": "int", "y": "type_str"}),
70
+ ]
71
+ )
72
+ def test_alias_defined_annotations(format, expected):
73
+ # Test the behaviour of type aliases and regular types
74
+ # Type Alias names should be kept while regular assignments will be lost
75
+
76
+ @prefab
77
+ class Example:
78
+ x: assign_int # type: ignore
79
+ y: type_str
80
+
81
+ annos = get_annotations(Example.__init__, format=format)
82
+
83
+ assert annos == expected
84
+
85
+
86
+ @pytest.mark.parametrize(
87
+ ["format", "expected"],
88
+ [
89
+ (Format.FORWARDREF, {"return": None, "x": str, "y": EqualToForwardRef("undefined")}),
90
+ (Format.STRING, {"return": "None", "x": "str", "y": "undefined"}),
91
+ ]
92
+ )
93
+ def test_forwardref_annotation(format, expected):
94
+ # Test where the annotation is a forwardref at processing and analysis
95
+ class Example(Prefab):
96
+ x: str
97
+ y: undefined
98
+
99
+ annos = get_annotations(Example.__init__, format=format)
100
+
101
+ assert annos == expected
102
+
103
+
104
+ def test_forwardref_raises():
105
+ # Should still raise a NameError with VALUE annotations
106
+ @prefab
107
+ class Example:
108
+ x: str
109
+ y: undefined
110
+
111
+ with pytest.raises(NameError):
112
+ annos = get_annotations(Example.__init__, format=Format.VALUE)
113
+
114
+
115
+ def test_raises_with_fake_globals():
116
+ @prefab
117
+ class Example:
118
+ x: str
119
+ y: undefined
120
+
121
+ with pytest.raises(NotImplementedError):
122
+ annos = Example.__init__.__annotate__(Format.VALUE_WITH_FAKE_GLOBALS)
@@ -0,0 +1,105 @@
1
+ # The same tests as test_init_signature but under the __future__ annotations import
2
+ from __future__ import annotations
3
+
4
+ from annotationlib import get_annotations, Format
5
+
6
+ import pytest
7
+
8
+
9
+ from ducktools.classbuilder.prefab import Prefab, prefab
10
+ from _test_support import EqualToForwardRef
11
+
12
+
13
+ # Aliases for alias test
14
+ assign_int = int
15
+ type type_str = str
16
+
17
+
18
+ @pytest.mark.parametrize(
19
+ ["format", "expected"],
20
+ [
21
+ (Format.VALUE, {"return": None, "x": "str", "y": "int"}),
22
+ (Format.FORWARDREF, {"return": None, "x": "str", "y": "int"}),
23
+ (Format.STRING, {"return": "None", "x": "str", "y": "int"}),
24
+ ]
25
+ )
26
+ def test_resolvable_annotations(format, expected):
27
+ @prefab
28
+ class Example:
29
+ x: str
30
+ y: int
31
+
32
+ annos = get_annotations(Example.__init__, format=format)
33
+
34
+ assert annos == expected
35
+
36
+ class Example(Prefab):
37
+ x: str
38
+ y: int
39
+
40
+ annos = get_annotations(Example.__init__, format=format)
41
+
42
+ assert annos == expected
43
+
44
+
45
+ @pytest.mark.parametrize(
46
+ ["format", "expected"],
47
+ [
48
+ (Format.VALUE, {"return": None, "x": "str", "y": "late_definition"}),
49
+ (Format.FORWARDREF, {"return": None, "x": "str", "y": "late_definition"}),
50
+ (Format.STRING, {"return": "None", "x": "str", "y": "late_definition"}),
51
+ ]
52
+ )
53
+ def test_late_defined_annotations(format, expected):
54
+ # Test where the annotation is a forwardref at processing time
55
+ @prefab
56
+ class Example:
57
+ x: str
58
+ y: late_definition
59
+
60
+ late_definition = int
61
+
62
+ annos = get_annotations(Example.__init__, format=format)
63
+
64
+ assert annos == expected
65
+
66
+
67
+ @pytest.mark.parametrize(
68
+ ["format", "expected"],
69
+ [
70
+ (Format.VALUE, {"return": None, "x": "assign_int", "y": "type_str"}),
71
+ (Format.FORWARDREF, {"return": None, "x": "assign_int", "y": "type_str"}),
72
+ (Format.STRING, {"return": "None", "x": "assign_int", "y": "type_str"}),
73
+ ]
74
+ )
75
+ def test_alias_defined_annotations(format, expected):
76
+ # Test the behaviour of type aliases and regular types
77
+ # under __future__ annotations both should be kept
78
+
79
+ @prefab
80
+ class Example:
81
+ x: assign_int # type: ignore
82
+ y: type_str
83
+
84
+ annos = get_annotations(Example.__init__, format=format)
85
+
86
+ assert annos == expected
87
+
88
+
89
+ @pytest.mark.parametrize(
90
+ ["format", "expected"],
91
+ [
92
+ (Format.VALUE, {"return": None, "x": "str", "y": "undefined"}),
93
+ (Format.FORWARDREF, {"return": None, "x": "str", "y": "undefined"}),
94
+ (Format.STRING, {"return": "None", "x": "str", "y": "undefined"}),
95
+ ]
96
+ )
97
+ def test_forwardref_annotation(format, expected):
98
+ # Test where the annotation is a forwardref at processing and analysis
99
+ class Example(Prefab):
100
+ x: str
101
+ y: undefined
102
+
103
+ annos = get_annotations(Example.__init__, format=format)
104
+
105
+ assert annos == expected
@@ -1,2 +0,0 @@
1
- __version__ = "0.10.1"
2
- __version_tuple__ = (0, 10, 1)