tinyfan 0.2.2__tar.gz → 0.2.4__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.2 → tinyfan-0.2.4}/PKG-INFO +1 -1
- {tinyfan-0.2.2 → tinyfan-0.2.4}/pyproject.toml +1 -1
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/codegen.py +7 -7
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/dagviz.py +3 -1
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/flow.py +9 -4
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/resources/base.py +2 -1
- tinyfan-0.2.4/tests/test_config.py +60 -0
- tinyfan-0.2.4/tests/test_decorators.py +32 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/uv.lock +1 -1
- tinyfan-0.2.2/tests/test_decorators.py +0 -9
- {tinyfan-0.2.2 → tinyfan-0.2.4}/.github/workflows/publish.yml +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/.gitignore +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/.python-version +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/README.md +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/README.md +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/configmap.yaml +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/configs.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/getting-started.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/.python-version +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/Dockerfile +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/README.md +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/pyproject.toml +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/src/tinyfan_examples_packaged/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/packaged/src/tinyfan_examples_packaged/asset.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/resource.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/examples/secret.yaml +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/argo_typing.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/cli.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/config.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/flowrundata.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/py.typed +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/resources/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/stores/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/stores/base.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/stores/bigquery.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/stores/gcs_pickle.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/stores/naive.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/embed.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/exjson.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/merge.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/typing.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/src/tinyfan/utils/yaml.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/codegen_samples/directory/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/codegen_samples/directory/asset.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/codegen_samples/directory/asset2.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/codegen_samples/singlefile.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/conftest.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/helpers.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/openapi2jsonschema.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/test_codegen.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/embed_samples/directory/__init__.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/embed_samples/directory/dep.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/embed_samples/singlefile.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/test_embed.py +0 -0
- {tinyfan-0.2.2 → tinyfan-0.2.4}/tests/utils/test_exjson.py +0 -0
|
@@ -191,13 +191,13 @@ class AssetTree:
|
|
|
191
191
|
|
|
192
192
|
relatives_by_schedules = self.relatives_by_schedules()
|
|
193
193
|
g: nx.DiGraph = nx.DiGraph()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
g.add_edge(self.flow.name,
|
|
199
|
-
g.add_edges_from([(
|
|
200
|
-
g.add_edges_from([(
|
|
194
|
+
g.add_node(self.flow.name, bullet="■")
|
|
195
|
+
for (tz, schedule), relatives in relatives_by_schedules.items():
|
|
196
|
+
schedule_node_name = schedule + f" ({tz})"
|
|
197
|
+
g.add_node(schedule_node_name, bullet="○")
|
|
198
|
+
g.add_edge(self.flow.name, schedule_node_name)
|
|
199
|
+
g.add_edges_from([(schedule_node_name, r.asset.name) for r in relatives])
|
|
200
|
+
g.add_edges_from([(d.asset.name, r.asset.name) for r in relatives for d in r.parents])
|
|
201
201
|
return dagviz(g, round_angle=True)
|
|
202
202
|
|
|
203
203
|
def compile(
|
|
@@ -15,6 +15,7 @@ def dagviz(g: nx.DiGraph, round_angle: bool = False) -> str:
|
|
|
15
15
|
assert nx.is_directed_acyclic_graph(g), "Graph contains cycles"
|
|
16
16
|
|
|
17
17
|
rows: List[Hashable] = []
|
|
18
|
+
bullets: List[str] = []
|
|
18
19
|
node_to_row: Dict[Hashable, int] = {}
|
|
19
20
|
indents: List[int] = []
|
|
20
21
|
|
|
@@ -27,6 +28,7 @@ def dagviz(g: nx.DiGraph, round_angle: bool = False) -> str:
|
|
|
27
28
|
for i in range(len(sources)):
|
|
28
29
|
node_to_row[sources[i]] = len(rows)
|
|
29
30
|
rows.append(sources[i])
|
|
31
|
+
bullets.append(g.nodes[sources[i]].get("bullet", "•"))
|
|
30
32
|
indents.append(indent + i)
|
|
31
33
|
_process_dag(nx.DiGraph(g.subgraph(set(g.nodes).difference(sources))), indent=indent + len(sources))
|
|
32
34
|
|
|
@@ -50,5 +52,5 @@ def dagviz(g: nx.DiGraph, round_angle: bool = False) -> str:
|
|
|
50
52
|
for k in range(indents[i] * 2 + 1, indents[n] * 2):
|
|
51
53
|
a[n][k] = "─"
|
|
52
54
|
|
|
53
|
-
lines: List[str] = [x.tounicode() + "
|
|
55
|
+
lines: List[str] = [x.tounicode() + f"{b} " + str(i).replace("\n", " ") for x, b, i in zip(a, bullets, rows)]
|
|
54
56
|
return "\n".join(lines)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
import asyncio
|
|
2
3
|
from .stores.base import StoreBase
|
|
3
4
|
from .stores.naive import NaiveStore
|
|
4
5
|
from .flowrundata import FlowRunData, StoreIdx, UMeta
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Callable, Any, Generic, TypeVar
|
|
7
|
+
from typing import Callable, Any, Generic, TypeVar, Awaitable, Coroutine, cast
|
|
7
8
|
from .resources.base import ResourceBase
|
|
8
9
|
from .argo_typing import ScriptTemplate
|
|
9
10
|
from .config import Config, ConfigValue
|
|
@@ -41,7 +42,7 @@ DEFAULT_FLOW: Flow = Flow(
|
|
|
41
42
|
@dataclass
|
|
42
43
|
class Asset(Generic[Ret, UMeta, StoreIdx]):
|
|
43
44
|
flow: Flow
|
|
44
|
-
func: Callable[..., Ret]
|
|
45
|
+
func: Callable[..., Ret | Awaitable[Ret]]
|
|
45
46
|
store: StoreBase[Ret, UMeta, StoreIdx]
|
|
46
47
|
schedule: str | None = None
|
|
47
48
|
tz: str | None = None
|
|
@@ -84,11 +85,15 @@ class Asset(Generic[Ret, UMeta, StoreIdx]):
|
|
|
84
85
|
if name in func_param_names:
|
|
85
86
|
params[name] = data
|
|
86
87
|
ret = self.func(**params)
|
|
87
|
-
|
|
88
|
+
if inspect.isawaitable(ret):
|
|
89
|
+
res: Ret = asyncio.run(cast(Coroutine[Any, Any, Ret], ret))
|
|
90
|
+
else:
|
|
91
|
+
res = cast(Ret, ret)
|
|
92
|
+
store_entry_idx = self.store.store(res, rundata)
|
|
88
93
|
rundata["store_entry_idx"] = store_entry_idx
|
|
89
94
|
if "parents" in rundata:
|
|
90
95
|
del rundata["parents"]
|
|
91
|
-
return (
|
|
96
|
+
return (res, rundata)
|
|
92
97
|
|
|
93
98
|
|
|
94
99
|
class AssetFunc(Generic[Ret, UMeta, StoreIdx]):
|
|
@@ -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):
|
|
@@ -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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from tinyfan import asset
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_preserve_original_functionality():
|
|
5
|
+
@asset()
|
|
6
|
+
def test(arg: str):
|
|
7
|
+
return arg
|
|
8
|
+
|
|
9
|
+
assert test(arg="test") == "test"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_asset_run():
|
|
13
|
+
@asset()
|
|
14
|
+
def test(ds: str):
|
|
15
|
+
return ds
|
|
16
|
+
|
|
17
|
+
actual, _ = test.asset.run({"ds": "2020-01-01"})
|
|
18
|
+
expected = "2020-01-01"
|
|
19
|
+
assert actual == expected
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_async_asset_run():
|
|
23
|
+
async def awaitable() -> str:
|
|
24
|
+
return " awaited"
|
|
25
|
+
|
|
26
|
+
@asset()
|
|
27
|
+
async def test(ds: str):
|
|
28
|
+
return ds + await awaitable()
|
|
29
|
+
|
|
30
|
+
actual, _ = test.asset.run({"ds": "2020-01-01"})
|
|
31
|
+
expected = "2020-01-01 awaited"
|
|
32
|
+
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
|