tinyfan 0.1.2__tar.gz → 0.2.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 (56) hide show
  1. {tinyfan-0.1.2 → tinyfan-0.2.1}/PKG-INFO +3 -1
  2. tinyfan-0.2.1/examples/README.md +12 -0
  3. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/configs.py +1 -1
  4. {tinyfan-0.1.2 → tinyfan-0.2.1}/pyproject.toml +10 -31
  5. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/codegen.py +22 -12
  6. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/flow.py +4 -0
  7. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/utils/merge.py +10 -0
  8. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/test_codegen.py +11 -0
  9. tinyfan-0.2.1/tests/utils/__init__.py +0 -0
  10. {tinyfan-0.1.2 → tinyfan-0.2.1}/uv.lock +14 -1
  11. {tinyfan-0.1.2 → tinyfan-0.2.1}/.github/workflows/publish.yml +0 -0
  12. {tinyfan-0.1.2 → tinyfan-0.2.1}/.gitignore +0 -0
  13. {tinyfan-0.1.2 → tinyfan-0.2.1}/.python-version +0 -0
  14. {tinyfan-0.1.2 → tinyfan-0.2.1}/README.md +0 -0
  15. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/configmap.yaml +0 -0
  16. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/getting-started.py +0 -0
  17. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/.python-version +0 -0
  18. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/Dockerfile +0 -0
  19. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/README.md +0 -0
  20. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/pyproject.toml +0 -0
  21. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/src/tinyfan_examples_packaged/__init__.py +0 -0
  22. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/packaged/src/tinyfan_examples_packaged/asset.py +0 -0
  23. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/resource.py +0 -0
  24. {tinyfan-0.1.2 → tinyfan-0.2.1}/examples/secret.yaml +0 -0
  25. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/__init__.py +0 -0
  26. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/argo_typing.py +0 -0
  27. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/cli.py +0 -0
  28. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/config.py +0 -0
  29. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/flowrundata.py +0 -0
  30. /tinyfan-0.1.2/src/tinyfan/resources/__init__.py → /tinyfan-0.2.1/src/tinyfan/py.typed +0 -0
  31. {tinyfan-0.1.2/src/tinyfan/stores → tinyfan-0.2.1/src/tinyfan/resources}/__init__.py +0 -0
  32. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/resources/base.py +0 -0
  33. {tinyfan-0.1.2/src/tinyfan/utils → tinyfan-0.2.1/src/tinyfan/stores}/__init__.py +0 -0
  34. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/stores/base.py +0 -0
  35. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/stores/bigquery.py +0 -0
  36. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/stores/gcs_pickle.py +0 -0
  37. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/stores/naive.py +0 -0
  38. {tinyfan-0.1.2/tests → tinyfan-0.2.1/src/tinyfan/utils}/__init__.py +0 -0
  39. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/utils/embed.py +0 -0
  40. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/utils/exjson.py +0 -0
  41. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/utils/typing.py +0 -0
  42. {tinyfan-0.1.2 → tinyfan-0.2.1}/src/tinyfan/utils/yaml.py +0 -0
  43. {tinyfan-0.1.2/tests/codegen_samples/directory → tinyfan-0.2.1/tests}/__init__.py +0 -0
  44. {tinyfan-0.1.2/tests/utils → tinyfan-0.2.1/tests/codegen_samples/directory}/__init__.py +0 -0
  45. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/codegen_samples/directory/asset.py +0 -0
  46. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/codegen_samples/directory/asset2.py +0 -0
  47. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/codegen_samples/singlefile.py +0 -0
  48. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/conftest.py +0 -0
  49. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/helpers.py +0 -0
  50. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/openapi2jsonschema.py +0 -0
  51. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/test_decorators.py +0 -0
  52. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/utils/embed_samples/directory/__init__.py +0 -0
  53. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/utils/embed_samples/directory/dep.py +0 -0
  54. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/utils/embed_samples/singlefile.py +0 -0
  55. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/utils/test_embed.py +0 -0
  56. {tinyfan-0.1.2 → tinyfan-0.2.1}/tests/utils/test_exjson.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyfan
3
- Version: 0.1.2
3
+ Version: 0.2.1
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
@@ -12,6 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Typing :: Typed
15
16
  Requires-Python: >=3.12
16
17
  Requires-Dist: croniter>=3.0.3
17
18
  Requires-Dist: pyyaml>=6.0.2
@@ -31,6 +32,7 @@ Requires-Dist: jsonschema[format]; extra == 'dev'
31
32
  Requires-Dist: mypy>=1.14; extra == 'dev'
32
33
  Requires-Dist: pytest>=8.3.3; extra == 'dev'
33
34
  Requires-Dist: ruff; extra == 'dev'
35
+ Requires-Dist: types-croniter; extra == 'dev'
34
36
  Requires-Dist: types-jsonschema; extra == 'dev'
35
37
  Requires-Dist: types-pyyaml; extra == 'dev'
36
38
  Provides-Extra: gcs
@@ -0,0 +1,12 @@
1
+ # How to run
2
+
3
+ ```bash
4
+ # setup k3d
5
+ k3d cluster create tinyfan
6
+ kubectl config use-context k3d-tinyfan
7
+ # install argowf
8
+ kubectl create namespace argo
9
+ kubectl apply -n argo -f "https://github.com/argoproj/argo-workflows/releases/download/v3.6.4/quick-start-minimal.yaml"
10
+ # apply manifests
11
+ tinyfan examples/getting-started.py | kubectl apply -n argo -f -
12
+ ```
@@ -27,5 +27,5 @@ def trace_configs(
27
27
  secretkey: SecretKeyRef,
28
28
  ):
29
29
  print(
30
- f'{constant} - {cliarg} - {configmap.get("otherkey")} - {configmapkey.get_value()} - {secret.get("othersecretkey")} - {secretkey.get_value()}'
30
+ f"{constant} - {cliarg} - {configmap.get('otherkey')} - {configmapkey.get_value()} - {secret.get('othersecretkey')} - {secretkey.get_value()}"
31
31
  )
@@ -1,51 +1,29 @@
1
1
  [project]
2
2
  name = "tinyfan"
3
- version = "0.1.2"
3
+ version = "0.2.1"
4
4
  description = 'Tiny Argo Workflows python devkit'
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
- keywords = [
8
- "argo-workflow",
9
- "tinyfan",
10
- "workflow",
11
- "pipeline",
12
- ]
13
- authors = [
14
- { name = "eunchuldev", email = "eunchulsong@gmail.com" },
15
- ]
7
+ keywords = ["argo-workflow", "tinyfan", "workflow", "pipeline"]
8
+ authors = [{ name = "eunchuldev", email = "eunchulsong@gmail.com" }]
16
9
  classifiers = [
17
10
  "Operating System :: OS Independent",
18
11
  "License :: OSI Approved :: MIT License",
19
12
  "Programming Language :: Python :: 3.10",
20
13
  "Programming Language :: Python :: 3.11",
21
14
  "Programming Language :: Python :: 3.12",
15
+ "Typing :: Typed",
22
16
  ]
23
- dependencies = [
24
- "croniter>=3.0.3",
25
- "pyyaml>=6.0.2",
26
- "typer-slim>=0.15.1",
27
- ]
17
+ dependencies = ["croniter>=3.0.3", "pyyaml>=6.0.2", "typer-slim>=0.15.1"]
28
18
 
29
19
  [project.scripts]
30
20
  tinyfan = "tinyfan.cli:main"
31
21
 
32
22
  [project.optional-dependencies]
33
- all = [
34
- "tinyfan[gcs]",
35
- "tinyfan[bigquery]",
36
- "tinyfan[pandas]",
37
- ]
38
- gcs = [
39
- "google-cloud-storage",
40
- ]
41
- bigquery = [
42
- "google-cloud-bigquery[pandas]",
43
- "tinyfan[pandas]",
44
- ]
45
- pandas = [
46
- "pandas",
47
- "pyarrow",
48
- ]
23
+ all = ["tinyfan[gcs]", "tinyfan[bigquery]", "tinyfan[pandas]"]
24
+ gcs = ["google-cloud-storage"]
25
+ bigquery = ["google-cloud-bigquery[pandas]", "tinyfan[pandas]"]
26
+ pandas = ["pandas", "pyarrow"]
49
27
  dev = [
50
28
  "pytest >= 8.3.3",
51
29
  "jsonpatch>=1",
@@ -54,6 +32,7 @@ dev = [
54
32
  "ruff",
55
33
  "types-PyYAML",
56
34
  "types-jsonschema",
35
+ "types-croniter",
57
36
  ]
58
37
 
59
38
  [project.urls]
@@ -13,7 +13,7 @@ import datetime
13
13
  import croniter
14
14
  from .argo_typing import ScriptTemplate
15
15
  from .utils.embed import embed
16
- from .utils.merge import deepmerge
16
+ from .utils.merge import deepmerge, dropnone
17
17
  from .config import CLI_CONFIG_STATE, Config, ConfigValue, ConfigMapKeyRef, ConfigMapRef, SecretKeyRef, SecretRef
18
18
  from .flowrundata import FlowRunData
19
19
 
@@ -89,19 +89,21 @@ class AssetNode:
89
89
  t1 = cron.get_next(datetime.datetime)
90
90
  t2 = cron.get_next(datetime.datetime)
91
91
  duration_sec = (t2 - t1).total_seconds()
92
+ data_interval_start_tmpl = (
93
+ f"sprig.dateModify('-{duration_sec}s', sprig.toDate('2006-01-02T15:04:05Z07:00', workflow.scheduledTime))"
94
+ )
92
95
  return re.sub(
93
96
  r'"(\{\{tasks\.[^.]+\.outputs\.parameters\.rundata\}\})"',
94
97
  r"\1",
95
98
  json.dumps(
96
99
  {
97
- "ds": "{{=sprig.date('2006-01-02', workflow.scheduledTime)}}",
98
- "ts": "{{workflow.scheduledTime}}",
100
+ "ds": "{{=sprig.date('2006-01-02', %s)}}" % data_interval_start_tmpl,
101
+ "ts": "{{=%s}}" % data_interval_start_tmpl,
99
102
  "data_interval_start": {
100
- DATETIME_ANNOTATION: "{{workflow.scheduledTime}}",
103
+ DATETIME_ANNOTATION: "{{=%s}}" % data_interval_start_tmpl,
101
104
  },
102
105
  "data_interval_end": {
103
- DATETIME_ANNOTATION: "{{=sprig.dateModify('%ss', sprig.toDate('2006-01-02T15:04:05Z07:00', workflow.scheduledTime))}}"
104
- % duration_sec,
106
+ DATETIME_ANNOTATION: "{{workflow.scheduledTime}}",
105
107
  },
106
108
  "parents": {
107
109
  p.asset.name: "{{tasks.%s.outputs.parameters.rundata}}" % p.asset.name for p in self.parents
@@ -171,6 +173,7 @@ class AssetTree:
171
173
  self,
172
174
  embedded: bool = True,
173
175
  container: ScriptTemplate = {},
176
+ serviceAccountName: str | None = None,
174
177
  ) -> str:
175
178
  if len(self.nodes) == 0:
176
179
  return ""
@@ -186,7 +189,7 @@ class AssetTree:
186
189
  root_location = get_root_path(list(self.nodes.values())[0].asset.func)
187
190
  source += embed(root_location)
188
191
  if not root_location.endswith(".py"):
189
- source += "from {{inputs.parameters.module_name}}" " import {{inputs.parameters.asset_name}}\n"
192
+ source += "from {{inputs.parameters.module_name}} import {{inputs.parameters.asset_name}}\n"
190
193
  source += "asset = {{inputs.parameters.asset_name}}.asset\n"
191
194
  else:
192
195
  source = (
@@ -219,14 +222,15 @@ class AssetTree:
219
222
  "apiVersion": "argoproj.io/v1alpha1",
220
223
  "kind": "CronWorkflow",
221
224
  "metadata": {
222
- "name": f"""{self.flow.name}-{ encode_schedule_to_k8sname(tz, schedule) }""",
223
- "generateName": f"""{self.flow.name}-{ encode_schedule_to_k8sname(tz, schedule) }-""",
225
+ "name": f"""{self.flow.name}-{encode_schedule_to_k8sname(tz, schedule)}""",
226
+ "generateName": f"""{self.flow.name}-{encode_schedule_to_k8sname(tz, schedule)}-""",
224
227
  },
225
228
  "spec": {
226
229
  "schedule": schedule,
227
230
  "timezone": tz,
228
231
  "workflowSpec": {
229
232
  "entrypoint": "flow",
233
+ **dropnone({"serviceAccountName": serviceAccountName or self.flow.serviceAccountName}),
230
234
  "podSpecPatch": yaml_dump(
231
235
  {
232
236
  "containers": [
@@ -249,6 +253,7 @@ class AssetTree:
249
253
  "templates": [
250
254
  {
251
255
  "name": node.asset.name.replace("_", "-"),
256
+ **dropnone({"serviceAccountName": node.asset.serviceAccountName}),
252
257
  "script": deepmerge(
253
258
  {
254
259
  "image": DEFAULT_IMAGE,
@@ -391,15 +396,20 @@ def codegen(
391
396
  location: str | None = None,
392
397
  embedded: bool = False,
393
398
  container: ScriptTemplate = {},
399
+ serviceAccountName: str | None = None,
394
400
  ) -> str:
395
401
  """Generate argocd workflow resource as yaml from tinyfan definitions"""
396
402
  CLI_CONFIG_STATE.codegen = True
397
403
  if flow:
398
- result = AssetTree(flow).compile(embedded, container)
404
+ result = AssetTree(flow).compile(embedded, container, serviceAccountName)
399
405
  elif location:
400
406
  import_all_submodules(location)
401
- result = "\n---\n".join(AssetTree(flow).compile(embedded, container) for flow in FLOW_REGISTER.values())
407
+ result = "\n---\n".join(
408
+ AssetTree(flow).compile(embedded, container, serviceAccountName) for flow in FLOW_REGISTER.values()
409
+ )
402
410
  else:
403
- result = "\n---\n".join(AssetTree(flow).compile(embedded, container) for flow in FLOW_REGISTER.values())
411
+ result = "\n---\n".join(
412
+ AssetTree(flow).compile(embedded, container, serviceAccountName) for flow in FLOW_REGISTER.values()
413
+ )
404
414
  CLI_CONFIG_STATE.codegen = False
405
415
  return result
@@ -22,6 +22,7 @@ class Flow:
22
22
  assets: dict[str, "Asset[Any, Any, Any]"] = field(default_factory=dict)
23
23
  resources: dict[str, ResourceBase] | None = None
24
24
  container: ScriptTemplate | None = None
25
+ serviceAccountName: str | None = None
25
26
  store: StoreBase = field(default_factory=NaiveStore)
26
27
  configs: dict[str, Config | ConfigValue | str | None] = field(default_factory=dict)
27
28
 
@@ -48,6 +49,7 @@ class Asset(Generic[Ret, UMeta, StoreIdx]):
48
49
  depends: str | None = None
49
50
  name: str = field(init=False)
50
51
  container: ScriptTemplate | None = None
52
+ serviceAccountName: str | None = None
51
53
 
52
54
  def __post_init__(self):
53
55
  self.name = self.func.__name__
@@ -110,6 +112,7 @@ def asset(
110
112
  tz: str | None = None,
111
113
  metadata: UMeta | None = None,
112
114
  container: ScriptTemplate | None = None,
115
+ serviceAccountName: str | None = None,
113
116
  ) -> Callable[..., AssetFunc[Ret, UMeta, StoreIdx]]:
114
117
  def wrapper(func: Callable[..., Ret]) -> AssetFunc[Ret, UMeta, StoreIdx]:
115
118
  asset = Asset[Ret, UMeta, StoreIdx](
@@ -121,6 +124,7 @@ def asset(
121
124
  tz=tz or flow.tz,
122
125
  metadata=metadata,
123
126
  container=container,
127
+ serviceAccountName=serviceAccountName,
124
128
  )
125
129
  return AssetFunc(func, asset)
126
130
 
@@ -15,3 +15,13 @@ def deepmerge(dict1: T, dict2: T, *args: T) -> T:
15
15
  for arg in args:
16
16
  deepmerge(dict1, arg)
17
17
  return dict1
18
+
19
+
20
+ def dropnone(d: dict) -> dict:
21
+ dropping = []
22
+ for k in d:
23
+ if d[k] is None:
24
+ dropping.append(k)
25
+ for k in dropping:
26
+ del d[k]
27
+ return d
@@ -127,3 +127,14 @@ def test_codegen_directory(validate_crds):
127
127
  if e.json_path != "$.spec.templates[0].script" or e.validator != "required":
128
128
  raise e
129
129
  assert_code_is_running(actual)
130
+
131
+
132
+ def test_rundata_data_interval(sample_flow):
133
+ tree = AssetTree(sample_flow)
134
+ node = list(tree.nodes.values())[0]
135
+ # rundatatmpl = node.rundatatmpl("0 * * * * *")
136
+ daily = node.rundatatmpl("@daily")
137
+ assert (
138
+ daily
139
+ == """{"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
+ )
File without changes
@@ -1,4 +1,5 @@
1
1
  version = 1
2
+ revision = 1
2
3
  requires-python = ">=3.12"
3
4
 
4
5
  [[package]]
@@ -774,7 +775,7 @@ wheels = [
774
775
 
775
776
  [[package]]
776
777
  name = "tinyfan"
777
- version = "0.1.2"
778
+ version = "0.2.1"
778
779
  source = { editable = "." }
779
780
  dependencies = [
780
781
  { name = "croniter" },
@@ -800,6 +801,7 @@ dev = [
800
801
  { name = "mypy" },
801
802
  { name = "pytest" },
802
803
  { name = "ruff" },
804
+ { name = "types-croniter" },
803
805
  { name = "types-jsonschema" },
804
806
  { name = "types-pyyaml" },
805
807
  ]
@@ -829,9 +831,11 @@ requires-dist = [
829
831
  { name = "tinyfan", extras = ["pandas"], marker = "extra == 'all'" },
830
832
  { name = "tinyfan", extras = ["pandas"], marker = "extra == 'bigquery'" },
831
833
  { name = "typer-slim", specifier = ">=0.15.1" },
834
+ { name = "types-croniter", marker = "extra == 'dev'" },
832
835
  { name = "types-jsonschema", marker = "extra == 'dev'" },
833
836
  { name = "types-pyyaml", marker = "extra == 'dev'" },
834
837
  ]
838
+ provides-extras = ["all", "gcs", "bigquery", "pandas", "dev"]
835
839
 
836
840
  [[package]]
837
841
  name = "typer-slim"
@@ -846,6 +850,15 @@ wheels = [
846
850
  { url = "https://files.pythonhosted.org/packages/1f/7b/032ecd581e2170513bb6dc3cdb2581e20fdb94a272bae70fe93f2bca580b/typer_slim-0.15.1-py3-none-any.whl", hash = "sha256:20233cb89938ea3cca633afee10b906a1b0e7c5330f31ed8c55f4f0779efe6df", size = 44968 },
847
851
  ]
848
852
 
853
+ [[package]]
854
+ name = "types-croniter"
855
+ version = "5.0.1.20241205"
856
+ source = { registry = "https://pypi.org/simple" }
857
+ sdist = { url = "https://files.pythonhosted.org/packages/77/2f/f3298f148a7983df2a878d53c4a17ef1dd8654a155e11caa8decb7d79670/types_croniter-5.0.1.20241205.tar.gz", hash = "sha256:8a7cb10aa0b487c654a10c14d4e99098c92a5d68ee55c7a92a3df095831a6933", size = 10928 }
858
+ wheels = [
859
+ { url = "https://files.pythonhosted.org/packages/dc/ec/ff0222b912fdff040a1908ea226f64ff5a1a384a4062a8714eb8b80ea5d9/types_croniter-5.0.1.20241205-py3-none-any.whl", hash = "sha256:e36e992d6a4600ddf510991a20d3273b888ecea5d77a19078348e00775ba54d3", size = 9375 },
860
+ ]
861
+
849
862
  [[package]]
850
863
  name = "types-jsonschema"
851
864
  version = "4.23.0.20240813"
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