pydagu 0.1.0__py3-none-any.whl

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.
pydagu/models/step.py ADDED
@@ -0,0 +1,144 @@
1
+ """Step configuration models"""
2
+
3
+ import re
4
+ from typing import Any, Literal, Self
5
+ from pydantic import BaseModel, Field, model_validator
6
+
7
+ from pydagu.models.base import Precondition
8
+ from pydagu.models.executor import ExecutorConfig
9
+
10
+
11
+ class RetryPolicy(BaseModel):
12
+ """Retry policy for a step"""
13
+
14
+ limit: int = Field(
15
+ ge=0, description="Maximum number of retries", examples=[3, 5, 10]
16
+ )
17
+ intervalSec: int = Field(
18
+ ge=0, description="Interval between retries in seconds", examples=[30, 60, 300]
19
+ )
20
+
21
+
22
+ class ContinueOn(BaseModel):
23
+ """Configuration for continuing execution on specific conditions"""
24
+
25
+ failure: bool | None = Field(None, description="Continue on failure")
26
+ skipped: bool | None = Field(None, description="Continue on skipped")
27
+
28
+
29
+ class ParallelConfig(BaseModel):
30
+ """Configuration for parallel step execution"""
31
+
32
+ items: list[str] = Field(
33
+ description="Items to process in parallel",
34
+ examples=[
35
+ ["customers", "orders", "products"],
36
+ ["file1.csv", "file2.csv", "file3.csv"],
37
+ ],
38
+ )
39
+ maxConcurrent: int | None = Field(
40
+ None, ge=1, description="Maximum concurrent items", examples=[2, 5, 10]
41
+ )
42
+
43
+
44
+ class Step(BaseModel):
45
+ """A step in the DAG"""
46
+
47
+ model_config = {
48
+ "json_schema_extra": {
49
+ "anyOf": [{"required": ["command"]}, {"required": ["script"]}],
50
+ }
51
+ }
52
+
53
+ name: str | None = Field(
54
+ None, description="Step name", examples=["extract-data", "validate-environment"]
55
+ )
56
+ description: str | None = Field(
57
+ None,
58
+ description="Step description",
59
+ examples=["Extract data from source database"],
60
+ )
61
+ command: str | None = Field(
62
+ None,
63
+ description="Command to execute",
64
+ examples=["python extract.py --date=${DATE}", "echo 'Processing...'"],
65
+ )
66
+ script: str | None = Field(
67
+ None,
68
+ description="Script path to execute",
69
+ examples=["./scripts/process.sh", "process.py"],
70
+ )
71
+ depends: str | list[str] | None = Field(
72
+ None,
73
+ description="Dependencies (step names)",
74
+ examples=["validate-environment", ["extract", "transform"]],
75
+ )
76
+ output: str | None = Field(
77
+ None,
78
+ description="Output variable name",
79
+ examples=["RAW_DATA_PATH", "RESULT_COUNT"],
80
+ )
81
+ params: str | list[str] | None = Field(
82
+ None,
83
+ description="Parameters for the step",
84
+ examples=[
85
+ "TYPE=${ITEM} INPUT=${RAW_DATA_PATH}",
86
+ ["--verbose", "--config=prod"],
87
+ ],
88
+ )
89
+ dir: str | None = Field(
90
+ None, description="Working directory", examples=["/data/workspace", "./scripts"]
91
+ )
92
+ executor: ExecutorConfig | None = Field(
93
+ None, description="Custom executor for this step"
94
+ )
95
+ continueOn: ContinueOn | None = Field(
96
+ None, description="Continue execution conditions"
97
+ )
98
+ retryPolicy: RetryPolicy | None = Field(None, description="Retry policy")
99
+ repeatPolicy: dict[str, Any] | None = Field(None, description="Repeat policy")
100
+ mailOnError: bool | None = Field(None, description="Send email on error")
101
+ preconditions: list[Precondition] | None = Field(
102
+ None, description="Step-level preconditions"
103
+ )
104
+ signalOnStop: (
105
+ Literal["SIGTERM", "SIGINT", "SIGKILL", "SIGHUP", "SIGQUIT"] | None
106
+ ) = Field(
107
+ None,
108
+ description="Signal to send on stop",
109
+ examples=["SIGTERM", "SIGKILL", "SIGINT"],
110
+ )
111
+ parallel: ParallelConfig | None = Field(
112
+ None, description="Parallel execution configuration"
113
+ )
114
+
115
+ @model_validator(mode="after")
116
+ def validate_step_has_action(self: Self) -> Self:
117
+ """Validate that step has at least one of: command or script"""
118
+ if not (self.command or self.script):
119
+ raise ValueError(
120
+ "Step must have at least one of: command or script. "
121
+ "Examples: command='echo hello', script='./run.sh'"
122
+ )
123
+ return self
124
+
125
+ @model_validator(mode="after")
126
+ def validate_http_executor_command(self: Self) -> Self:
127
+ """Validate that HTTP executor steps have command in 'METHOD URL' format"""
128
+ if self.executor and self.executor.type == "http" and self.command:
129
+ # HTTP executor requires command in format: "METHOD URL"
130
+ # Valid methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
131
+ # URL can be a literal or a Dagu parameter like ${WEBHOOK_URL}
132
+ http_method_pattern = re.compile(
133
+ r"^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(https?://\S+|\$\{[A-Z_]+\})",
134
+ re.IGNORECASE,
135
+ )
136
+ if not http_method_pattern.match(self.command):
137
+ raise ValueError(
138
+ f"HTTP executor command must be in format 'METHOD URL'. "
139
+ f"Got: '{self.command}'. "
140
+ f"Examples: 'GET https://api.example.com/data', "
141
+ f"'POST https://api.example.com/webhook', "
142
+ f"'POST ${{WEBHOOK_URL}}' (using Dagu parameter)"
143
+ )
144
+ return self
pydagu/models/types.py ADDED
@@ -0,0 +1,16 @@
1
+ from typing import Annotated, TypeAlias
2
+
3
+ from pydantic import BeforeValidator
4
+
5
+
6
+ def _empty_str_to_none(v: str | None) -> None:
7
+ if v is None:
8
+ return None
9
+ if v == "":
10
+ return None
11
+ raise ValueError(
12
+ "Value is not empty"
13
+ ) # Not str or None, Fall to next type. e.g. Decimal, or a non-empty str
14
+
15
+
16
+ EmptyStrToNone: TypeAlias = Annotated[None, BeforeValidator(_empty_str_to_none)]
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydagu
3
+ Version: 0.1.0
4
+ Summary: Python client library for Dagu - A cron alternative with a Web UI
5
+ Project-URL: Homepage, https://github.com/yourusername/pydagu
6
+ Project-URL: Documentation, https://github.com/yourusername/pydagu#readme
7
+ Project-URL: Repository, https://github.com/yourusername/pydagu
8
+ Project-URL: Issues, https://github.com/yourusername/pydagu/issues
9
+ Author-email: Patrick Dobbs <patrick.dobbs@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: cron,dag,dagu,scheduler,workflow
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: System :: Monitoring
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: httpx>=0.28.1
23
+ Requires-Dist: pydantic>=2.12.3
24
+ Requires-Dist: pyyaml>=6.0.3
25
+ Provides-Extra: dev
26
+ Requires-Dist: hypothesis-jsonschema>=0.23.1; extra == 'dev'
27
+ Requires-Dist: import-linter>=2.5.2; extra == 'dev'
28
+ Requires-Dist: pre-commit>=4.3.0; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.4.2; extra == 'dev'
31
+ Requires-Dist: ruff>=0.14.3; extra == 'dev'
32
+ Requires-Dist: schemathesis>=4.3.18; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # pydagu
36
+
37
+ [![PyPI version](https://badge.fury.io/py/pydagu.svg)](https://badge.fury.io/py/pydagu)
38
+ [![Tests](https://github.com/yourusername/pydagu/actions/workflows/test.yml/badge.svg)](https://github.com/yourusername/pydagu/actions/workflows/test.yml)
39
+ [![Python Versions](https://img.shields.io/pypi/pyversions/pydagu.svg)](https://pypi.org/project/pydagu/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
+
42
+ A Python client library for [Dagu](https://github.com/dagu-org/dagu) - providing type-safe DAG creation and HTTP API interaction with Pydantic validation.
43
+
44
+ ## Features
45
+
46
+ - 🎯 **Type-safe**: Built with Pydantic models for full type safety and validation
47
+ - 🏗️ **Builder Pattern**: Fluent API for constructing DAGs and steps
48
+ - 🔌 **HTTP Client**: Complete client for Dagu's HTTP API
49
+ - 🔄 **Webhook Support**: Built-in patterns for webhook integration
50
+ - ✅ **Well-tested**: Comprehensive test suite with 95%+ coverage
51
+ - 📝 **Examples**: Production-ready integration examples
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install pydagu
57
+ ```
58
+
59
+ ## Prerequisites
60
+
61
+ You need a running [Dagu server](https://github.com/dagu-org/dagu) to run the tests. Install Dagu:
62
+
63
+ ```bash
64
+ # macOS
65
+ brew install dagu-org/brew/dagu
66
+
67
+ # Linux
68
+ curl -L https://github.com/dagu-org/dagu/releases/latest/download/dagu_linux_amd64.tar.gz | tar xz
69
+ sudo mv dagu /usr/local/bin/
70
+
71
+ # Start the server
72
+ dagu server
73
+ ```
74
+
75
+ ## Quick Start
76
+
77
+ ### Create and Run a Simple DAG
78
+
79
+ ```python
80
+ from pydagu.builder import DagBuilder, StepBuilder
81
+ from pydagu.http import DaguHttpClient
82
+ from pydagu.models.request import StartDagRun
83
+
84
+ # Initialize client
85
+ client = DaguHttpClient(
86
+ dag_name="my-first-dag",
87
+ url_root="http://localhost:8080/api/v2/"
88
+ )
89
+
90
+ # Build a DAG
91
+ dag = (
92
+ DagBuilder("my-first-dag")
93
+ .description("My first DAG with pydagu")
94
+ .add_step_models(
95
+ StepBuilder("hello-world")
96
+ .command("echo 'Hello from pydagu!'")
97
+ .build()
98
+ )
99
+ .build()
100
+ )
101
+
102
+ # Post the DAG to Dagu
103
+ client.post_dag(dag)
104
+
105
+ # Start a run
106
+ dag_run_id = client.start_dag_run(StartDagRun(dagName=dag.name))
107
+
108
+ # Check status
109
+ status = client.get_dag_run_status(dag_run_id.dagRunId)
110
+ print(f"Status: {status.statusLabel}")
111
+ ```
112
+
113
+ ### HTTP Executor with Retry
114
+
115
+ ```python
116
+ from pydagu.builder import StepBuilder
117
+
118
+ step = (
119
+ StepBuilder("api-call")
120
+ .command("POST https://api.example.com/webhook")
121
+ .http_executor(
122
+ headers={
123
+ "Content-Type": "application/json",
124
+ "Authorization": "Bearer ${API_TOKEN}"
125
+ },
126
+ body={"event": "user.created", "user_id": "123"},
127
+ timeout=30
128
+ )
129
+ .retry(limit=3, interval=5)
130
+ .build()
131
+ )
132
+ ```
133
+
134
+ ### Chained Steps with Dependencies
135
+
136
+ ```python
137
+ from pydagu.builder import DagBuilder, StepBuilder
138
+
139
+ dag = (
140
+ DagBuilder("data-pipeline")
141
+ .add_step_models(
142
+ StepBuilder("extract")
143
+ .command("python extract_data.py")
144
+ .output("EXTRACTED_FILE")
145
+ .build(),
146
+
147
+ StepBuilder("transform")
148
+ .command("python transform_data.py ${EXTRACTED_FILE}")
149
+ .depends_on("extract")
150
+ .output("TRANSFORMED_FILE")
151
+ .build(),
152
+
153
+ StepBuilder("load")
154
+ .command("python load_data.py ${TRANSFORMED_FILE}")
155
+ .depends_on("transform")
156
+ .build()
157
+ )
158
+ .build()
159
+ )
160
+ ```
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ # Clone the repository
166
+ git clone https://github.com/yourusername/pydagu.git
167
+ cd pydagu
168
+
169
+ # Install with dev dependencies
170
+ pip install -e ".[dev]"
171
+
172
+ # Run tests (requires Dagu server running)
173
+ pytest tests/ -v
174
+
175
+ # Run with coverage
176
+ pytest tests/ --cov=pydagu --cov-report=html
177
+ ```
178
+
179
+ ## Documentation
180
+
181
+ - [Dagu Documentation](https://dagu.readthedocs.io/)
182
+ - [Webhook Integration Guide](examples/README.md)
183
+ - [API Reference](https://github.com/yourusername/pydagu/wiki)
184
+
185
+ ## Contributing
186
+
187
+ Contributions are welcome! Please feel free to submit a Pull Request.
188
+
189
+ ## License
190
+
191
+ MIT License - see [LICENSE](LICENSE) file for details.
192
+
193
+ ## Related Projects
194
+
195
+ - [Dagu](https://github.com/dagu-org/dagu) - The underlying DAG execution engine
196
+ - [Airflow](https://airflow.apache.org/) - Alternative workflow orchestration platform
@@ -0,0 +1,18 @@
1
+ pydagu/__init__.py,sha256=kfDenuf6m3o0UNuxUuETSCkRJdoQQacVRp8khYDiKpw,218
2
+ pydagu/builder.py,sha256=_GfMrzaxHxYBKPxGaZsNwooCvqkjgK7pB_6ZZsXB6oU,16486
3
+ pydagu/http.py,sha256=pd25yx39sjHrQnXegWFS-bf_yEdiOVKJiewlpgi-gNw,3101
4
+ pydagu/models/__init__.py,sha256=5QMhVLCjHE_err-9cq6oIUfLpKT8zou3g8jm62Vw9LY,1388
5
+ pydagu/models/base.py,sha256=sPZ0ysADgTDegnTiE2rgsyqezAJLbleGdyJ_dTEYOp8,339
6
+ pydagu/models/dag.py,sha256=QvZBPif8yfzy5wn3TWnuDpqGNcCUuKMqBjKtfF1Zytw,7965
7
+ pydagu/models/executor.py,sha256=8WZd85JXKXQEL8_dyEt8XEWs-7yip4Xn_QRsUPPnPks,5934
8
+ pydagu/models/handlers.py,sha256=oWC4OKGpMzzqTr3lxQiA08BuodDtyLYu8SkgLMaLR4Q,945
9
+ pydagu/models/infrastructure.py,sha256=O9QCX7PwiAwcznQUQ5YNGjbBoyY6qM_RpG6vvB9Ojh4,2147
10
+ pydagu/models/notifications.py,sha256=F58k8FAdvIF0pQalvvk3UVypaDQ3eNUlZ7unM17f6yk,837
11
+ pydagu/models/request.py,sha256=I8b10lN7WfY9onQaGIInGMujalqJwqSTcthrtqexCco,313
12
+ pydagu/models/response.py,sha256=GkYDBKdMDmbd1EiQJVFoyt5UudRXYC_eMwUydSqqdpQ,1596
13
+ pydagu/models/step.py,sha256=jeErnStYPtA_9LByJrsEoPGo6DdmZlp7iq_O0SRkkNQ,5146
14
+ pydagu/models/types.py,sha256=7baIATh_m4FH5OcsMLcG1HKh4nVpv0hcHdoOEg6Vw90,413
15
+ pydagu-0.1.0.dist-info/METADATA,sha256=FUWiOOguZTOBrTWtygo-K3E4MBv1_2aPdMOIlSGCiy4,5611
16
+ pydagu-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ pydagu-0.1.0.dist-info/licenses/LICENSE,sha256=TaXqim4LU9XdIufnAraWqxqVFmiyS2mwbi0K8QJszlA,1081
18
+ pydagu-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Patrick [Your Last Name]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.