Skip to content

Commit 31bdbb6

Browse files
committed
Merge branch 'main' into nk/serialize_as_any_updates
Signed-off-by: Nijat Khanbabayev <[email protected]>
2 parents a1be9b9 + cc8f0ee commit 31bdbb6

File tree

13 files changed

+444
-24
lines changed

13 files changed

+444
-24
lines changed

.copier-answers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier
2-
_commit: a74894d
2+
_commit: f812aaa
33
_src_path: https://github.com/python-project-templates/base.git
44
add_docs: false
55
add_extension: python

.github/workflows/build.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727

2828
strategy:
2929
matrix:
30-
os: [ubuntu-latest, macos-13] # NOTE: use macos-13 as no standard csp wheels for arm64 3.9
31-
python-version: [3.9]
30+
os: [ubuntu-latest, macos-latest]
31+
python-version: ['3.10']
3232

3333
steps:
3434
- uses: actions/checkout@v5
@@ -53,7 +53,7 @@ jobs:
5353
run: make coverage
5454

5555
- name: Upload test results (Python)
56-
uses: actions/upload-artifact@v4
56+
uses: actions/upload-artifact@v5
5757
with:
5858
name: py-test-results-${{ matrix.os }}-${{ matrix.python-version }}-
5959
path: junit.xml
@@ -74,7 +74,7 @@ jobs:
7474
- name: Twine check
7575
run: make dist
7676

77-
- uses: actions/upload-artifact@v4
77+
- uses: actions/upload-artifact@v5
7878
with:
7979
name: dist-${{matrix.os}}
8080
path: dist

ccflow/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
__version__ = "0.6.4"
1+
__version__ = "0.6.8"
22

33
from .arrow import *
44
from .base import *
55
from .callable import *
66
from .context import *
77
from .enums import Enum
88
from .exttypes import *
9+
from .global_state import *
910
from .models import *
1011
from .object_config import *
1112
from .publisher import *

ccflow/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def __init__(self, *args, **kwargs):
310310
models = {}
311311
if "models" in kwargs:
312312
models = kwargs.pop("models")
313-
if not isinstance(models, dict):
313+
if not isinstance(models, (dict, MappingProxyType)):
314314
raise TypeError("models must be a dict")
315315
super(ModelRegistry, self).__init__(*args, **kwargs)
316316
for name, model in models.items():
@@ -352,6 +352,10 @@ def clear(self) -> Self:
352352
self.remove(name)
353353
return self
354354

355+
def clone(self, name: Optional[str] = None) -> Self:
356+
"""Shallow clone the registry (but not the models within it)."""
357+
return ModelRegistry(name=name or self.name, models=self.models)
358+
355359
def remove(self, name: str) -> None:
356360
"""Remove a model from the registry.
357361

ccflow/callable.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,19 +587,75 @@ def result_type(self) -> Type[ResultType]:
587587

588588

589589
class CallableModelGenericType(CallableModel, Generic[ContextType, ResultType]):
590-
"""Special type of callable model to use for type declarations, such that the
591-
context and result type will be validated.
590+
"""Special type of callable model that provides context and result via
591+
a generic type instead of annotations on __call__.
592592
"""
593593

594+
_context_type: ClassVar[Type[ContextType]]
595+
_result_type: ClassVar[Type[ResultType]]
596+
597+
@property
598+
def context_type(self) -> Type[ContextType]:
599+
return self._context_type
600+
601+
@property
602+
def result_type(self) -> Type[ResultType]:
603+
return self._result_type
604+
594605
@model_validator(mode="wrap")
595606
def _validate_callable_model_generic_type(cls, m, handler, info):
596607
from ccflow.base import resolve_str
597608

598609
if isinstance(m, str):
599610
m = resolve_str(m)
611+
if isinstance(m, dict):
612+
m = handler(m)
600613
# Raise ValueError (not TypeError) as per https://docs.pydantic.dev/latest/errors/errors/
601614
if not isinstance(m, CallableModel):
602615
raise ValueError(f"{m} is not a CallableModel: {type(m)}")
616+
617+
# Extract the generic types from the class definition
618+
if not hasattr(cls, "_context_type") or not hasattr(cls, "_result_type"):
619+
new_context_type = None
620+
new_result_type = None
621+
for base in cls.__mro__[1:]:
622+
if issubclass(base, CallableModelGenericType):
623+
# Found the generic base class, it should
624+
# have either generic parameters or context/result
625+
if new_context_type is None and hasattr(base, "_context_type") and issubclass(base._context_type, ContextBase):
626+
new_context_type = base._context_type
627+
if new_result_type is None and hasattr(base, "_result_type") and issubclass(base._result_type, ResultBase):
628+
new_result_type = base._result_type
629+
if base.__pydantic_generic_metadata__["args"]:
630+
for arg in base.__pydantic_generic_metadata__["args"]:
631+
if new_context_type is None and isinstance(arg, type) and issubclass(arg, ContextBase):
632+
new_context_type = arg
633+
elif new_result_type is None and isinstance(arg, type) and issubclass(arg, ResultBase):
634+
# NOTE: ContextBase inherits from ResultBase, so order matters here!
635+
new_result_type = arg
636+
if new_context_type and new_result_type:
637+
break
638+
if new_context_type is not None:
639+
# Validate that the model's context_type match
640+
orig_context_typ = _cached_signature(cls.__call__).parameters["context"].annotation
641+
if orig_context_typ is not Signature.empty and orig_context_typ != new_context_type:
642+
raise TypeError(
643+
f"Context type annotation {orig_context_typ} on __call__ does not match context_type {new_context_type} defined by CallableModelGenericType"
644+
)
645+
# Set on class
646+
cls._context_type = new_context_type
647+
648+
if new_result_type is not None:
649+
# Validate that the model's result_type match
650+
orig_return_typ = _cached_signature(cls.__call__).return_annotation
651+
if orig_return_typ is not Signature.empty and orig_return_typ != new_result_type:
652+
raise TypeError(
653+
f"Return type annotation {orig_return_typ} on __call__ does not match result_type {new_result_type} defined by CallableModelGenericType"
654+
)
655+
656+
# Set on class
657+
cls._result_type = new_result_type
658+
603659
subtypes = cls.__pydantic_generic_metadata__["args"]
604660
if subtypes:
605661
TypeAdapter(Type[subtypes[0]]).validate_python(m.context_type)

ccflow/context.py

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,60 @@
77

88
from .base import ContextBase
99
from .exttypes import Frequency
10-
from .validators import normalize_date
10+
from .validators import normalize_date, normalize_datetime
1111

12-
__all__ = [
12+
__all__ = (
1313
"NullContext",
1414
"GenericContext",
1515
"DateContext",
1616
"DatetimeContext",
1717
"EntryTimeContext",
18+
"SourceContext",
1819
"DateRangeContext",
20+
"DatetimeRangeContext",
21+
"SeededDateRangeContext",
22+
"SeededDatetimeRangeContext",
1923
"VersionedDateContext",
24+
"VersionedDatetimeContext",
2025
"VersionedDateRangeContext",
26+
"VersionedDatetimeRangeContext",
2127
"FreqContext",
2228
"FreqDateContext",
29+
"FreqDatetimeContext",
2330
"FreqDateRangeContext",
31+
"FreqDatetimeRangeContext",
2432
"HorizonContext",
2533
"FreqHorizonContext",
2634
"FreqHorizonDateContext",
35+
"FreqHorizonDatetimeContext",
2736
"FreqHorizonDateRangeContext",
28-
"SeededDateRangeContext",
29-
"SourceContext",
37+
"FreqHorizonDatetimeRangeContext",
3038
"UniverseContext",
3139
"UniverseDateContext",
40+
"UniverseDatetimeContext",
3241
"UniverseDateRangeContext",
42+
"UniverseDatetimeRangeContext",
3343
"UniverseFrequencyDateRangeContext",
44+
"UniverseFrequencyDatetimeRangeContext",
3445
"UniverseFrequencyHorizonDateRangeContext",
46+
"UniverseFrequencyHorizonDatetimeRangeContext",
3547
"VersionedUniverseDateContext",
48+
"VersionedUniverseDatetimeContext",
3649
"VersionedUniverseDateRangeContext",
50+
"VersionedUniverseDatetimeRangeContext",
3751
"ModelContext",
3852
"ModelDateContext",
53+
"ModelDatetimeContext",
3954
"ModelDateRangeContext",
55+
"ModelDatetimeRangeContext",
4056
"ModelDateRangeSourceContext",
4157
"ModelFreqDateRangeContext",
58+
"ModelFreqDatetimeRangeContext",
4259
"VersionedModelDateContext",
60+
"VersionedModelDatetimeContext",
4361
"VersionedModelDateRangeContext",
44-
]
62+
"VersionedModelDatetimeRangeContext",
63+
)
4564

4665
_SEPARATOR = ","
4766

@@ -100,6 +119,9 @@ def _date_context_validator(cls, v, handler, info):
100119
class DatetimeContext(ContextBase):
101120
dt: datetime
102121

122+
# validators
123+
_normalize_dt = field_validator("dt", mode="before")(normalize_datetime)
124+
103125
@model_validator(mode="wrap")
104126
def _datetime_context_validator(cls, v, handler, info):
105127
if cls is DatetimeContext and not isinstance(v, (DatetimeContext, dict)):
@@ -126,18 +148,38 @@ class DateRangeContext(ContextBase):
126148
_normalize_end = field_validator("end_date", mode="before")(normalize_date)
127149

128150

151+
class DatetimeRangeContext(ContextBase):
152+
start_datetime: datetime
153+
end_datetime: datetime
154+
155+
_normalize_start = field_validator("start_datetime", mode="before")(normalize_datetime)
156+
_normalize_end = field_validator("end_datetime", mode="before")(normalize_datetime)
157+
158+
129159
class SeededDateRangeContext(DateRangeContext):
130160
seed: int = 1234
131161

132162

163+
class SeededDatetimeRangeContext(DatetimeRangeContext):
164+
seed: int = 1234
165+
166+
133167
class VersionedDateContext(DateContext, EntryTimeContext):
134168
pass
135169

136170

171+
class VersionedDatetimeContext(DatetimeContext, EntryTimeContext):
172+
pass
173+
174+
137175
class VersionedDateRangeContext(DateRangeContext, EntryTimeContext):
138176
pass
139177

140178

179+
class VersionedDatetimeRangeContext(DatetimeRangeContext, EntryTimeContext):
180+
pass
181+
182+
141183
class FreqContext(ContextBase):
142184
freq: Frequency
143185

@@ -146,10 +188,18 @@ class FreqDateContext(DateContext, FreqContext):
146188
pass
147189

148190

191+
class FreqDatetimeContext(DatetimeContext, FreqContext):
192+
pass
193+
194+
149195
class FreqDateRangeContext(DateRangeContext, FreqContext):
150196
pass
151197

152198

199+
class FreqDatetimeRangeContext(DatetimeRangeContext, FreqContext):
200+
pass
201+
202+
153203
class HorizonContext(ContextBase):
154204
horizon: Frequency
155205

@@ -162,10 +212,18 @@ class FreqHorizonDateContext(DateContext, HorizonContext, FreqContext):
162212
pass
163213

164214

215+
class FreqHorizonDatetimeContext(DatetimeContext, HorizonContext, FreqContext):
216+
pass
217+
218+
165219
class FreqHorizonDateRangeContext(DateRangeContext, HorizonContext, FreqContext):
166220
pass
167221

168222

223+
class FreqHorizonDatetimeRangeContext(DatetimeRangeContext, HorizonContext, FreqContext):
224+
pass
225+
226+
169227
class UniverseContext(ContextBase):
170228
universe: str
171229

@@ -174,26 +232,50 @@ class UniverseDateContext(DateContext, UniverseContext):
174232
pass
175233

176234

235+
class UniverseDatetimeContext(DatetimeContext, UniverseContext):
236+
pass
237+
238+
177239
class UniverseDateRangeContext(DateRangeContext, UniverseContext):
178240
pass
179241

180242

243+
class UniverseDatetimeRangeContext(DatetimeRangeContext, UniverseContext):
244+
pass
245+
246+
181247
class UniverseFrequencyDateRangeContext(DateRangeContext, FreqContext, UniverseContext):
182248
pass
183249

184250

251+
class UniverseFrequencyDatetimeRangeContext(DatetimeRangeContext, FreqContext, UniverseContext):
252+
pass
253+
254+
185255
class UniverseFrequencyHorizonDateRangeContext(DateRangeContext, HorizonContext, FreqContext, UniverseContext):
186256
pass
187257

188258

259+
class UniverseFrequencyHorizonDatetimeRangeContext(DatetimeRangeContext, HorizonContext, FreqContext, UniverseContext):
260+
pass
261+
262+
189263
class VersionedUniverseDateContext(VersionedDateContext, UniverseContext):
190264
pass
191265

192266

267+
class VersionedUniverseDatetimeContext(VersionedDatetimeContext, UniverseContext):
268+
pass
269+
270+
193271
class VersionedUniverseDateRangeContext(VersionedDateRangeContext, UniverseContext):
194272
pass
195273

196274

275+
class VersionedUniverseDatetimeRangeContext(VersionedDatetimeRangeContext, UniverseContext):
276+
pass
277+
278+
197279
class ModelContext(ContextBase):
198280
model: str
199281

@@ -202,10 +284,18 @@ class ModelDateContext(DateContext, ModelContext):
202284
pass
203285

204286

287+
class ModelDatetimeContext(DatetimeContext, ModelContext):
288+
pass
289+
290+
205291
class ModelDateRangeContext(DateRangeContext, ModelContext):
206292
pass
207293

208294

295+
class ModelDatetimeRangeContext(DatetimeRangeContext, ModelContext):
296+
pass
297+
298+
209299
class ModelDateRangeSourceContext(SourceContext, ModelDateRangeContext):
210300
pass
211301

@@ -214,9 +304,21 @@ class ModelFreqDateRangeContext(FreqDateRangeContext, ModelContext):
214304
pass
215305

216306

307+
class ModelFreqDatetimeRangeContext(FreqDatetimeRangeContext, ModelContext):
308+
pass
309+
310+
217311
class VersionedModelDateContext(VersionedDateContext, ModelContext):
218312
pass
219313

220314

315+
class VersionedModelDatetimeContext(VersionedDatetimeContext, ModelContext):
316+
pass
317+
318+
221319
class VersionedModelDateRangeContext(VersionedDateRangeContext, ModelContext):
222320
pass
321+
322+
323+
class VersionedModelDatetimeRangeContext(VersionedDatetimeRangeContext, ModelContext):
324+
pass

0 commit comments

Comments
 (0)