tinyfan 0.4.0__tar.gz → 0.5.1__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.
Files changed (67) hide show
  1. {tinyfan-0.4.0 → tinyfan-0.5.1}/.github/workflows/publish.yml +4 -2
  2. tinyfan-0.5.1/PKG-INFO +258 -0
  3. tinyfan-0.5.1/README.md +206 -0
  4. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/configs.py +1 -1
  5. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/getting-started.py +17 -7
  6. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/src/tinyfan_examples_packaged/asset.py +2 -1
  7. {tinyfan-0.4.0 → tinyfan-0.5.1}/pyproject.toml +2 -2
  8. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/__init__.py +10 -10
  9. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/argo_typing.py +1 -1
  10. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/cli.py +4 -7
  11. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/codegen.py +231 -152
  12. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/config.py +15 -11
  13. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/dagviz.py +7 -6
  14. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/flow.py +19 -10
  15. tinyfan-0.5.1/src/tinyfan/flowrundata.py +84 -0
  16. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/resources/base.py +2 -1
  17. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/stores/base.py +5 -5
  18. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/stores/naive.py +3 -2
  19. tinyfan-0.5.1/src/tinyfan/utils/embed.py +171 -0
  20. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/utils/exjson.py +3 -4
  21. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/utils/merge.py +5 -4
  22. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/codegen_samples/directory/asset2.py +1 -0
  23. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/conftest.py +5 -9
  24. tinyfan-0.5.1/tests/helpers.py +21 -0
  25. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/openapi2jsonschema.py +12 -21
  26. tinyfan-0.5.1/tests/test_codegen.py +731 -0
  27. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/test_config.py +35 -6
  28. tinyfan-0.5.1/tests/test_flowrundata.py +124 -0
  29. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/test_gcs_pickle_store.py +4 -3
  30. tinyfan-0.5.1/tests/utils/test_embed.py +242 -0
  31. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/utils/test_exjson.py +2 -1
  32. {tinyfan-0.4.0 → tinyfan-0.5.1}/uv.lock +7 -6
  33. tinyfan-0.4.0/PKG-INFO +0 -124
  34. tinyfan-0.4.0/README.md +0 -73
  35. tinyfan-0.4.0/src/tinyfan/flowrundata.py +0 -25
  36. tinyfan-0.4.0/src/tinyfan/utils/embed.py +0 -129
  37. tinyfan-0.4.0/tests/helpers.py +0 -21
  38. tinyfan-0.4.0/tests/test_codegen.py +0 -257
  39. tinyfan-0.4.0/tests/utils/test_embed.py +0 -55
  40. {tinyfan-0.4.0 → tinyfan-0.5.1}/.gitignore +0 -0
  41. {tinyfan-0.4.0 → tinyfan-0.5.1}/.python-version +0 -0
  42. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/README.md +0 -0
  43. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/configmap.yaml +0 -0
  44. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/.python-version +0 -0
  45. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/Dockerfile +0 -0
  46. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/README.md +0 -0
  47. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/pyproject.toml +0 -0
  48. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/packaged/src/tinyfan_examples_packaged/__init__.py +0 -0
  49. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/resource.py +0 -0
  50. {tinyfan-0.4.0 → tinyfan-0.5.1}/examples/secret.yaml +0 -0
  51. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/py.typed +0 -0
  52. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/resources/__init__.py +0 -0
  53. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/stores/__init__.py +0 -0
  54. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/stores/bigquery.py +0 -0
  55. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/stores/gcs_pickle.py +0 -0
  56. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/utils/__init__.py +0 -0
  57. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/utils/typing.py +0 -0
  58. {tinyfan-0.4.0 → tinyfan-0.5.1}/src/tinyfan/utils/yaml.py +0 -0
  59. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/__init__.py +0 -0
  60. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/codegen_samples/directory/__init__.py +0 -0
  61. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/codegen_samples/directory/asset.py +0 -0
  62. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/codegen_samples/singlefile.py +0 -0
  63. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/test_decorators.py +0 -0
  64. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/utils/__init__.py +0 -0
  65. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/utils/embed_samples/directory/__init__.py +0 -0
  66. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/utils/embed_samples/directory/dep.py +0 -0
  67. {tinyfan-0.4.0 → tinyfan-0.5.1}/tests/utils/embed_samples/singlefile.py +0 -0
@@ -21,10 +21,12 @@ jobs:
21
21
  python-version-file: "pyproject.toml"
22
22
  - name: Install the Project
23
23
  run: uv sync --all-extras --dev
24
- - name: Run tests
25
- run: uv run pytest tests
26
24
  - name: Build Package
27
25
  run: uv build
26
+ - name: Test installed wheel
27
+ run: |
28
+ uv pip install --no-deps --reinstall dist/*.whl
29
+ uv run --no-sync pytest tests
28
30
  - name: Publish Project
29
31
  run: uv publish
30
32
  env:
tinyfan-0.5.1/PKG-INFO ADDED
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.5
2
+ Name: tinyfan
3
+ Version: 0.5.1
4
+ Summary: Tiny Argo Workflows python devkit
5
+ Project-URL: Documentation, https://github.com/eunchuldev/tinyfan#readme
6
+ Project-URL: Issues, https://github.com/eunchuldev/tinyfan/issues
7
+ Project-URL: Source, https://github.com/eunchuldev/tinyfan
8
+ Author-email: eunchuldev <eunchulsong@gmail.com>
9
+ Keywords: argo-workflow,pipeline,tinyfan,workflow
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.12
15
+ Requires-Dist: croniter>=6.2.4
16
+ Requires-Dist: pyyaml>=6.0.3
17
+ Requires-Dist: typer-slim>=0.24.0
18
+ Requires-Dist: tzdata
19
+ Provides-Extra: all
20
+ Requires-Dist: google-cloud-bigquery[pandas]; extra == 'all'
21
+ Requires-Dist: google-cloud-storage; extra == 'all'
22
+ Requires-Dist: networkx; extra == 'all'
23
+ Requires-Dist: pandas; extra == 'all'
24
+ Requires-Dist: pyarrow; extra == 'all'
25
+ Provides-Extra: bigquery
26
+ Requires-Dist: google-cloud-bigquery[pandas]; extra == 'bigquery'
27
+ Requires-Dist: pandas; extra == 'bigquery'
28
+ Requires-Dist: pyarrow; extra == 'bigquery'
29
+ Provides-Extra: dev
30
+ Requires-Dist: google-cloud-bigquery[pandas]; extra == 'dev'
31
+ Requires-Dist: google-cloud-storage; extra == 'dev'
32
+ Requires-Dist: jsonpatch>=1; extra == 'dev'
33
+ Requires-Dist: jsonschema[format]; extra == 'dev'
34
+ Requires-Dist: mypy>=1.14; extra == 'dev'
35
+ Requires-Dist: networkx; extra == 'dev'
36
+ Requires-Dist: pandas; extra == 'dev'
37
+ Requires-Dist: pyarrow; extra == 'dev'
38
+ Requires-Dist: pytest>=8.3.3; extra == 'dev'
39
+ Requires-Dist: ruff; extra == 'dev'
40
+ Requires-Dist: types-croniter; extra == 'dev'
41
+ Requires-Dist: types-jsonschema; extra == 'dev'
42
+ Requires-Dist: types-networkx; extra == 'dev'
43
+ Requires-Dist: types-pyyaml; extra == 'dev'
44
+ Provides-Extra: gcs
45
+ Requires-Dist: google-cloud-storage; extra == 'gcs'
46
+ Provides-Extra: pandas
47
+ Requires-Dist: pandas; extra == 'pandas'
48
+ Requires-Dist: pyarrow; extra == 'pandas'
49
+ Provides-Extra: viz
50
+ Requires-Dist: networkx; extra == 'viz'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # Tinyfan
54
+
55
+ Tinyfan: Minimalist Data Pipeline Kit - Generate Argo Workflows with Python
56
+
57
+ # Features
58
+
59
+ * Generate Argo Workflows manifests from Python data pipeline definitions.
60
+ * Ease of Use and highly extendable
61
+ * Intuitive data model abstraction: let Argo handle orchestration—we focus on the data.
62
+ * Argo Workflows is notably lightweight and powerful – and so are we!
63
+ * Enhanced DevOps Experience: easly testable, Cloud Native and GitOps-ready.
64
+
65
+ # Our Goal
66
+
67
+ * **Minimize mental overhead** when building data pipelines.
68
+
69
+ # Not Our Goal
70
+
71
+ * **Full-featured orchestration framework:** We don't aim to be a battery-powered, comprehensive data pipeline orchestration solution.
72
+ No databases, web servers, or controllers—just a data pipeline compiler. Let's Algo Workflows handle all the complexity.
73
+
74
+ # Installation
75
+
76
+ ```
77
+ # Requires Python 3.10+
78
+ pipx install tinyfan
79
+ ```
80
+
81
+ # Tiny Example
82
+
83
+ ```python
84
+ # main.py
85
+
86
+ # Asset definitions
87
+
88
+ from tinyfan import asset
89
+
90
+
91
+ @asset(schedule="*/3 * * * *")
92
+ def world() -> str:
93
+ return "world"
94
+
95
+
96
+ @asset()
97
+ def greeting(world: str):
98
+ print("hello " + world)
99
+ ```
100
+
101
+ ```shell
102
+ # Apply the changes to argo workflow
103
+
104
+ tinyfan main.py | kubectl apply -f -
105
+ ```
106
+
107
+ For a package, pass its directory or import name. Tinyfan statically discovers
108
+ modules containing `@asset`, so unrelated submodules are not executed during
109
+ manifest generation. Modules imported by an asset module (for example, a shared
110
+ `Flow` definition) are loaded normally.
111
+
112
+ ```shell
113
+ tinyfan src/my_package
114
+ ```
115
+
116
+ Automatic package discovery requires statically recognizable Tinyfan `@asset`
117
+ decorators. Dynamic asset registration and custom decorator wrappers are not
118
+ automatically discovered.
119
+
120
+ # Native Argo options
121
+
122
+ Use native Argo field names at the level where Argo defines them:
123
+
124
+ | Tinyfan option | Generated location | Examples |
125
+ | --- | --- | --- |
126
+ | `Flow(workflowSpec={...})` or `codegen(workflowSpec={...})` | `spec.workflowSpec` | `nodeSelector`, `tolerations`, `affinity`, `parallelism`, `templateDefaults` |
127
+ | `@asset(template={...})` | The asset's entry in `workflowSpec.templates` | `nodeSelector`, `tolerations`, `retryStrategy`, `timeout`, pod `metadata` |
128
+ | `@asset(task={...})` | The asset's DAG task | `when`, additional `arguments` |
129
+ | `container={...}` | The script container | `image`, `resources`, `env`, `volumeMounts` |
130
+
131
+ ```python
132
+ from tinyfan import Flow, asset
133
+
134
+ flow = Flow(
135
+ "batch",
136
+ workflowSpec={
137
+ "nodeSelector": {"nodepool": "batch"},
138
+ "tolerations": [{"key": "batch", "operator": "Exists", "effect": "NoSchedule"}],
139
+ "arguments": {"parameters": [{"name": "enabled", "value": "true"}]},
140
+ "templateDefaults": {"retryStrategy": {"limit": "2"}},
141
+ },
142
+ )
143
+
144
+ @asset(
145
+ flow=flow,
146
+ schedule="@daily",
147
+ container={"image": "python:3.12"},
148
+ template={"nodeSelector": {"nodepool": "high-memory"}, "timeout": "30m"},
149
+ task={"when": "{{workflow.parameters.enabled}} == true"},
150
+ )
151
+ def report():
152
+ return "ready"
153
+ ```
154
+
155
+ These dictionaries pass native options through without a field whitelist, so
156
+ additional Argo options do not need new Tinyfan keyword arguments. They must be
157
+ valid for your installed Argo version; see the [Argo field reference](https://argo-workflows.readthedocs.io/en/latest/fields/).
158
+ Template and workflow scheduling inheritance follows Argo's rules. Tinyfan does
159
+ not move pod or task fields into the container.
160
+
161
+ `Flow.workflowSpec` overrides `codegen(workflowSpec=...)` defaults: dictionaries
162
+ merge recursively, while lists and scalar values are replaced, including empty
163
+ lists, `False`, and zero. Configuration dictionaries are not mutated. Explicit
164
+ `serviceAccountName` arguments take precedence over the corresponding native
165
+ option. Use `workflowSpec.templateDefaults` for defaults shared by asset templates.
166
+
167
+ Tinyfan rejects options that replace its generated wiring: workflow entrypoint,
168
+ templates and source pod patch; template identity, executable body, inputs,
169
+ outputs, source pod patch and mutex; and task identity, template and dependencies.
170
+ Use the existing `container`, `inputs`, `outputs`, and `depends` arguments for
171
+ those settings. `task.arguments` can add parameters and artifacts, but cannot
172
+ replace Tinyfan's reserved runtime parameters or input artifact. `continueOn` is incompatible with
173
+ Argo's enhanced `depends`; use a condition such as `depends="report.Failed"`.
174
+
175
+ `when` is an Argo expression and does not infer dependency edges. Declare upstream
176
+ assets through function arguments or `depends`; use Argo task names (underscores
177
+ become hyphens) inside expressions. A parent that did not succeed has empty
178
+ runtime metadata and supplies `None` as its stored value. To skip a consumer
179
+ unless its parent succeeded, use `depends="report.Succeeded"`.
180
+
181
+ These options require Tinyfan 0.5.1 or newer in both the compiler environment and
182
+ packaged workflow images. Regenerate manifests after upgrading.
183
+
184
+ # Embedded source
185
+
186
+ Embedded workflows unpack their source into temporary directories, so package
187
+ data works with both `importlib.resources` and paths relative to `__file__`.
188
+ Use `Path(__file__).parent` to locate data; the working directory is unchanged.
189
+
190
+ Regular packages include their files and subdirectories, including extensionless
191
+ data files. Embedding a nested module includes its top-level package. Standalone
192
+ scripts include their containing directory so sibling imports and data files are
193
+ available; keep scripts in a directory containing only files intended for the
194
+ workflow. Git metadata, `.venv`/`venv`, Python bytecode, and common tool caches are
195
+ excluded by default. Scripts execute as modules, preserving `__file__`, future
196
+ imports, and runtime annotations; `if __name__ == "__main__"` blocks do not run.
197
+
198
+ The embedding utilities accept `includes` and `excludes` glob patterns relative
199
+ to the bundled directory (the top-level package for nested modules). Excluding a
200
+ directory excludes its contents. Bundled imports take precedence over installed
201
+ copies that have not already been imported. Temporary files are cleaned up on
202
+ normal process exit, and unchanged source produces identical bundle bytes.
203
+
204
+ Embedding does not resolve third-party dependencies or copy distribution metadata
205
+ such as `.dist-info`. Install dependencies in the container image, including
206
+ packages that need native libraries or `importlib.metadata`. Import-name embedding
207
+ of a standalone module includes only its `.py` file; use a script path or regular
208
+ package for accompanying data. Native libraries and namespace-package import
209
+ names are rejected; use regular packages with `__init__.py`. Keep embedded source
210
+ and data small because generated workflows carry them in an environment variable.
211
+
212
+ # Runtime metadata
213
+
214
+ Generated workflows read runtime metadata from the reserved `tinyfan-rundata`
215
+ input artifact at `/tmp/tinyfan/input.json`. Parent metadata is transported as
216
+ base64-encoded JSON, preserving datetime values and literal text through Argo
217
+ parameter substitution. Asset functions and store APIs still receive ordinary
218
+ Python values.
219
+
220
+ After upgrading, regenerate and apply all workflow manifests together. The
221
+ internal `rundata` output parameter now contains base64-encoded JSON, written to
222
+ `/tmp/tinyfan/rundata.b64`; it is not compatible with the previous plain JSON
223
+ transport. For packaged workflows, update the Tinyfan version in the container
224
+ image as well.
225
+
226
+ Intervals are calculated at runtime from the triggering workflow's cron schedule,
227
+ timezone, and `workflow.scheduledTime`. If the workflow parameter `cronScheduleTime`
228
+ is present, it takes precedence, supporting the timestamp supplied by
229
+ `argo cron backfill` with its default `--argname`. Both RFC 3339 timestamps and the
230
+ alpha CLI's Go-style timestamps (for example, `2026-03-09 00:00:00 -0400 EDT`)
231
+ are accepted. Invalid or empty supplied values fail instead of falling back.
232
+ The interval runs from the previous
233
+ scheduled occurrence (inclusive) to the current one (exclusive), regardless of
234
+ task delays or retries. Month lengths and leap years are calendar-based; missing
235
+ DST times are skipped and repeated times are distinct occurrences.
236
+ `data_interval_start` and `data_interval_end` are timezone-aware datetimes in the
237
+ schedule's timezone. `ds` is the local start date and `ts` is its ISO timestamp
238
+ including the UTC offset. All tasks in a workflow use that workflow's interval.
239
+
240
+ Embedded workflows include croniter's required runtime modules and timezone data
241
+ for the configured zones; no runtime download is needed. Regenerate manifests
242
+ after timezone-data updates. Packaged images must install Tinyfan's dependencies,
243
+ including croniter 6.2.4 or newer and `tzdata`.
244
+
245
+ Backfill CLI compatibility is version-dependent: use a version supporting
246
+ `spec.schedules` and the configured timezone. Older alpha implementations, such
247
+ as Argo 3.7.0, only read the legacy `spec.schedule` field. Tinyfan does not add a
248
+ duplicate `cronScheduleTime` default argument because the CLI appends it itself.
249
+
250
+ # Real World Example (still tiny though!)
251
+
252
+ Comming soon
253
+
254
+
255
+
256
+ # License
257
+
258
+ This project is licensed under the MIT License.
@@ -0,0 +1,206 @@
1
+ # Tinyfan
2
+
3
+ Tinyfan: Minimalist Data Pipeline Kit - Generate Argo Workflows with Python
4
+
5
+ # Features
6
+
7
+ * Generate Argo Workflows manifests from Python data pipeline definitions.
8
+ * Ease of Use and highly extendable
9
+ * Intuitive data model abstraction: let Argo handle orchestration—we focus on the data.
10
+ * Argo Workflows is notably lightweight and powerful – and so are we!
11
+ * Enhanced DevOps Experience: easly testable, Cloud Native and GitOps-ready.
12
+
13
+ # Our Goal
14
+
15
+ * **Minimize mental overhead** when building data pipelines.
16
+
17
+ # Not Our Goal
18
+
19
+ * **Full-featured orchestration framework:** We don't aim to be a battery-powered, comprehensive data pipeline orchestration solution.
20
+ No databases, web servers, or controllers—just a data pipeline compiler. Let's Algo Workflows handle all the complexity.
21
+
22
+ # Installation
23
+
24
+ ```
25
+ # Requires Python 3.10+
26
+ pipx install tinyfan
27
+ ```
28
+
29
+ # Tiny Example
30
+
31
+ ```python
32
+ # main.py
33
+
34
+ # Asset definitions
35
+
36
+ from tinyfan import asset
37
+
38
+
39
+ @asset(schedule="*/3 * * * *")
40
+ def world() -> str:
41
+ return "world"
42
+
43
+
44
+ @asset()
45
+ def greeting(world: str):
46
+ print("hello " + world)
47
+ ```
48
+
49
+ ```shell
50
+ # Apply the changes to argo workflow
51
+
52
+ tinyfan main.py | kubectl apply -f -
53
+ ```
54
+
55
+ For a package, pass its directory or import name. Tinyfan statically discovers
56
+ modules containing `@asset`, so unrelated submodules are not executed during
57
+ manifest generation. Modules imported by an asset module (for example, a shared
58
+ `Flow` definition) are loaded normally.
59
+
60
+ ```shell
61
+ tinyfan src/my_package
62
+ ```
63
+
64
+ Automatic package discovery requires statically recognizable Tinyfan `@asset`
65
+ decorators. Dynamic asset registration and custom decorator wrappers are not
66
+ automatically discovered.
67
+
68
+ # Native Argo options
69
+
70
+ Use native Argo field names at the level where Argo defines them:
71
+
72
+ | Tinyfan option | Generated location | Examples |
73
+ | --- | --- | --- |
74
+ | `Flow(workflowSpec={...})` or `codegen(workflowSpec={...})` | `spec.workflowSpec` | `nodeSelector`, `tolerations`, `affinity`, `parallelism`, `templateDefaults` |
75
+ | `@asset(template={...})` | The asset's entry in `workflowSpec.templates` | `nodeSelector`, `tolerations`, `retryStrategy`, `timeout`, pod `metadata` |
76
+ | `@asset(task={...})` | The asset's DAG task | `when`, additional `arguments` |
77
+ | `container={...}` | The script container | `image`, `resources`, `env`, `volumeMounts` |
78
+
79
+ ```python
80
+ from tinyfan import Flow, asset
81
+
82
+ flow = Flow(
83
+ "batch",
84
+ workflowSpec={
85
+ "nodeSelector": {"nodepool": "batch"},
86
+ "tolerations": [{"key": "batch", "operator": "Exists", "effect": "NoSchedule"}],
87
+ "arguments": {"parameters": [{"name": "enabled", "value": "true"}]},
88
+ "templateDefaults": {"retryStrategy": {"limit": "2"}},
89
+ },
90
+ )
91
+
92
+ @asset(
93
+ flow=flow,
94
+ schedule="@daily",
95
+ container={"image": "python:3.12"},
96
+ template={"nodeSelector": {"nodepool": "high-memory"}, "timeout": "30m"},
97
+ task={"when": "{{workflow.parameters.enabled}} == true"},
98
+ )
99
+ def report():
100
+ return "ready"
101
+ ```
102
+
103
+ These dictionaries pass native options through without a field whitelist, so
104
+ additional Argo options do not need new Tinyfan keyword arguments. They must be
105
+ valid for your installed Argo version; see the [Argo field reference](https://argo-workflows.readthedocs.io/en/latest/fields/).
106
+ Template and workflow scheduling inheritance follows Argo's rules. Tinyfan does
107
+ not move pod or task fields into the container.
108
+
109
+ `Flow.workflowSpec` overrides `codegen(workflowSpec=...)` defaults: dictionaries
110
+ merge recursively, while lists and scalar values are replaced, including empty
111
+ lists, `False`, and zero. Configuration dictionaries are not mutated. Explicit
112
+ `serviceAccountName` arguments take precedence over the corresponding native
113
+ option. Use `workflowSpec.templateDefaults` for defaults shared by asset templates.
114
+
115
+ Tinyfan rejects options that replace its generated wiring: workflow entrypoint,
116
+ templates and source pod patch; template identity, executable body, inputs,
117
+ outputs, source pod patch and mutex; and task identity, template and dependencies.
118
+ Use the existing `container`, `inputs`, `outputs`, and `depends` arguments for
119
+ those settings. `task.arguments` can add parameters and artifacts, but cannot
120
+ replace Tinyfan's reserved runtime parameters or input artifact. `continueOn` is incompatible with
121
+ Argo's enhanced `depends`; use a condition such as `depends="report.Failed"`.
122
+
123
+ `when` is an Argo expression and does not infer dependency edges. Declare upstream
124
+ assets through function arguments or `depends`; use Argo task names (underscores
125
+ become hyphens) inside expressions. A parent that did not succeed has empty
126
+ runtime metadata and supplies `None` as its stored value. To skip a consumer
127
+ unless its parent succeeded, use `depends="report.Succeeded"`.
128
+
129
+ These options require Tinyfan 0.5.1 or newer in both the compiler environment and
130
+ packaged workflow images. Regenerate manifests after upgrading.
131
+
132
+ # Embedded source
133
+
134
+ Embedded workflows unpack their source into temporary directories, so package
135
+ data works with both `importlib.resources` and paths relative to `__file__`.
136
+ Use `Path(__file__).parent` to locate data; the working directory is unchanged.
137
+
138
+ Regular packages include their files and subdirectories, including extensionless
139
+ data files. Embedding a nested module includes its top-level package. Standalone
140
+ scripts include their containing directory so sibling imports and data files are
141
+ available; keep scripts in a directory containing only files intended for the
142
+ workflow. Git metadata, `.venv`/`venv`, Python bytecode, and common tool caches are
143
+ excluded by default. Scripts execute as modules, preserving `__file__`, future
144
+ imports, and runtime annotations; `if __name__ == "__main__"` blocks do not run.
145
+
146
+ The embedding utilities accept `includes` and `excludes` glob patterns relative
147
+ to the bundled directory (the top-level package for nested modules). Excluding a
148
+ directory excludes its contents. Bundled imports take precedence over installed
149
+ copies that have not already been imported. Temporary files are cleaned up on
150
+ normal process exit, and unchanged source produces identical bundle bytes.
151
+
152
+ Embedding does not resolve third-party dependencies or copy distribution metadata
153
+ such as `.dist-info`. Install dependencies in the container image, including
154
+ packages that need native libraries or `importlib.metadata`. Import-name embedding
155
+ of a standalone module includes only its `.py` file; use a script path or regular
156
+ package for accompanying data. Native libraries and namespace-package import
157
+ names are rejected; use regular packages with `__init__.py`. Keep embedded source
158
+ and data small because generated workflows carry them in an environment variable.
159
+
160
+ # Runtime metadata
161
+
162
+ Generated workflows read runtime metadata from the reserved `tinyfan-rundata`
163
+ input artifact at `/tmp/tinyfan/input.json`. Parent metadata is transported as
164
+ base64-encoded JSON, preserving datetime values and literal text through Argo
165
+ parameter substitution. Asset functions and store APIs still receive ordinary
166
+ Python values.
167
+
168
+ After upgrading, regenerate and apply all workflow manifests together. The
169
+ internal `rundata` output parameter now contains base64-encoded JSON, written to
170
+ `/tmp/tinyfan/rundata.b64`; it is not compatible with the previous plain JSON
171
+ transport. For packaged workflows, update the Tinyfan version in the container
172
+ image as well.
173
+
174
+ Intervals are calculated at runtime from the triggering workflow's cron schedule,
175
+ timezone, and `workflow.scheduledTime`. If the workflow parameter `cronScheduleTime`
176
+ is present, it takes precedence, supporting the timestamp supplied by
177
+ `argo cron backfill` with its default `--argname`. Both RFC 3339 timestamps and the
178
+ alpha CLI's Go-style timestamps (for example, `2026-03-09 00:00:00 -0400 EDT`)
179
+ are accepted. Invalid or empty supplied values fail instead of falling back.
180
+ The interval runs from the previous
181
+ scheduled occurrence (inclusive) to the current one (exclusive), regardless of
182
+ task delays or retries. Month lengths and leap years are calendar-based; missing
183
+ DST times are skipped and repeated times are distinct occurrences.
184
+ `data_interval_start` and `data_interval_end` are timezone-aware datetimes in the
185
+ schedule's timezone. `ds` is the local start date and `ts` is its ISO timestamp
186
+ including the UTC offset. All tasks in a workflow use that workflow's interval.
187
+
188
+ Embedded workflows include croniter's required runtime modules and timezone data
189
+ for the configured zones; no runtime download is needed. Regenerate manifests
190
+ after timezone-data updates. Packaged images must install Tinyfan's dependencies,
191
+ including croniter 6.2.4 or newer and `tzdata`.
192
+
193
+ Backfill CLI compatibility is version-dependent: use a version supporting
194
+ `spec.schedules` and the configured timezone. Older alpha implementations, such
195
+ as Argo 3.7.0, only read the legacy `spec.schedule` field. Tinyfan does not add a
196
+ duplicate `cronScheduleTime` default argument because the CLI appends it itself.
197
+
198
+ # Real World Example (still tiny though!)
199
+
200
+ Comming soon
201
+
202
+
203
+
204
+ # License
205
+
206
+ This project is licensed under the MIT License.
@@ -1,5 +1,5 @@
1
1
  import tinyfan
2
- from tinyfan import ConfigMapRef, ConfigMapKeyRef, SecretRef, SecretKeyRef, cli_arg
2
+ from tinyfan import ConfigMapKeyRef, ConfigMapRef, SecretKeyRef, SecretRef, cli_arg
3
3
 
4
4
  flow = tinyfan.Flow(
5
5
  name="configs",
@@ -1,25 +1,35 @@
1
- import tinyfan
2
1
  from datetime import datetime
3
2
 
3
+ import tinyfan
4
+
4
5
 
5
6
  @tinyfan.asset(
6
7
  schedule="*/1 * * * *",
7
8
  )
8
- def target() -> str:
9
+ def fast_target() -> str:
9
10
  return "world"
10
11
 
11
12
 
12
13
  @tinyfan.asset(
13
- schedule="*/1 * * * *",
14
+ schedule="*/2 * * * *",
14
15
  )
15
- def target2() -> str:
16
+ def slow_target() -> str:
16
17
  return "earth"
17
18
 
19
+ @tinyfan.asset(
20
+ schedule="*/2 * * * *",
21
+ )
22
+ def slow_greeting(fast_target: list[str], slow_target: str):
23
+ for t in fast_target:
24
+ print("hello " + t)
25
+ print("hello " + slow_target)
18
26
 
19
27
  @tinyfan.asset()
20
- def greeting(target: str, target2: str):
21
- print("hello " + target, "hello " + target2)
22
-
28
+ def greeting(fast_target: str|None, slow_target: str|None):
29
+ if slow_target:
30
+ print("hello " + slow_target)
31
+ if fast_target:
32
+ print("hello " + fast_target)
23
33
 
24
34
  @tinyfan.asset(
25
35
  schedule="*/1 * * * *",
@@ -1,6 +1,7 @@
1
- import tinyfan
2
1
  from datetime import datetime
3
2
 
3
+ import tinyfan
4
+
4
5
 
5
6
  @tinyfan.asset(
6
7
  schedule="*/1 * * * *", container={"image": "tinyfan-examples-packaged:local", "imagePullPolicy": "Never"}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinyfan"
3
- version = "0.4.0"
3
+ version = "0.5.1"
4
4
  description = 'Tiny Argo Workflows python devkit'
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -12,7 +12,7 @@ classifiers = [
12
12
  "Programming Language :: Python :: 3.12",
13
13
  "Typing :: Typed",
14
14
  ]
15
- dependencies = ["croniter>=6", "pyyaml>=6.0.3", "typer-slim>=0.24.0"]
15
+ dependencies = ["croniter>=6.2.4", "pyyaml>=6.0.3", "typer-slim>=0.24.0", "tzdata"]
16
16
 
17
17
  [project.scripts]
18
18
  tinyfan = "tinyfan.cli:main"
@@ -1,19 +1,19 @@
1
- from .flow import Flow, Asset, asset
1
+ from .config import ConfigMapKeyRef, ConfigMapRef, SecretKeyRef, SecretRef, cli_arg
2
+ from .flow import Asset, Flow, asset
3
+ from .resources.base import ResourceBase
2
4
  from .stores.base import StoreBase
3
5
  from .stores.naive import NaiveStore
4
- from .config import ConfigMapRef, ConfigMapKeyRef, SecretRef, SecretKeyRef, cli_arg
5
- from .resources.base import ResourceBase
6
6
 
7
7
  __all__ = [
8
- "Flow",
9
8
  "Asset",
10
- "asset",
11
- "StoreBase",
12
- "NaiveStore",
13
- "ConfigMapRef",
14
9
  "ConfigMapKeyRef",
15
- "SecretRef",
10
+ "ConfigMapRef",
11
+ "Flow",
12
+ "NaiveStore",
13
+ "ResourceBase",
16
14
  "SecretKeyRef",
15
+ "SecretRef",
16
+ "StoreBase",
17
+ "asset",
17
18
  "cli_arg",
18
- "ResourceBase",
19
19
  ]
@@ -1,4 +1,4 @@
1
- from typing import Any, TypedDict, NotRequired
1
+ from typing import Any, NotRequired, TypedDict
2
2
 
3
3
 
4
4
  class ExecAction(TypedDict):
@@ -1,6 +1,7 @@
1
+ from typing import Annotated
2
+
1
3
  import typer
2
4
 
3
- from typing_extensions import Annotated
4
5
  from .codegen import codegen
5
6
  from .config import CLI_CONFIG_STATE
6
7
 
@@ -9,10 +10,6 @@ def tinyfan(
9
10
  location: str,
10
11
  embedded: bool | None = None,
11
12
  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,
16
13
  ):
17
14
  """
18
15
  Generate argocd workflow resource as yaml from tinyfan definitions
@@ -20,9 +17,9 @@ def tinyfan(
20
17
  if arg is not None:
21
18
  CLI_CONFIG_STATE.cli_args.update({k: v for (k, v) in (val.split("=") for val in arg)})
22
19
  if location.endswith(".py"):
23
- print(codegen(location=location, embedded=embedded or True, import_all=import_all))
20
+ print(codegen(location=location, embedded=embedded or True))
24
21
  else:
25
- print(codegen(location=location, embedded=embedded or False, import_all=import_all))
22
+ print(codegen(location=location, embedded=embedded or False))
26
23
 
27
24
 
28
25
  def main():