Skip to content

Commit 681a6bd

Browse files
author
Nijat Khanbabayev
committed
Update to simplify code and logic
Signed-off-by: Nijat Khanbabayev <nijat.khanbabayev@cubistsystematic.com>
1 parent 2e84b48 commit 681a6bd

15 files changed

+1726
-5837
lines changed

ccflow/callable.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -535,37 +535,46 @@ def model(*args, **kwargs):
535535
features (caching, evaluation, registry, serialization) work unchanged.
536536
537537
Args:
538-
context_args: List of parameter names that come from context (for unpacked mode)
539-
context_type: Explicit ContextBase subclass to use with context_args mode
538+
context_type: Optional ContextBase subclass used only to validate/coerce
539+
`FromContext[...]` inputs against an existing nominal context shape
540540
cacheable: Enable caching of results (default: False)
541541
volatile: Mark as volatile (default: False)
542542
log_level: Logging verbosity (default: logging.DEBUG)
543543
validate_result: Validate return type (default: True)
544544
verbose: Verbose logging output (default: True)
545545
evaluator: Custom evaluator (default: None)
546546
547-
Two Context Modes:
547+
Primary authoring model:
548+
Mark runtime/contextual inputs explicitly with `FromContext[...]`.
549+
Ordinary unmarked parameters are regular bound inputs and are never
550+
read implicitly from the runtime context.
548551
549-
Mode 1 - Explicit context parameter:
550-
Function has a 'context' parameter annotated with a ContextBase subclass.
552+
@Flow.model
553+
def load_prices(
554+
source: str,
555+
start_date: FromContext[date],
556+
end_date: FromContext[date],
557+
) -> GenericResult[pl.DataFrame]:
558+
return GenericResult(value=query_db(source, start_date, end_date))
559+
560+
Advanced interop path:
561+
Functions may still declare an explicit context parameter annotated
562+
with a ContextBase subclass.
551563
552564
@Flow.model
553565
def load_prices(context: DateRangeContext, source: str) -> GenericResult[pl.DataFrame]:
554566
return GenericResult(value=query_db(source, context.start_date, context.end_date))
555567
556-
Mode 2 - Unpacked context_args:
557-
Context fields are unpacked into function parameters.
558-
559-
@Flow.model(context_args=["start_date", "end_date"], context_type=DateRangeContext)
560-
def load_prices(start_date: date, end_date: date, source: str) -> GenericResult[pl.DataFrame]:
561-
return GenericResult(value=query_db(source, start_date, end_date))
562-
563568
Dependencies:
564-
Any non-context parameter can be bound either to a literal value or
569+
Any ordinary parameter can be bound either to a literal value or
565570
to another CallableModel. When a CallableModel is supplied, the
566571
generated model treats it as an upstream dependency and resolves it
567572
with the current context before calling the underlying function.
568573
574+
`FromContext[...]` parameters are different: they may be satisfied by
575+
runtime context, construction-time contextual defaults, or function
576+
defaults, but not by CallableModel values.
577+
569578
Usage:
570579
# Create model instances
571580
loader = load_prices(source="prod_db")

ccflow/exttypes/frequency.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import warnings
23
from datetime import timedelta
34
from functools import cached_property
@@ -32,6 +33,13 @@ def _validate(cls, value) -> "Frequency":
3233
if isinstance(value, cls):
3334
return cls._validate(str(value))
3435

36+
if isinstance(value, timedelta):
37+
if value.total_seconds() % 86400 == 0:
38+
return cls(f"{int(value.total_seconds() // 86400)}D")
39+
40+
if isinstance(value, str):
41+
value = _normalize_frequency_alias(value)
42+
3543
if isinstance(value, (timedelta, str)):
3644
try:
3745
with warnings.catch_warnings():
@@ -43,7 +51,7 @@ def _validate(cls, value) -> "Frequency":
4351
raise ValueError(f"ensure this value can be converted to a pandas offset: {e}")
4452

4553
if isinstance(value, pd.offsets.DateOffset):
46-
return cls(f"{value.n}{value.base.freqstr}")
54+
return cls(_canonicalize_offset_string(value))
4755

4856
raise ValueError(f"ensure this value can be converted to a pandas offset: {value}")
4957

@@ -54,3 +62,39 @@ def validate(cls, value) -> "Frequency":
5462

5563

5664
_TYPE_ADAPTER = TypeAdapter(Frequency)
65+
66+
67+
_LEGACY_FREQ_PATTERN = re.compile(
68+
r"^(?P<count>[+-]?\d+)?(?P<unit>T|M|A|Y)(?:-(?P<suffix>[A-Za-z]{3}))?$",
69+
re.IGNORECASE,
70+
)
71+
72+
73+
def _normalize_frequency_alias(value: str) -> str:
74+
normalized = value.strip()
75+
if not normalized:
76+
return normalized
77+
78+
match = _LEGACY_FREQ_PATTERN.fullmatch(normalized)
79+
if not match:
80+
day_match = re.fullmatch(r"(?P<count>[+-]?\d+)?d", normalized, re.IGNORECASE)
81+
if day_match:
82+
return f"{day_match.group('count') or 1}D"
83+
return normalized
84+
85+
count = match.group("count") or "1"
86+
unit = match.group("unit").upper()
87+
suffix = (match.group("suffix") or "DEC").upper()
88+
replacements = {
89+
"T": f"{count}min",
90+
"M": f"{count}ME",
91+
"A": f"{count}YE-{suffix}",
92+
"Y": f"{count}YE-{suffix}",
93+
}
94+
return replacements[unit]
95+
96+
97+
def _canonicalize_offset_string(offset: pd.offsets.DateOffset) -> str:
98+
if isinstance(offset, pd.offsets.Day):
99+
return f"{offset.n}D"
100+
return f"{offset.n}{offset.base.freqstr}"

0 commit comments

Comments
 (0)