tinyfan 0.3.0__tar.gz → 0.4.0__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.3.0 → tinyfan-0.4.0}/.gitignore +4 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/PKG-INFO +13 -1
- {tinyfan-0.3.0 → tinyfan-0.4.0}/README.md +12 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/pyproject.toml +1 -1
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/cli.py +6 -2
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/codegen.py +97 -7
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/test_codegen.py +70 -3
- {tinyfan-0.3.0 → tinyfan-0.4.0}/uv.lock +2 -2
- {tinyfan-0.3.0 → tinyfan-0.4.0}/.github/workflows/publish.yml +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/.python-version +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/README.md +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/configmap.yaml +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/configs.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/getting-started.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/.python-version +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/Dockerfile +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/README.md +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/pyproject.toml +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/src/tinyfan_examples_packaged/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/packaged/src/tinyfan_examples_packaged/asset.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/resource.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/examples/secret.yaml +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/argo_typing.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/config.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/dagviz.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/flow.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/flowrundata.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/py.typed +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/resources/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/resources/base.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/stores/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/stores/base.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/stores/bigquery.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/stores/gcs_pickle.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/stores/naive.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/embed.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/exjson.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/merge.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/typing.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/src/tinyfan/utils/yaml.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/codegen_samples/directory/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/codegen_samples/directory/asset.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/codegen_samples/directory/asset2.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/codegen_samples/singlefile.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/conftest.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/helpers.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/openapi2jsonschema.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/test_config.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/test_decorators.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/test_gcs_pickle_store.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/embed_samples/directory/__init__.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/embed_samples/directory/dep.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/embed_samples/singlefile.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/test_embed.py +0 -0
- {tinyfan-0.3.0 → tinyfan-0.4.0}/tests/utils/test_exjson.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinyfan
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Tiny Argo Workflows python devkit
|
|
5
5
|
Project-URL: Documentation, https://github.com/eunchuldev/tinyfan#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/eunchuldev/tinyfan/issues
|
|
@@ -101,6 +101,18 @@ def greeting(world: str):
|
|
|
101
101
|
tinyfan main.py | kubectl apply -f -
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
For a package, pass its directory or import name. Tinyfan statically discovers
|
|
105
|
+
modules containing `@asset`, so unrelated submodules are not executed during
|
|
106
|
+
manifest generation. Modules imported by an asset module (for example, a shared
|
|
107
|
+
`Flow` definition) are loaded normally.
|
|
108
|
+
|
|
109
|
+
```shell
|
|
110
|
+
tinyfan src/my_package
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If a project registers assets dynamically or wraps `@asset` in another decorator,
|
|
114
|
+
use `--import-all` to opt into the previous eager-import behavior.
|
|
115
|
+
|
|
104
116
|
# Real World Example (still tiny though!)
|
|
105
117
|
|
|
106
118
|
Comming soon
|
|
@@ -50,6 +50,18 @@ def greeting(world: str):
|
|
|
50
50
|
tinyfan main.py | kubectl apply -f -
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
For a package, pass its directory or import name. Tinyfan statically discovers
|
|
54
|
+
modules containing `@asset`, so unrelated submodules are not executed during
|
|
55
|
+
manifest generation. Modules imported by an asset module (for example, a shared
|
|
56
|
+
`Flow` definition) are loaded normally.
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
tinyfan src/my_package
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If a project registers assets dynamically or wraps `@asset` in another decorator,
|
|
63
|
+
use `--import-all` to opt into the previous eager-import behavior.
|
|
64
|
+
|
|
53
65
|
# Real World Example (still tiny though!)
|
|
54
66
|
|
|
55
67
|
Comming soon
|
|
@@ -9,6 +9,10 @@ def tinyfan(
|
|
|
9
9
|
location: str,
|
|
10
10
|
embedded: bool | None = None,
|
|
11
11
|
arg: Annotated[list[str] | None, typer.Option(metavar="KEY=VALUE")] = None,
|
|
12
|
+
import_all: Annotated[
|
|
13
|
+
bool,
|
|
14
|
+
typer.Option(help="Import every submodule instead of discovering modules with @asset statically."),
|
|
15
|
+
] = False,
|
|
12
16
|
):
|
|
13
17
|
"""
|
|
14
18
|
Generate argocd workflow resource as yaml from tinyfan definitions
|
|
@@ -16,9 +20,9 @@ def tinyfan(
|
|
|
16
20
|
if arg is not None:
|
|
17
21
|
CLI_CONFIG_STATE.cli_args.update({k: v for (k, v) in (val.split("=") for val in arg)})
|
|
18
22
|
if location.endswith(".py"):
|
|
19
|
-
print(codegen(location=location, embedded=embedded or True))
|
|
23
|
+
print(codegen(location=location, embedded=embedded or True, import_all=import_all))
|
|
20
24
|
else:
|
|
21
|
-
print(codegen(location=location, embedded=embedded or False))
|
|
25
|
+
print(codegen(location=location, embedded=embedded or False, import_all=import_all))
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
def main():
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from tinyfan.flow import Flow, Asset, FLOW_REGISTER, DEFAULT_IMAGE
|
|
2
|
+
import ast
|
|
2
3
|
import copy
|
|
3
4
|
import importlib.util
|
|
4
5
|
from typing import Self
|
|
@@ -9,7 +10,8 @@ import sys
|
|
|
9
10
|
import re
|
|
10
11
|
import json
|
|
11
12
|
import os
|
|
12
|
-
import
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import tokenize
|
|
13
15
|
import datetime
|
|
14
16
|
import croniter
|
|
15
17
|
from .argo_typing import ScriptTemplate, TemplateInputs, TemplateOutputs
|
|
@@ -28,7 +30,9 @@ INTERNAL_INPUT_PARAMETERS = {"rundata", "asset_name", "moduledata"}
|
|
|
28
30
|
INTERNAL_OUTPUT_PARAMETERS = {"rundata"}
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def merge_template_io(
|
|
33
|
+
def merge_template_io(
|
|
34
|
+
template_io: dict, user_io: TemplateInputs | TemplateOutputs | None, reserved_parameter_names: set[str]
|
|
35
|
+
) -> dict:
|
|
32
36
|
result = copy.deepcopy(template_io)
|
|
33
37
|
if user_io is None:
|
|
34
38
|
return result
|
|
@@ -75,7 +79,85 @@ def encode_schedule_to_k8sname(tz: str, cron: str) -> str:
|
|
|
75
79
|
return f"{tz}|{cron}".lower().translate(str.maketrans("* ,-/_+|", "a-cds-p-", "@"))[:58]
|
|
76
80
|
|
|
77
81
|
|
|
78
|
-
def
|
|
82
|
+
def _defines_tinyfan_asset(source_path: Path) -> bool:
|
|
83
|
+
"""Return whether a module has a statically recognizable Tinyfan asset."""
|
|
84
|
+
with tokenize.open(source_path) as source_file:
|
|
85
|
+
tree = ast.parse(source_file.read(), filename=str(source_path))
|
|
86
|
+
|
|
87
|
+
asset_names: set[str] = set()
|
|
88
|
+
tinyfan_namespaces: set[str] = set()
|
|
89
|
+
|
|
90
|
+
for node in ast.walk(tree):
|
|
91
|
+
if isinstance(node, ast.Import):
|
|
92
|
+
for imported in node.names:
|
|
93
|
+
if imported.name == "tinyfan":
|
|
94
|
+
tinyfan_namespaces.add(imported.asname or "tinyfan")
|
|
95
|
+
elif imported.name == "tinyfan.flow":
|
|
96
|
+
tinyfan_namespaces.add(imported.asname or "tinyfan")
|
|
97
|
+
elif isinstance(node, ast.ImportFrom) and node.module in {"tinyfan", "tinyfan.flow"}:
|
|
98
|
+
for imported in node.names:
|
|
99
|
+
if imported.name == "asset":
|
|
100
|
+
asset_names.add(imported.asname or imported.name)
|
|
101
|
+
elif imported.name == "*":
|
|
102
|
+
asset_names.add("asset")
|
|
103
|
+
|
|
104
|
+
def is_asset_decorator(decorator: ast.expr) -> bool:
|
|
105
|
+
while isinstance(decorator, ast.Call):
|
|
106
|
+
decorator = decorator.func
|
|
107
|
+
if isinstance(decorator, ast.Name):
|
|
108
|
+
return decorator.id in asset_names
|
|
109
|
+
|
|
110
|
+
names: list[str] = []
|
|
111
|
+
while isinstance(decorator, ast.Attribute):
|
|
112
|
+
names.append(decorator.attr)
|
|
113
|
+
decorator = decorator.value
|
|
114
|
+
if isinstance(decorator, ast.Name):
|
|
115
|
+
names.append(decorator.id)
|
|
116
|
+
names.reverse()
|
|
117
|
+
return len(names) >= 2 and names[0] in tinyfan_namespaces and names[-1] == "asset"
|
|
118
|
+
|
|
119
|
+
class AssetVisitor(ast.NodeVisitor):
|
|
120
|
+
found = False
|
|
121
|
+
|
|
122
|
+
def _visit_definition(self, node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
|
|
123
|
+
if any(is_asset_decorator(decorator) for decorator in node.decorator_list):
|
|
124
|
+
self.found = True
|
|
125
|
+
|
|
126
|
+
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
127
|
+
self._visit_definition(node)
|
|
128
|
+
|
|
129
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
|
|
130
|
+
self._visit_definition(node)
|
|
131
|
+
|
|
132
|
+
def visit_ClassDef(self, node: ast.ClassDef):
|
|
133
|
+
self._visit_definition(node)
|
|
134
|
+
if not self.found:
|
|
135
|
+
self.generic_visit(node)
|
|
136
|
+
|
|
137
|
+
visitor = AssetVisitor()
|
|
138
|
+
visitor.visit(tree)
|
|
139
|
+
return visitor.found
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _package_source_modules(package) -> list[tuple[str, Path]]:
|
|
143
|
+
modules: list[tuple[str, Path]] = []
|
|
144
|
+
for package_path in package.__path__:
|
|
145
|
+
root = Path(package_path)
|
|
146
|
+
for source_path in root.rglob("*.py"):
|
|
147
|
+
relative_path = source_path.relative_to(root)
|
|
148
|
+
if source_path.name == "__init__.py":
|
|
149
|
+
module_parts = relative_path.parts[:-1]
|
|
150
|
+
else:
|
|
151
|
+
module_parts = relative_path.with_suffix("").parts
|
|
152
|
+
if not module_parts:
|
|
153
|
+
# Importing the root package has already executed its __init__.py.
|
|
154
|
+
continue
|
|
155
|
+
modules.append((".".join((package.__name__, *module_parts)), source_path))
|
|
156
|
+
return modules
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def import_asset_modules(location: str, import_all: bool = False):
|
|
160
|
+
"""Import asset-bearing modules from a module name, package directory, or file."""
|
|
79
161
|
try:
|
|
80
162
|
package = importlib.import_module(location)
|
|
81
163
|
except ModuleNotFoundError:
|
|
@@ -90,9 +172,16 @@ def import_all_submodules(location: str):
|
|
|
90
172
|
package = importlib.util.module_from_spec(spec)
|
|
91
173
|
sys.modules[spec.name] = package
|
|
92
174
|
spec.loader.exec_module(package)
|
|
93
|
-
if package.__name__
|
|
94
|
-
|
|
95
|
-
|
|
175
|
+
if package.__name__ == "__tinyfan_repo__" or not hasattr(package, "__path__"):
|
|
176
|
+
return
|
|
177
|
+
for module_name, source_path in _package_source_modules(package):
|
|
178
|
+
if import_all or _defines_tinyfan_asset(source_path):
|
|
179
|
+
importlib.import_module(module_name)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def import_all_submodules(location: str):
|
|
183
|
+
"""Import every submodule, preserving the eager discovery API used before 0.3.0."""
|
|
184
|
+
import_asset_modules(location, import_all=True)
|
|
96
185
|
|
|
97
186
|
|
|
98
187
|
def assetname2template(name: str):
|
|
@@ -496,6 +585,7 @@ def codegen(
|
|
|
496
585
|
embedded: bool = False,
|
|
497
586
|
container: ScriptTemplate = {},
|
|
498
587
|
serviceAccountName: str | None = None,
|
|
588
|
+
import_all: bool = False,
|
|
499
589
|
) -> str:
|
|
500
590
|
"""Generate argocd workflow resource as yaml from tinyfan definitions"""
|
|
501
591
|
try:
|
|
@@ -503,7 +593,7 @@ def codegen(
|
|
|
503
593
|
if flow:
|
|
504
594
|
result = AssetTree(flow).compile(embedded, container, serviceAccountName)
|
|
505
595
|
elif location:
|
|
506
|
-
|
|
596
|
+
import_asset_modules(location, import_all=import_all)
|
|
507
597
|
result = "\n---\n".join(
|
|
508
598
|
AssetTree(flow).compile(embedded, container, serviceAccountName) for flow in FLOW_REGISTER.values()
|
|
509
599
|
)
|
|
@@ -4,6 +4,7 @@ from tinyfan.codegen import codegen, AssetTree
|
|
|
4
4
|
from jsonschema.exceptions import ValidationError
|
|
5
5
|
from typing import Generator
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
import pytest
|
|
8
9
|
import yaml
|
|
9
10
|
|
|
@@ -121,9 +122,7 @@ def test_codegen_asset_inputs_outputs(validate_crds):
|
|
|
121
122
|
validate_crds(actual)
|
|
122
123
|
manifest = next(yaml.load_all(actual, yaml.Loader))
|
|
123
124
|
template = next(
|
|
124
|
-
template
|
|
125
|
-
for template in manifest["spec"]["workflowSpec"]["templates"]
|
|
126
|
-
if template["name"] == "asset1"
|
|
125
|
+
template for template in manifest["spec"]["workflowSpec"]["templates"] if template["name"] == "asset1"
|
|
127
126
|
)
|
|
128
127
|
input_params = {param["name"]: param for param in template["inputs"]["parameters"]}
|
|
129
128
|
output_params = {param["name"]: param for param in template["outputs"]["parameters"]}
|
|
@@ -170,6 +169,74 @@ def test_codegen_directory(validate_crds):
|
|
|
170
169
|
assert_code_is_running(actual)
|
|
171
170
|
|
|
172
171
|
|
|
172
|
+
def test_codegen_directory_only_imports_asset_modules(tmp_path, monkeypatch, validate_crds):
|
|
173
|
+
FLOW_REGISTER.clear()
|
|
174
|
+
package_name = "selective_asset_package"
|
|
175
|
+
package_path = tmp_path / package_name
|
|
176
|
+
package_path.mkdir()
|
|
177
|
+
(package_path / "__init__.py").write_text("", encoding="utf-8")
|
|
178
|
+
(package_path / "flow.py").write_text(
|
|
179
|
+
'from tinyfan import Flow\nflow = Flow("selective-discovery")\n',
|
|
180
|
+
encoding="utf-8",
|
|
181
|
+
)
|
|
182
|
+
(package_path / "assets.py").write_text(
|
|
183
|
+
"import tinyfan as tf\n"
|
|
184
|
+
"from .flow import flow\n\n"
|
|
185
|
+
'@tf.asset(flow=flow, schedule="@daily")\n'
|
|
186
|
+
"def discovered_asset():\n"
|
|
187
|
+
' return "hello"\n',
|
|
188
|
+
encoding="utf-8",
|
|
189
|
+
)
|
|
190
|
+
(package_path / "unrelated.py").write_text(
|
|
191
|
+
'raise RuntimeError("unrelated module was imported")\n',
|
|
192
|
+
encoding="utf-8",
|
|
193
|
+
)
|
|
194
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
actual = codegen(location=package_name)
|
|
198
|
+
|
|
199
|
+
validate_crds(actual)
|
|
200
|
+
assert f"{package_name}.assets" in sys.modules
|
|
201
|
+
assert f"{package_name}.flow" in sys.modules
|
|
202
|
+
assert f"{package_name}.unrelated" not in sys.modules
|
|
203
|
+
finally:
|
|
204
|
+
for module_name in list(sys.modules):
|
|
205
|
+
if module_name == package_name or module_name.startswith(f"{package_name}."):
|
|
206
|
+
sys.modules.pop(module_name)
|
|
207
|
+
FLOW_REGISTER.clear()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_codegen_import_all_supports_dynamic_asset_decorators(tmp_path, monkeypatch, validate_crds):
|
|
211
|
+
FLOW_REGISTER.clear()
|
|
212
|
+
package_name = "dynamic_asset_package"
|
|
213
|
+
package_path = tmp_path / package_name
|
|
214
|
+
package_path.mkdir()
|
|
215
|
+
(package_path / "__init__.py").write_text("", encoding="utf-8")
|
|
216
|
+
(package_path / "assets.py").write_text(
|
|
217
|
+
"from tinyfan import asset\n\n"
|
|
218
|
+
'scheduled_asset = asset(schedule="@daily")\n\n'
|
|
219
|
+
"@scheduled_asset\n"
|
|
220
|
+
"def dynamic_asset():\n"
|
|
221
|
+
' return "hello"\n',
|
|
222
|
+
encoding="utf-8",
|
|
223
|
+
)
|
|
224
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
codegen(location=package_name)
|
|
228
|
+
assert f"{package_name}.assets" not in sys.modules
|
|
229
|
+
|
|
230
|
+
actual = codegen(location=package_name, import_all=True)
|
|
231
|
+
validate_crds(actual)
|
|
232
|
+
assert f"{package_name}.assets" in sys.modules
|
|
233
|
+
finally:
|
|
234
|
+
for module_name in list(sys.modules):
|
|
235
|
+
if module_name == package_name or module_name.startswith(f"{package_name}."):
|
|
236
|
+
sys.modules.pop(module_name)
|
|
237
|
+
FLOW_REGISTER.clear()
|
|
238
|
+
|
|
239
|
+
|
|
173
240
|
def test_rundata_data_interval(sample_flow):
|
|
174
241
|
tree = AssetTree(sample_flow)
|
|
175
242
|
node = list(tree.nodes.values())[0]
|
|
@@ -853,7 +853,7 @@ wheels = [
|
|
|
853
853
|
|
|
854
854
|
[[package]]
|
|
855
855
|
name = "tinyfan"
|
|
856
|
-
version = "0.
|
|
856
|
+
version = "0.4.0"
|
|
857
857
|
source = { editable = "." }
|
|
858
858
|
dependencies = [
|
|
859
859
|
{ name = "croniter" },
|
|
@@ -903,7 +903,7 @@ viz = [
|
|
|
903
903
|
|
|
904
904
|
[package.metadata]
|
|
905
905
|
requires-dist = [
|
|
906
|
-
{ name = "croniter", specifier = ">=6
|
|
906
|
+
{ name = "croniter", specifier = ">=6" },
|
|
907
907
|
{ name = "google-cloud-bigquery", extras = ["pandas"], marker = "extra == 'bigquery'" },
|
|
908
908
|
{ name = "google-cloud-storage", marker = "extra == 'gcs'" },
|
|
909
909
|
{ name = "jsonpatch", marker = "extra == 'dev'", specifier = ">=1" },
|
|
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
|
|
File without changes
|