tinyfan 0.2.3__tar.gz → 0.2.5__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.
- {tinyfan-0.2.3 → tinyfan-0.2.5}/PKG-INFO +1 -1
- {tinyfan-0.2.3 → tinyfan-0.2.5}/pyproject.toml +1 -1
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/argo_typing.py +17 -1
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/codegen.py +15 -11
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/resources/base.py +2 -1
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/test_codegen.py +18 -1
- tinyfan-0.2.5/tests/test_config.py +60 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/uv.lock +1 -1
- {tinyfan-0.2.3 → tinyfan-0.2.5}/.github/workflows/publish.yml +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/.gitignore +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/.python-version +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/README.md +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/README.md +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/configmap.yaml +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/configs.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/getting-started.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/.python-version +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/Dockerfile +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/README.md +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/pyproject.toml +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/src/tinyfan_examples_packaged/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/packaged/src/tinyfan_examples_packaged/asset.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/resource.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/examples/secret.yaml +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/cli.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/config.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/dagviz.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/flow.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/flowrundata.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/py.typed +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/resources/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/stores/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/stores/base.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/stores/bigquery.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/stores/gcs_pickle.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/stores/naive.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/embed.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/exjson.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/merge.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/typing.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/src/tinyfan/utils/yaml.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/codegen_samples/directory/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/codegen_samples/directory/asset.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/codegen_samples/directory/asset2.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/codegen_samples/singlefile.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/conftest.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/helpers.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/openapi2jsonschema.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/test_decorators.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/embed_samples/directory/__init__.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/embed_samples/directory/dep.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/embed_samples/singlefile.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/test_embed.py +0 -0
- {tinyfan-0.2.3 → tinyfan-0.2.5}/tests/utils/test_exjson.py +0 -0
|
@@ -93,6 +93,22 @@ class EnvVar(TypedDict):
|
|
|
93
93
|
valueFrom: NotRequired[EnvVarSource]
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
class ConfigMapEnvSource(TypedDict, total=False):
|
|
97
|
+
name: str
|
|
98
|
+
optional: bool
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SecretEnvSource(TypedDict, total=False):
|
|
102
|
+
name: str
|
|
103
|
+
optional: bool
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class EnvFromSource(TypedDict, total=False):
|
|
107
|
+
configMapRef: ConfigMapEnvSource
|
|
108
|
+
secretRef: SecretEnvSource
|
|
109
|
+
prefix: str
|
|
110
|
+
|
|
111
|
+
|
|
96
112
|
class Lifecycle(TypedDict, total=False):
|
|
97
113
|
postStart: LifecycleHandler
|
|
98
114
|
preStop: LifecycleHandler
|
|
@@ -183,7 +199,7 @@ class ScriptTemplate(TypedDict, total=False):
|
|
|
183
199
|
args: list[str]
|
|
184
200
|
command: list[str]
|
|
185
201
|
env: list[EnvVar]
|
|
186
|
-
envFrom: list[
|
|
202
|
+
envFrom: list[EnvFromSource]
|
|
187
203
|
image: str
|
|
188
204
|
imagePullPolicy: str
|
|
189
205
|
lifecycle: Lifecycle
|
|
@@ -117,11 +117,11 @@ class AssetNode:
|
|
|
117
117
|
|
|
118
118
|
def relatives(self, result: set[Self] | None = None) -> set[Self]:
|
|
119
119
|
if result is None:
|
|
120
|
-
result =
|
|
121
|
-
for
|
|
122
|
-
if
|
|
123
|
-
result.add(
|
|
124
|
-
|
|
120
|
+
result = {self}
|
|
121
|
+
for node in self.parents + self.children:
|
|
122
|
+
if node not in result:
|
|
123
|
+
result.add(node)
|
|
124
|
+
node.relatives(result)
|
|
125
125
|
return result
|
|
126
126
|
|
|
127
127
|
|
|
@@ -142,18 +142,23 @@ class AssetTree:
|
|
|
142
142
|
| set(inspect.get_annotations(FlowRunData).keys())
|
|
143
143
|
)
|
|
144
144
|
unknown_names = [n for n in func_param_names if n not in known_names]
|
|
145
|
-
if
|
|
145
|
+
if unknown_names:
|
|
146
146
|
raise Exception(
|
|
147
147
|
f"unknown asset args: {', '.join('`' + n + '`' for n in unknown_names)} from asset `{node.asset.name}`"
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
params_ids = set(name for name in func_param_names if name in self.flow.assets)
|
|
151
151
|
if node.asset.depends is not None:
|
|
152
|
+
assert re.match(VALID_DEPENDS_REGEXP, node.asset.depends)
|
|
152
153
|
depends_ids = set(re.findall(EXTRACT_DEPENDS_REGEXP, node.asset.depends))
|
|
153
154
|
node.parents = [self.nodes[n] for n in depends_ids.union(params_ids)]
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
new_ids = params_ids - depends_ids
|
|
156
|
+
if new_ids:
|
|
157
|
+
if len(depends_ids) > 1:
|
|
158
|
+
node.asset.depends = f"({node.asset.depends}) && {' && '.join(new_ids)}".replace("_", "-")
|
|
159
|
+
else:
|
|
160
|
+
node.asset.depends = f"{node.asset.depends} && {' && '.join(new_ids)}".replace("_", "-")
|
|
161
|
+
assert re.match(VALID_DEPENDS_REGEXP, node.asset.depends)
|
|
157
162
|
for p in node.parents:
|
|
158
163
|
p.children.append(node)
|
|
159
164
|
else:
|
|
@@ -189,10 +194,9 @@ class AssetTree:
|
|
|
189
194
|
|
|
190
195
|
from .dagviz import dagviz
|
|
191
196
|
|
|
192
|
-
relatives_by_schedules = self.relatives_by_schedules()
|
|
193
197
|
g: nx.DiGraph = nx.DiGraph()
|
|
194
198
|
g.add_node(self.flow.name, bullet="■")
|
|
195
|
-
for (tz, schedule), relatives in relatives_by_schedules.items():
|
|
199
|
+
for (tz, schedule), relatives in self.relatives_by_schedules().items():
|
|
196
200
|
schedule_node_name = schedule + f" ({tz})"
|
|
197
201
|
g.add_node(schedule_node_name, bullet="○")
|
|
198
202
|
g.add_edge(self.flow.name, schedule_node_name)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from ..config import Config, ConfigValue
|
|
2
|
+
import inspect
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class ResourceBase:
|
|
5
6
|
def get_refs(self) -> set[Config | ConfigValue]:
|
|
6
7
|
refs = set()
|
|
7
|
-
for _, value in
|
|
8
|
+
for _, value in inspect.getmembers(self):
|
|
8
9
|
if isinstance(value, (Config, ConfigValue)): # or a common base 'Reference'
|
|
9
10
|
refs.add(value)
|
|
10
11
|
elif isinstance(value, ResourceBase):
|
|
@@ -21,6 +21,14 @@ def sample_flow() -> Generator:
|
|
|
21
21
|
def asset2(asset1: str): # type: ignore
|
|
22
22
|
print("asset1 says: " + asset1)
|
|
23
23
|
|
|
24
|
+
@asset(flow=flow, depends="asset1 && asset2")
|
|
25
|
+
def asset3(asset1: str): # type: ignore
|
|
26
|
+
print("asset1 says: " + asset1)
|
|
27
|
+
|
|
28
|
+
@asset(flow=flow, depends="asset2")
|
|
29
|
+
def asset4(asset1: str): # type: ignore
|
|
30
|
+
print("asset1 says: " + asset1)
|
|
31
|
+
|
|
24
32
|
yield flow
|
|
25
33
|
FLOW_REGISTER.clear()
|
|
26
34
|
|
|
@@ -132,9 +140,18 @@ def test_codegen_directory(validate_crds):
|
|
|
132
140
|
def test_rundata_data_interval(sample_flow):
|
|
133
141
|
tree = AssetTree(sample_flow)
|
|
134
142
|
node = list(tree.nodes.values())[0]
|
|
135
|
-
# rundatatmpl = node.rundatatmpl("0 * * * * *")
|
|
136
143
|
daily = node.rundatatmpl("@daily")
|
|
137
144
|
assert (
|
|
138
145
|
daily
|
|
139
146
|
== """{"ds": "{{=sprig.date('2006-01-02', sprig.dateModify('-86400.0s', sprig.toDate('2006-01-02T15:04:05Z07:00', workflow.scheduledTime)))}}", "ts": "{{=sprig.dateModify('-86400.0s', sprig.toDate('2006-01-02T15:04:05Z07:00', workflow.scheduledTime))}}", "data_interval_start": {"__tinyfan_datetime__": "{{=sprig.dateModify('-86400.0s', sprig.toDate('2006-01-02T15:04:05Z07:00', workflow.scheduledTime))}}"}, "data_interval_end": {"__tinyfan_datetime__": "{{workflow.scheduledTime}}"}, "parents": {}, "asset_name": "asset1", "flow_name": "test", "module_name": "tests.test_codegen"}"""
|
|
140
147
|
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_depends(sample_flow):
|
|
151
|
+
tree = AssetTree(sample_flow)
|
|
152
|
+
assert [node.asset.depends for node in tree.nodes.values()] == [
|
|
153
|
+
"",
|
|
154
|
+
"asset1",
|
|
155
|
+
"asset1 && asset2",
|
|
156
|
+
"asset2 && asset1",
|
|
157
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from tinyfan import Flow, asset, ResourceBase, ConfigMapRef, ConfigMapKeyRef, SecretRef, SecretKeyRef
|
|
2
|
+
from tinyfan.flow import FLOW_REGISTER
|
|
3
|
+
from tinyfan.codegen import codegen
|
|
4
|
+
from typing import Generator
|
|
5
|
+
from unittest import mock
|
|
6
|
+
import pytest
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# type: ignore
|
|
11
|
+
@pytest.fixture()
|
|
12
|
+
def sample_flow() -> Generator:
|
|
13
|
+
class Resource(ResourceBase):
|
|
14
|
+
cm = ConfigMapRef("cm")
|
|
15
|
+
cm_key = ConfigMapKeyRef("cm", "key")
|
|
16
|
+
cm_default = ConfigMapKeyRef("cm", "non_key", "default")
|
|
17
|
+
sec = SecretRef("secret")
|
|
18
|
+
sec_key = SecretKeyRef("secret", "key")
|
|
19
|
+
sec_default = SecretKeyRef("secret", "non_key", "default")
|
|
20
|
+
|
|
21
|
+
flow: Flow = Flow(
|
|
22
|
+
"test",
|
|
23
|
+
resources={
|
|
24
|
+
"res": Resource(),
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@asset(flow=flow, schedule="@daily")
|
|
29
|
+
def test_asset(res: Resource): # type: ignore
|
|
30
|
+
return (
|
|
31
|
+
res.cm.get("key"),
|
|
32
|
+
res.cm_key.get_value(),
|
|
33
|
+
res.cm_default.get_value(),
|
|
34
|
+
res.sec.get("key"),
|
|
35
|
+
res.sec_key.get_value(),
|
|
36
|
+
res.sec_default.get_value(),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
yield flow
|
|
40
|
+
FLOW_REGISTER.clear()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# HACK IT: skip it at this stage. too tired.
|
|
44
|
+
def test_resource_config_codgen(validate_crds, sample_flow):
|
|
45
|
+
res = codegen(flow=sample_flow)
|
|
46
|
+
validate_crds(res)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_resource_config_run(sample_flow: Flow):
|
|
50
|
+
env = {
|
|
51
|
+
"TINYFAN_CM__cm__key": "cmkey",
|
|
52
|
+
"TINYFAN_SC__secret__key": "sekey",
|
|
53
|
+
"key": "key",
|
|
54
|
+
}
|
|
55
|
+
with mock.patch.dict(os.environ, env):
|
|
56
|
+
_ = codegen(flow=sample_flow)
|
|
57
|
+
asset = list(sample_flow.assets.values())[0]
|
|
58
|
+
actual, _ = asset.run()
|
|
59
|
+
expected = ("key", "cmkey", "default", "key", "sekey", "default")
|
|
60
|
+
assert actual == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|