ddeutil-workflow 0.0.6__tar.gz → 0.0.8__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 (55) hide show
  1. {ddeutil_workflow-0.0.6/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.8}/PKG-INFO +48 -76
  2. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/README.md +42 -73
  3. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/pyproject.toml +18 -6
  4. ddeutil_workflow-0.0.8/src/ddeutil/workflow/__about__.py +1 -0
  5. ddeutil_workflow-0.0.8/src/ddeutil/workflow/__init__.py +31 -0
  6. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/__types.py +11 -1
  7. ddeutil_workflow-0.0.8/src/ddeutil/workflow/api.py +120 -0
  8. ddeutil_workflow-0.0.8/src/ddeutil/workflow/app.py +45 -0
  9. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/exceptions.py +3 -3
  10. ddeutil_workflow-0.0.8/src/ddeutil/workflow/log.py +79 -0
  11. ddeutil_workflow-0.0.8/src/ddeutil/workflow/pipeline.py +893 -0
  12. ddeutil_workflow-0.0.8/src/ddeutil/workflow/repeat.py +134 -0
  13. ddeutil_workflow-0.0.8/src/ddeutil/workflow/route.py +78 -0
  14. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/stage.py +209 -86
  15. ddeutil_workflow-0.0.8/src/ddeutil/workflow/utils.py +680 -0
  16. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8/src/ddeutil_workflow.egg-info}/PKG-INFO +48 -76
  17. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil_workflow.egg-info/SOURCES.txt +10 -0
  18. ddeutil_workflow-0.0.8/src/ddeutil_workflow.egg-info/requires.txt +11 -0
  19. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test__regex.py +41 -5
  20. ddeutil_workflow-0.0.8/tests/test_job.py +7 -0
  21. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_if.py +3 -3
  22. ddeutil_workflow-0.0.8/tests/test_pipeline_matrix.py +159 -0
  23. ddeutil_workflow-0.0.8/tests/test_pipeline_on_ready.py +26 -0
  24. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_run.py +1 -1
  25. ddeutil_workflow-0.0.8/tests/test_pipeline_run_raise.py +12 -0
  26. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_task.py +17 -1
  27. ddeutil_workflow-0.0.8/tests/test_stage.py +15 -0
  28. ddeutil_workflow-0.0.8/tests/test_stage_trigger.py +32 -0
  29. ddeutil_workflow-0.0.8/tests/test_utils.py +41 -0
  30. ddeutil_workflow-0.0.8/tests/test_utils_param2template.py +71 -0
  31. ddeutil_workflow-0.0.8/tests/test_utils_result.py +43 -0
  32. ddeutil_workflow-0.0.6/src/ddeutil/workflow/__about__.py +0 -1
  33. ddeutil_workflow-0.0.6/src/ddeutil/workflow/__init__.py +0 -9
  34. ddeutil_workflow-0.0.6/src/ddeutil/workflow/pipeline.py +0 -497
  35. ddeutil_workflow-0.0.6/src/ddeutil/workflow/utils.py +0 -378
  36. ddeutil_workflow-0.0.6/src/ddeutil_workflow.egg-info/requires.txt +0 -7
  37. ddeutil_workflow-0.0.6/tests/test_pipeline_matrix.py +0 -87
  38. ddeutil_workflow-0.0.6/tests/test_stage_trigger.py +0 -10
  39. ddeutil_workflow-0.0.6/tests/test_utils.py +0 -8
  40. ddeutil_workflow-0.0.6/tests/test_utils_result.py +0 -22
  41. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/LICENSE +0 -0
  42. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/setup.cfg +0 -0
  43. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/loader.py +0 -0
  44. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/on.py +0 -0
  45. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil/workflow/scheduler.py +0 -0
  46. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  47. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  48. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test__conf_exist.py +0 -0
  49. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test__local_and_global.py +0 -0
  50. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_on.py +0 -0
  51. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline.py +0 -0
  52. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_desc.py +0 -0
  53. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_on.py +0 -0
  54. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_pipeline_params.py +0 -0
  55. {ddeutil_workflow-0.0.6 → ddeutil_workflow-0.0.8}/tests/test_scheduler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: Data Developer & Engineer Workflow Utility Objects
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -25,8 +25,11 @@ Requires-Dist: fmtutil
25
25
  Requires-Dist: ddeutil-io
26
26
  Requires-Dist: python-dotenv==1.0.1
27
27
  Provides-Extra: app
28
- Requires-Dist: fastapi==0.112.0; extra == "app"
29
- Requires-Dist: apscheduler[sqlalchemy]==3.10.4; extra == "app"
28
+ Requires-Dist: schedule<2.0.0,==1.2.2; extra == "app"
29
+ Provides-Extra: api
30
+ Requires-Dist: fastapi[standard]==0.112.0; extra == "api"
31
+ Requires-Dist: apscheduler[sqlalchemy]<4.0.0,==3.10.4; extra == "api"
32
+ Requires-Dist: croniter==3.0.3; extra == "api"
30
33
 
31
34
  # Workflow
32
35
 
@@ -39,7 +42,6 @@ Requires-Dist: apscheduler[sqlalchemy]==3.10.4; extra == "app"
39
42
 
40
43
  - [Installation](#installation)
41
44
  - [Getting Started](#getting-started)
42
- - [Core Features](#core-features)
43
45
  - [On](#on)
44
46
  - [Pipeline](#pipeline)
45
47
  - [Usage](#usage)
@@ -50,12 +52,14 @@ Requires-Dist: apscheduler[sqlalchemy]==3.10.4; extra == "app"
50
52
  - [Deployment](#deployment)
51
53
 
52
54
  This **Workflow** objects was created for easy to make a simple metadata
53
- driven pipeline that able to **ETL, T, EL, or ELT** by `.yaml` file.
55
+ driven for data pipeline orchestration that able to use for **ETL, T, EL, or
56
+ ELT** by a `.yaml` file template.
54
57
 
55
- I think we should not create the multiple pipeline per use-case if we able to
56
- write some dynamic pipeline that just change the input parameters per use-case
57
- instead. This way we can handle a lot of pipelines in our orgs with metadata only.
58
- It called **Metadata Driven**.
58
+ In my opinion, I think it should not create duplicate pipeline codes if I can
59
+ write with dynamic input parameters on the one template pipeline that just change
60
+ the input parameters per use-case instead.
61
+ This way I can handle a lot of logical pipelines in our orgs with only metadata
62
+ configuration. It called **Metadata Driven Data Pipeline**.
59
63
 
60
64
  Next, we should get some monitoring tools for manage logging that return from
61
65
  pipeline running. Because it not show us what is a use-case that running data
@@ -75,9 +79,10 @@ pip install ddeutil-workflow
75
79
  This project need `ddeutil-io` extension namespace packages. If you want to install
76
80
  this package with application add-ons, you should add `app` in installation;
77
81
 
78
- ```shell
79
- pip install ddeutil-workflow[app]
80
- ```
82
+ | Usecase | Install Optional |
83
+ |--------------------|---------------------------|
84
+ | Scheduler Service | `ddeutil-workflow[app]` |
85
+ | FastAPI Server | `ddeutil-workflow[api]` |
81
86
 
82
87
  ## Getting Started
83
88
 
@@ -160,11 +165,12 @@ use-case.
160
165
  > I recommend you to use `task` stage for all actions that you want to do with
161
166
  > pipeline object.
162
167
 
163
- ### Python & Bash
164
-
165
168
  ```yaml
166
169
  run_py_local:
167
170
  type: pipeline.Pipeline
171
+ on:
172
+ - cronjob: '* * * * *'
173
+ timezone: "Asia/Bangkok"
168
174
  params:
169
175
  author-run: str
170
176
  run-date: datetime
@@ -174,12 +180,11 @@ run_py_local:
174
180
  - name: "Printing Information"
175
181
  id: define-func
176
182
  run: |
177
- x = '${{ params.author-run }}'
178
- print(f'Hello {x}')
183
+ x = '${{ params.run-date | fmt("%Y%m%d") }}'
184
+ print(f'Hello at {x}')
179
185
 
180
186
  def echo(name: str):
181
187
  print(f'Hello {name}')
182
-
183
188
  - name: "Run Sequence and use var from Above"
184
189
  vars:
185
190
  x: ${{ params.author-run }}
@@ -187,7 +192,6 @@ run_py_local:
187
192
  print(f'Receive x from above with {x}')
188
193
  # Change x value
189
194
  x: int = 1
190
-
191
195
  - name: "Call Function"
192
196
  vars:
193
197
  echo: ${{ stages.define-func.outputs.echo }}
@@ -202,75 +206,34 @@ run_py_local:
202
206
  ```
203
207
 
204
208
  ```python
209
+ from datetime import datetime
205
210
  from ddeutil.workflow.pipeline import Pipeline
206
211
 
207
- pipe = Pipeline.from_loader(name='run_py_local', externals={})
208
- pipe.execute(params={'author-run': 'Local Workflow', 'run-date': '2024-01-01'})
212
+ pipe: Pipeline = Pipeline.from_loader(name='run_py_local', externals={})
213
+ pipe.execute(params={
214
+ 'author-run': 'Local Workflow',
215
+ 'run-date': datetime(2024, 1, 1),
216
+ })
209
217
  ```
210
218
 
211
219
  ```shell
212
- > Hello Local Workflow
220
+ > Hello at 20240101
213
221
  > Receive x from above with Local Workflow
214
222
  > Hello Caller
215
223
  > Hello World from Shell
216
224
  ```
217
225
 
218
- ### Hook (Extract & Load)
219
-
220
- ```yaml
221
- pipe_el_pg_to_lake:
222
- type: pipeline.Pipeline
223
- params:
224
- run-date: datetime
225
- author-email: str
226
- jobs:
227
- extract-load:
228
- stages:
229
- - name: "Extract Load from Postgres to Lake"
230
- id: extract-load
231
- uses: tasks/postgres-to-delta@polars
232
- with:
233
- source:
234
- conn: conn_postgres_url
235
- query: |
236
- select * from ${{ params.name }}
237
- where update_date = '${{ params.datetime }}'
238
- sink:
239
- conn: conn_az_lake
240
- endpoint: "/${{ params.name }}"
241
- ```
242
-
243
- ### Hook (Transform)
244
-
245
- ```yaml
246
- pipeline_hook_mssql_proc:
247
- type: pipeline.Pipeline
248
- params:
249
- run_date: datetime
250
- sp_name: str
251
- source_name: str
252
- target_name: str
253
- jobs:
254
- transform:
255
- stages:
256
- - name: "Transform Data in MS SQL Server"
257
- id: transform
258
- uses: tasks/mssql-proc@odbc
259
- with:
260
- exec: ${{ params.sp_name }}
261
- params:
262
- run_mode: "T"
263
- run_date: ${{ params.run_date }}
264
- source: ${{ params.source_name }}
265
- target: ${{ params.target_name }}
266
- ```
267
-
268
226
  ## Configuration
269
227
 
270
228
  ```bash
271
229
  export WORKFLOW_ROOT_PATH=.
272
230
  export WORKFLOW_CORE_REGISTRY=ddeutil.workflow,tests.utils
231
+ export WORKFLOW_CORE_REGISTRY_FILTER=ddeutil.workflow.utils
273
232
  export WORKFLOW_CORE_PATH_CONF=conf
233
+ export WORKFLOW_CORE_TIMEZONE=Asia/Bangkok
234
+ export WORKFLOW_CORE_DEFAULT_STAGE_ID=true
235
+ export WORKFLOW_CORE_MAX_PIPELINE_POKING=4
236
+ export WORKFLOW_CORE_MAX_JOB_PARALLEL=2
274
237
  ```
275
238
 
276
239
  Application config:
@@ -283,12 +246,21 @@ export WORKFLOW_APP_INTERVAL=10
283
246
  ## Deployment
284
247
 
285
248
  This package able to run as a application service for receive manual trigger
286
- from the master node via RestAPI.
249
+ from the master node via RestAPI or use to be Scheduler background service
250
+ like crontab job but via Python API.
287
251
 
288
- > [!WARNING]
289
- > This feature do not start yet because I still research and find the best tool
290
- > to use it provision an app service, like `starlette`, `fastapi`, `apscheduler`.
252
+ ### Schedule Service
291
253
 
292
254
  ```shell
293
- (venv) $ workflow start -p 7070
255
+ (venv) $ python src.ddeutil.workflow.app
294
256
  ```
257
+
258
+ ### API Server
259
+
260
+ ```shell
261
+ (venv) $ uvicorn src.ddeutil.workflow.api:app --host 0.0.0.0 --port 80 --reload
262
+ ```
263
+
264
+ > [!NOTE]
265
+ > If this package already deploy, it able to use
266
+ > `uvicorn ddeutil.workflow.api:app --host 0.0.0.0 --port 80`
@@ -9,7 +9,6 @@
9
9
 
10
10
  - [Installation](#installation)
11
11
  - [Getting Started](#getting-started)
12
- - [Core Features](#core-features)
13
12
  - [On](#on)
14
13
  - [Pipeline](#pipeline)
15
14
  - [Usage](#usage)
@@ -20,12 +19,14 @@
20
19
  - [Deployment](#deployment)
21
20
 
22
21
  This **Workflow** objects was created for easy to make a simple metadata
23
- driven pipeline that able to **ETL, T, EL, or ELT** by `.yaml` file.
22
+ driven for data pipeline orchestration that able to use for **ETL, T, EL, or
23
+ ELT** by a `.yaml` file template.
24
24
 
25
- I think we should not create the multiple pipeline per use-case if we able to
26
- write some dynamic pipeline that just change the input parameters per use-case
27
- instead. This way we can handle a lot of pipelines in our orgs with metadata only.
28
- It called **Metadata Driven**.
25
+ In my opinion, I think it should not create duplicate pipeline codes if I can
26
+ write with dynamic input parameters on the one template pipeline that just change
27
+ the input parameters per use-case instead.
28
+ This way I can handle a lot of logical pipelines in our orgs with only metadata
29
+ configuration. It called **Metadata Driven Data Pipeline**.
29
30
 
30
31
  Next, we should get some monitoring tools for manage logging that return from
31
32
  pipeline running. Because it not show us what is a use-case that running data
@@ -45,9 +46,10 @@ pip install ddeutil-workflow
45
46
  This project need `ddeutil-io` extension namespace packages. If you want to install
46
47
  this package with application add-ons, you should add `app` in installation;
47
48
 
48
- ```shell
49
- pip install ddeutil-workflow[app]
50
- ```
49
+ | Usecase | Install Optional |
50
+ |--------------------|---------------------------|
51
+ | Scheduler Service | `ddeutil-workflow[app]` |
52
+ | FastAPI Server | `ddeutil-workflow[api]` |
51
53
 
52
54
  ## Getting Started
53
55
 
@@ -130,11 +132,12 @@ use-case.
130
132
  > I recommend you to use `task` stage for all actions that you want to do with
131
133
  > pipeline object.
132
134
 
133
- ### Python & Bash
134
-
135
135
  ```yaml
136
136
  run_py_local:
137
137
  type: pipeline.Pipeline
138
+ on:
139
+ - cronjob: '* * * * *'
140
+ timezone: "Asia/Bangkok"
138
141
  params:
139
142
  author-run: str
140
143
  run-date: datetime
@@ -144,12 +147,11 @@ run_py_local:
144
147
  - name: "Printing Information"
145
148
  id: define-func
146
149
  run: |
147
- x = '${{ params.author-run }}'
148
- print(f'Hello {x}')
150
+ x = '${{ params.run-date | fmt("%Y%m%d") }}'
151
+ print(f'Hello at {x}')
149
152
 
150
153
  def echo(name: str):
151
154
  print(f'Hello {name}')
152
-
153
155
  - name: "Run Sequence and use var from Above"
154
156
  vars:
155
157
  x: ${{ params.author-run }}
@@ -157,7 +159,6 @@ run_py_local:
157
159
  print(f'Receive x from above with {x}')
158
160
  # Change x value
159
161
  x: int = 1
160
-
161
162
  - name: "Call Function"
162
163
  vars:
163
164
  echo: ${{ stages.define-func.outputs.echo }}
@@ -172,75 +173,34 @@ run_py_local:
172
173
  ```
173
174
 
174
175
  ```python
176
+ from datetime import datetime
175
177
  from ddeutil.workflow.pipeline import Pipeline
176
178
 
177
- pipe = Pipeline.from_loader(name='run_py_local', externals={})
178
- pipe.execute(params={'author-run': 'Local Workflow', 'run-date': '2024-01-01'})
179
+ pipe: Pipeline = Pipeline.from_loader(name='run_py_local', externals={})
180
+ pipe.execute(params={
181
+ 'author-run': 'Local Workflow',
182
+ 'run-date': datetime(2024, 1, 1),
183
+ })
179
184
  ```
180
185
 
181
186
  ```shell
182
- > Hello Local Workflow
187
+ > Hello at 20240101
183
188
  > Receive x from above with Local Workflow
184
189
  > Hello Caller
185
190
  > Hello World from Shell
186
191
  ```
187
192
 
188
- ### Hook (Extract & Load)
189
-
190
- ```yaml
191
- pipe_el_pg_to_lake:
192
- type: pipeline.Pipeline
193
- params:
194
- run-date: datetime
195
- author-email: str
196
- jobs:
197
- extract-load:
198
- stages:
199
- - name: "Extract Load from Postgres to Lake"
200
- id: extract-load
201
- uses: tasks/postgres-to-delta@polars
202
- with:
203
- source:
204
- conn: conn_postgres_url
205
- query: |
206
- select * from ${{ params.name }}
207
- where update_date = '${{ params.datetime }}'
208
- sink:
209
- conn: conn_az_lake
210
- endpoint: "/${{ params.name }}"
211
- ```
212
-
213
- ### Hook (Transform)
214
-
215
- ```yaml
216
- pipeline_hook_mssql_proc:
217
- type: pipeline.Pipeline
218
- params:
219
- run_date: datetime
220
- sp_name: str
221
- source_name: str
222
- target_name: str
223
- jobs:
224
- transform:
225
- stages:
226
- - name: "Transform Data in MS SQL Server"
227
- id: transform
228
- uses: tasks/mssql-proc@odbc
229
- with:
230
- exec: ${{ params.sp_name }}
231
- params:
232
- run_mode: "T"
233
- run_date: ${{ params.run_date }}
234
- source: ${{ params.source_name }}
235
- target: ${{ params.target_name }}
236
- ```
237
-
238
193
  ## Configuration
239
194
 
240
195
  ```bash
241
196
  export WORKFLOW_ROOT_PATH=.
242
197
  export WORKFLOW_CORE_REGISTRY=ddeutil.workflow,tests.utils
198
+ export WORKFLOW_CORE_REGISTRY_FILTER=ddeutil.workflow.utils
243
199
  export WORKFLOW_CORE_PATH_CONF=conf
200
+ export WORKFLOW_CORE_TIMEZONE=Asia/Bangkok
201
+ export WORKFLOW_CORE_DEFAULT_STAGE_ID=true
202
+ export WORKFLOW_CORE_MAX_PIPELINE_POKING=4
203
+ export WORKFLOW_CORE_MAX_JOB_PARALLEL=2
244
204
  ```
245
205
 
246
206
  Application config:
@@ -253,12 +213,21 @@ export WORKFLOW_APP_INTERVAL=10
253
213
  ## Deployment
254
214
 
255
215
  This package able to run as a application service for receive manual trigger
256
- from the master node via RestAPI.
216
+ from the master node via RestAPI or use to be Scheduler background service
217
+ like crontab job but via Python API.
257
218
 
258
- > [!WARNING]
259
- > This feature do not start yet because I still research and find the best tool
260
- > to use it provision an app service, like `starlette`, `fastapi`, `apscheduler`.
219
+ ### Schedule Service
261
220
 
262
221
  ```shell
263
- (venv) $ workflow start -p 7070
222
+ (venv) $ python src.ddeutil.workflow.app
264
223
  ```
224
+
225
+ ### API Server
226
+
227
+ ```shell
228
+ (venv) $ uvicorn src.ddeutil.workflow.api:app --host 0.0.0.0 --port 80 --reload
229
+ ```
230
+
231
+ > [!NOTE]
232
+ > If this package already deploy, it able to use
233
+ > `uvicorn ddeutil.workflow.api:app --host 0.0.0.0 --port 80`
@@ -33,8 +33,12 @@ dynamic = ["version"]
33
33
 
34
34
  [project.optional-dependencies]
35
35
  app = [
36
- "fastapi==0.112.0",
37
- "apscheduler[sqlalchemy]==3.10.4",
36
+ "schedule==1.2.2,<2.0.0",
37
+ ]
38
+ api = [
39
+ "fastapi[standard]==0.112.0",
40
+ "apscheduler[sqlalchemy]==3.10.4,<4.0.0",
41
+ "croniter==3.0.3",
38
42
  ]
39
43
 
40
44
  [project.urls]
@@ -55,8 +59,16 @@ changelog = "CHANGELOG.md"
55
59
  branch = true
56
60
  relative_files = true
57
61
  concurrency = ["thread", "multiprocessing"]
58
- source = ["ddeutil", "tests"]
59
- omit = ["scripts/"]
62
+ source = ["ddeutil.workflow", "tests"]
63
+ omit = [
64
+ "scripts/",
65
+ "src/ddeutil/workflow/api.py",
66
+ "src/ddeutil/workflow/app.py",
67
+ "src/ddeutil/workflow/repeat.py",
68
+ "src/ddeutil/workflow/route.py",
69
+ "tests/utils.py",
70
+ "tests/tasks/dummy.py",
71
+ ]
60
72
 
61
73
  [tool.coverage.report]
62
74
  exclude_lines = ["raise NotImplementedError"]
@@ -71,8 +83,8 @@ addopts = [
71
83
  filterwarnings = ["error"]
72
84
  log_cli = true
73
85
  log_cli_level = "DEBUG"
74
- log_cli_format = "%(asctime)s [%(levelname)7s] %(message)s (%(filename)s:%(lineno)s)"
75
- log_cli_date_format = "%Y-%m-%d %H:%M:%S"
86
+ log_cli_format = "%(asctime)s [%(levelname)-7s] %(message)-100s (%(filename)s:%(lineno)s)"
87
+ log_cli_date_format = "%Y%m%d %H:%M:%S"
76
88
 
77
89
  [tool.black]
78
90
  line-length = 80
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.8"
@@ -0,0 +1,31 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Copyright (c) 2022 Korawich Anuttra. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE in the project root for
4
+ # license information.
5
+ # ------------------------------------------------------------------------------
6
+ from .exceptions import (
7
+ JobException,
8
+ ParamValueException,
9
+ PipelineException,
10
+ StageException,
11
+ UtilException,
12
+ )
13
+ from .on import AwsOn, On
14
+ from .pipeline import Job, Pipeline
15
+ from .stage import (
16
+ BashStage,
17
+ EmptyStage,
18
+ HookStage,
19
+ PyStage,
20
+ Stage,
21
+ TriggerStage,
22
+ )
23
+ from .utils import (
24
+ ChoiceParam,
25
+ DatetimeParam,
26
+ IntParam,
27
+ Param,
28
+ StrParam,
29
+ dash2underscore,
30
+ param2template,
31
+ )
@@ -27,12 +27,21 @@ class Re:
27
27
  """Regular expression config."""
28
28
 
29
29
  # NOTE: Search caller
30
+ # \${{\s*(?P<caller>[a-zA-Z0-9_.\s'\"\[\]\(\)\-\{}]+?)\s*(?P<post_filters>(?:\|\s*(?:[a-zA-Z0-9_]{3,}[a-zA-Z0-9_.,-\\%\s'\"[\]()\{}]+)\s*)*)}}
30
31
  __re_caller: str = r"""
31
32
  \$
32
33
  {{
33
- \s*(?P<caller>
34
+ \s*
35
+ (?P<caller>
34
36
  [a-zA-Z0-9_.\s'\"\[\]\(\)\-\{}]+?
35
37
  )\s*
38
+ (?P<post_filters>
39
+ (?:
40
+ \|\s*
41
+ (?:[a-zA-Z0-9_]{3,}[a-zA-Z0-9_.,-\\%\s'\"[\]()\{}]*)
42
+ \s*
43
+ )*
44
+ )
36
45
  }}
37
46
  """
38
47
  RE_CALLER: Pattern = re.compile(
@@ -40,6 +49,7 @@ class Re:
40
49
  )
41
50
 
42
51
  # NOTE: Search task
52
+ # ^(?P<path>[^/@]+)/(?P<func>[^@]+)@(?P<tag>.+)$
43
53
  __re_task_fmt: str = r"""
44
54
  ^
45
55
  (?P<path>[^/@]+)
@@ -0,0 +1,120 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Copyright (c) 2022 Korawich Anuttra. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE in the project root for
4
+ # license information.
5
+ # ------------------------------------------------------------------------------
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import queue
10
+ import time
11
+ import uuid
12
+ from contextlib import asynccontextmanager
13
+ from datetime import datetime
14
+
15
+ from apscheduler.executors.pool import ProcessPoolExecutor
16
+ from apscheduler.jobstores.memory import MemoryJobStore
17
+ from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
18
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
19
+ from fastapi import BackgroundTasks, FastAPI
20
+ from fastapi.middleware.gzip import GZipMiddleware
21
+ from fastapi.responses import UJSONResponse
22
+ from pydantic import BaseModel
23
+
24
+ from .log import get_logger
25
+ from .repeat import repeat_every
26
+ from .route import schedule_route, workflow_route
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ def broker_upper_messages():
32
+ for _ in range(app.queue_limit):
33
+ try:
34
+ obj = app.queue.get_nowait()
35
+ app.output_dict[obj["request_id"]] = obj["text"].upper()
36
+ logger.info(f"Upper message: {app.output_dict}")
37
+ except queue.Empty:
38
+ pass
39
+
40
+
41
+ jobstores = {
42
+ "default": MemoryJobStore(),
43
+ "sqlite": SQLAlchemyJobStore(url="sqlite:///jobs-store.sqlite"),
44
+ }
45
+ executors = {
46
+ "default": {"type": "threadpool", "max_workers": 5},
47
+ "processpool": ProcessPoolExecutor(max_workers=5),
48
+ }
49
+ scheduler = AsyncIOScheduler(
50
+ jobstores=jobstores,
51
+ executors=executors,
52
+ timezone="Asia/Bangkok",
53
+ )
54
+
55
+
56
+ @asynccontextmanager
57
+ async def lifespan(_: FastAPI):
58
+ scheduler.start()
59
+ yield
60
+ scheduler.shutdown(wait=False)
61
+
62
+
63
+ app = FastAPI(lifespan=lifespan)
64
+ app.add_middleware(GZipMiddleware, minimum_size=1000)
65
+ app.include_router(schedule_route)
66
+ app.include_router(workflow_route)
67
+
68
+ app.scheduler = scheduler
69
+ app.scheduler.add_job(
70
+ broker_upper_messages,
71
+ "interval",
72
+ seconds=10,
73
+ )
74
+ app.queue = queue.Queue()
75
+ app.output_dict = {}
76
+ app.queue_limit = 2
77
+
78
+
79
+ def write_pipeline(task_id: str, message=""):
80
+ logger.info(f"{task_id} : {message}")
81
+ time.sleep(5)
82
+ logger.info(f"{task_id} : run task successfully!!!")
83
+
84
+
85
+ @app.post("/schedule/{name}", response_class=UJSONResponse)
86
+ async def send_schedule(name: str, background_tasks: BackgroundTasks):
87
+ background_tasks.add_task(
88
+ write_pipeline,
89
+ name,
90
+ message=f"some message for {name}",
91
+ )
92
+ await fetch_current_time()
93
+ return {"message": f"Schedule sent {name!r} in the background"}
94
+
95
+
96
+ @repeat_every(seconds=2, max_repetitions=3)
97
+ async def fetch_current_time():
98
+ logger.info(f"Fetch: {datetime.now()}")
99
+
100
+
101
+ class Payload(BaseModel):
102
+ text: str
103
+
104
+
105
+ async def get_result(request_id):
106
+ while 1:
107
+ if request_id in app.output_dict:
108
+ result = app.output_dict[request_id]
109
+ del app.output_dict[request_id]
110
+ return {"message": result}
111
+ await asyncio.sleep(0.001)
112
+
113
+
114
+ @app.post("/upper", response_class=UJSONResponse)
115
+ async def message_upper(payload: Payload):
116
+ request_id: str = str(uuid.uuid4())
117
+ app.queue.put(
118
+ {"text": payload.text, "request_id": request_id},
119
+ )
120
+ return await get_result(request_id)
@@ -0,0 +1,45 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Copyright (c) 2022 Korawich Anuttra. All rights reserved.
3
+ # Licensed under the MIT License. See LICENSE in the project root for
4
+ # license information.
5
+ # ------------------------------------------------------------------------------
6
+ from __future__ import annotations
7
+
8
+ import functools
9
+ import time
10
+
11
+ import schedule
12
+
13
+
14
+ def catch_exceptions(cancel_on_failure=False):
15
+ """Catch exception error from scheduler job."""
16
+
17
+ def catch_exceptions_decorator(job_func):
18
+ @functools.wraps(job_func)
19
+ def wrapper(*args, **kwargs):
20
+ try:
21
+ return job_func(*args, **kwargs)
22
+ except Exception as err:
23
+ print(err)
24
+
25
+ if cancel_on_failure:
26
+ return schedule.CancelJob
27
+
28
+ return wrapper
29
+
30
+ return catch_exceptions_decorator
31
+
32
+
33
+ @catch_exceptions(cancel_on_failure=True)
34
+ def bad_task():
35
+ return 1 / 0
36
+
37
+
38
+ schedule.every(5).seconds.do(bad_task)
39
+
40
+ if __name__ == "__main__":
41
+ while True:
42
+ schedule.run_pending()
43
+ time.sleep(1)
44
+ if not schedule.get_jobs():
45
+ break