dotflow 0.14.1.dev2__tar.gz → 0.15.0.dev2__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 (108) hide show
  1. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/PKG-INFO +17 -1
  2. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/__init__.py +1 -1
  3. dotflow-0.15.0.dev2/dotflow/abc/log.py +122 -0
  4. dotflow-0.15.0.dev2/dotflow/abc/metrics.py +32 -0
  5. dotflow-0.15.0.dev2/dotflow/abc/tracer.py +24 -0
  6. dotflow-0.15.0.dev2/dotflow/cli/commands/__init__.py +18 -0
  7. dotflow-0.15.0.dev2/dotflow/cli/commands/cloud.py +194 -0
  8. dotflow-0.15.0.dev2/dotflow/cli/commands/deploy.py +110 -0
  9. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/setup.py +65 -0
  10. dotflow-0.15.0.dev2/dotflow/cloud/__init__.py +1 -0
  11. dotflow-0.15.0.dev2/dotflow/cloud/aws/__init__.py +15 -0
  12. dotflow-0.15.0.dev2/dotflow/cloud/aws/base_lambda_deployer.py +96 -0
  13. dotflow-0.15.0.dev2/dotflow/cloud/aws/constants.py +9 -0
  14. dotflow-0.15.0.dev2/dotflow/cloud/aws/ecs_deployer.py +108 -0
  15. dotflow-0.15.0.dev2/dotflow/cloud/aws/lambda_api_deployer.py +67 -0
  16. dotflow-0.15.0.dev2/dotflow/cloud/aws/lambda_deployer.py +49 -0
  17. dotflow-0.15.0.dev2/dotflow/cloud/aws/lambda_s3_deployer.py +56 -0
  18. dotflow-0.15.0.dev2/dotflow/cloud/aws/lambda_sqs_deployer.py +43 -0
  19. dotflow-0.15.0.dev2/dotflow/cloud/aws/services/__init__.py +0 -0
  20. dotflow-0.15.0.dev2/dotflow/cloud/aws/services/cloudwatch.py +22 -0
  21. dotflow-0.15.0.dev2/dotflow/cloud/aws/services/ecr.py +72 -0
  22. dotflow-0.15.0.dev2/dotflow/cloud/aws/services/iam.py +79 -0
  23. dotflow-0.15.0.dev2/dotflow/cloud/aws/services/s3.py +47 -0
  24. dotflow-0.15.0.dev2/dotflow/cloud/core.py +89 -0
  25. dotflow-0.15.0.dev2/dotflow/cloud/gcp/__init__.py +5 -0
  26. dotflow-0.15.0.dev2/dotflow/cloud/gcp/cloudrun_deployer.py +111 -0
  27. dotflow-0.15.0.dev2/dotflow/cloud/gcp/constants.py +11 -0
  28. dotflow-0.15.0.dev2/dotflow/cloud/gcp/services/__init__.py +1 -0
  29. dotflow-0.15.0.dev2/dotflow/cloud/gcp/services/apis.py +37 -0
  30. dotflow-0.15.0.dev2/dotflow/cloud/gcp/services/artifact_registry.py +127 -0
  31. dotflow-0.15.0.dev2/dotflow/cloud/gcp/services/gcs.py +45 -0
  32. dotflow-0.15.0.dev2/dotflow/cloud/github/__init__.py +5 -0
  33. dotflow-0.15.0.dev2/dotflow/cloud/github/actions_deployer.py +113 -0
  34. dotflow-0.15.0.dev2/dotflow/cloud/github/constants.py +8 -0
  35. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/config.py +14 -0
  36. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/dotflow.py +4 -1
  37. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/execution.py +3 -1
  38. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/task.py +12 -3
  39. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/workflow.py +33 -1
  40. dotflow-0.15.0.dev2/dotflow/logging.py +14 -0
  41. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/__init__.py +36 -0
  42. dotflow-0.15.0.dev2/dotflow/providers/log_default.py +62 -0
  43. dotflow-0.15.0.dev2/dotflow/providers/log_opentelemetry.py +91 -0
  44. dotflow-0.15.0.dev2/dotflow/providers/log_sentry.py +96 -0
  45. dotflow-0.15.0.dev2/dotflow/providers/metrics_default.py +25 -0
  46. dotflow-0.15.0.dev2/dotflow/providers/metrics_opentelemetry.py +95 -0
  47. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/storage_gcs.py +4 -32
  48. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/storage_s3.py +4 -34
  49. dotflow-0.15.0.dev2/dotflow/providers/tracer_default.py +19 -0
  50. dotflow-0.15.0.dev2/dotflow/providers/tracer_opentelemetry.py +126 -0
  51. dotflow-0.15.0.dev2/dotflow/providers/tracer_sentry.py +126 -0
  52. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/settings.py +9 -2
  53. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/pyproject.toml +7 -2
  54. dotflow-0.14.1.dev2/dotflow/abc/log.py +0 -16
  55. dotflow-0.14.1.dev2/dotflow/cli/commands/__init__.py +0 -8
  56. dotflow-0.14.1.dev2/dotflow/logging.py +0 -27
  57. dotflow-0.14.1.dev2/dotflow/providers/log_default.py +0 -25
  58. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/LICENSE +0 -0
  59. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/README.md +0 -0
  60. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/__init__.py +0 -0
  61. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/api.py +0 -0
  62. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/file.py +0 -0
  63. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/flow.py +0 -0
  64. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/http.py +0 -0
  65. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/notify.py +0 -0
  66. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/scheduler.py +0 -0
  67. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/storage.py +0 -0
  68. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/abc/tcp.py +0 -0
  69. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/__init__.py +0 -0
  70. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/command.py +0 -0
  71. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/commands/init.py +0 -0
  72. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/commands/log.py +0 -0
  73. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/commands/schedule.py +0 -0
  74. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/commands/start.py +0 -0
  75. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/validators/__init__.py +0 -0
  76. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/cli/validators/start.py +0 -0
  77. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/__init__.py +0 -0
  78. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/action.py +0 -0
  79. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/context.py +0 -0
  80. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/decorators/__init__.py +0 -0
  81. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/decorators/time.py +0 -0
  82. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/exception.py +0 -0
  83. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/module.py +0 -0
  84. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/serializers/__init__.py +0 -0
  85. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/serializers/task.py +0 -0
  86. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/serializers/transport.py +0 -0
  87. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/serializers/workflow.py +0 -0
  88. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/__init__.py +0 -0
  89. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/execution.py +0 -0
  90. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/overlap.py +0 -0
  91. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/status.py +0 -0
  92. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/storage.py +0 -0
  93. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/core/types/workflow.py +0 -0
  94. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/main.py +0 -0
  95. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/api_default.py +0 -0
  96. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/notify_default.py +0 -0
  97. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/notify_discord.py +0 -0
  98. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/notify_telegram.py +0 -0
  99. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/scheduler_cron.py +0 -0
  100. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/scheduler_default.py +0 -0
  101. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/storage_default.py +0 -0
  102. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/providers/storage_file.py +0 -0
  103. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/storage.py +0 -0
  104. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/types.py +0 -0
  105. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/utils/__init__.py +0 -0
  106. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/utils/basic_functions.py +0 -0
  107. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/utils/error_handler.py +0 -0
  108. {dotflow-0.14.1.dev2 → dotflow-0.15.0.dev2}/dotflow/utils/tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotflow
3
- Version: 0.14.1.dev2
3
+ Version: 0.15.0.dev2
4
4
  Summary: 🎲 Dotflow turns an idea into flow! Lightweight Python library for execution pipelines with retry, parallel, cron and async support.
5
5
  License: MIT License
6
6
 
@@ -42,15 +42,31 @@ Classifier: Programming Language :: Python :: 3.11
42
42
  Classifier: Programming Language :: Python :: 3.12
43
43
  Classifier: Programming Language :: Python :: 3.13
44
44
  Provides-Extra: aws
45
+ Provides-Extra: deploy-aws
46
+ Provides-Extra: deploy-gcp
47
+ Provides-Extra: deploy-github
45
48
  Provides-Extra: gcp
49
+ Provides-Extra: otel
46
50
  Provides-Extra: scheduler
51
+ Provides-Extra: sentry
52
+ Requires-Dist: PyGithub ; extra == "deploy-github"
47
53
  Requires-Dist: boto3 ; extra == "aws"
54
+ Requires-Dist: boto3 ; extra == "deploy-aws"
48
55
  Requires-Dist: cookiecutter (>=2.0)
49
56
  Requires-Dist: croniter ; extra == "scheduler"
57
+ Requires-Dist: google-auth ; extra == "deploy-gcp"
58
+ Requires-Dist: google-cloud-artifact-registry ; extra == "deploy-gcp"
59
+ Requires-Dist: google-cloud-build ; extra == "deploy-gcp"
60
+ Requires-Dist: google-cloud-run ; extra == "deploy-gcp"
61
+ Requires-Dist: google-cloud-service-usage ; extra == "deploy-gcp"
62
+ Requires-Dist: google-cloud-storage ; extra == "deploy-gcp"
50
63
  Requires-Dist: google-cloud-storage ; extra == "gcp"
64
+ Requires-Dist: opentelemetry-api ; extra == "otel"
65
+ Requires-Dist: opentelemetry-sdk ; extra == "otel"
51
66
  Requires-Dist: pydantic
52
67
  Requires-Dist: requests
53
68
  Requires-Dist: rich
69
+ Requires-Dist: sentry-sdk ; extra == "sentry"
54
70
  Requires-Dist: typing-extensions
55
71
  Project-URL: Changelog, https://dotflow-io.github.io/dotflow/nav/development/release-notes/
56
72
  Project-URL: Documentation, https://dotflow-io.github.io/dotflow/
@@ -1,6 +1,6 @@
1
1
  """Dotflow __init__ module."""
2
2
 
3
- __version__ = "0.14.1.dev2"
3
+ __version__ = "0.15.0.dev2"
4
4
  __description__ = "🎲 Dotflow turns an idea into flow!"
5
5
 
6
6
  from .core.action import Action as action
@@ -0,0 +1,122 @@
1
+ """Log ABC"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from abc import ABC
7
+ from datetime import datetime, timezone
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ LEVELS = {
13
+ "DEBUG": logging.DEBUG,
14
+ "INFO": logging.INFO,
15
+ "WARNING": logging.WARNING,
16
+ "ERROR": logging.ERROR,
17
+ }
18
+
19
+
20
+ class LogRecordError(BaseModel):
21
+ exception: str = Field(default="")
22
+ message: str = Field(default="")
23
+
24
+
25
+ class LogRecord(BaseModel):
26
+ timestamp: str = Field(default="")
27
+ level: str = Field(default="")
28
+ workflow_id: str = Field(default="")
29
+ task_id: str = Field(default="")
30
+ status: str = Field(default="")
31
+ duration: Optional[float] = Field(default=None)
32
+ retry_count: Optional[int] = Field(default=None)
33
+ error: Optional[LogRecordError] = Field(default=None)
34
+
35
+
36
+ class Log(ABC):
37
+ """Log
38
+
39
+ Base class for all log providers. Subclasses must set
40
+ ``self._logger``, ``self._level`` and ``self._format``
41
+ in their ``__init__``.
42
+ """
43
+
44
+ _logger: logging.Logger
45
+ _level: int = logging.INFO
46
+ _format: str = "simple"
47
+
48
+ def info(self, **kwargs) -> None:
49
+ """Info"""
50
+ self._log(logging.INFO, **kwargs)
51
+
52
+ def error(self, **kwargs) -> None:
53
+ """Error"""
54
+ self._log(logging.ERROR, **kwargs)
55
+
56
+ def warning(self, **kwargs) -> None:
57
+ """Warning"""
58
+ self._log(logging.WARNING, **kwargs)
59
+
60
+ def debug(self, **kwargs) -> None:
61
+ """Debug"""
62
+ self._log(logging.DEBUG, **kwargs)
63
+
64
+ _FORMATTERS = {
65
+ "json": "_format_json",
66
+ "simple": "_format_text",
67
+ }
68
+
69
+ def _log(self, level: int, **kwargs) -> None:
70
+ if level < self._level:
71
+ return
72
+
73
+ formatter = getattr(
74
+ self, self._FORMATTERS.get(self._format, "_format_text")
75
+ )
76
+ task = kwargs.get("task")
77
+
78
+ if task:
79
+ message = formatter(level, task=task)
80
+ else:
81
+ message = formatter(level, **kwargs)
82
+
83
+ self._logger.log(level, message)
84
+
85
+ def _format_json(self, level: int, **kwargs) -> str:
86
+ task = kwargs.get("task")
87
+ if task:
88
+ record = LogRecord(
89
+ timestamp=datetime.now(timezone.utc).isoformat(),
90
+ level=logging.getLevelName(level),
91
+ workflow_id=str(task.workflow_id),
92
+ task_id=str(task.task_id),
93
+ status=str(task.status),
94
+ )
95
+
96
+ if task.duration is not None:
97
+ record.duration = task.duration
98
+
99
+ if task.retry_count:
100
+ record.retry_count = task.retry_count
101
+
102
+ if task.errors and level >= logging.ERROR:
103
+ last_error = task.errors[-1]
104
+ record.error = LogRecordError(
105
+ exception=str(last_error.exception),
106
+ message=str(last_error.message),
107
+ )
108
+
109
+ return record.model_dump_json(exclude_none=True)
110
+
111
+ record = LogRecord(**{k: str(v) for k, v in kwargs.items()})
112
+ return record.model_dump_json(exclude_none=True)
113
+
114
+ def _format_text(self, level: int, **kwargs) -> str:
115
+ task = kwargs.get("task")
116
+ if task:
117
+ message = f"ID {task.workflow_id} - {task.task_id} - {task.status}"
118
+ if task.errors and level >= logging.ERROR:
119
+ message += f" \n {task.errors[-1].traceback}"
120
+ return message
121
+
122
+ return str(kwargs)
@@ -0,0 +1,32 @@
1
+ """Metrics ABC"""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+
7
+ class Metrics(ABC):
8
+ """Metrics"""
9
+
10
+ @abstractmethod
11
+ def workflow_started(self, workflow_id: Any, **kwargs) -> None:
12
+ """Called when a workflow starts."""
13
+
14
+ @abstractmethod
15
+ def workflow_completed(self, workflow_id: Any, duration: float) -> None:
16
+ """Called when a workflow completes successfully."""
17
+
18
+ @abstractmethod
19
+ def workflow_failed(self, workflow_id: Any, duration: float) -> None:
20
+ """Called when a workflow fails."""
21
+
22
+ @abstractmethod
23
+ def task_completed(self, task: Any) -> None:
24
+ """Called when a task completes successfully."""
25
+
26
+ @abstractmethod
27
+ def task_failed(self, task: Any) -> None:
28
+ """Called when a task fails."""
29
+
30
+ @abstractmethod
31
+ def task_retried(self, task: Any) -> None:
32
+ """Called when a task is retried."""
@@ -0,0 +1,24 @@
1
+ """Tracer ABC"""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+
7
+ class Tracer(ABC):
8
+ """Tracer"""
9
+
10
+ @abstractmethod
11
+ def start_workflow(self, workflow_id: Any, **kwargs) -> None:
12
+ """Called when a workflow starts execution."""
13
+
14
+ @abstractmethod
15
+ def end_workflow(self, workflow_id: Any, **kwargs) -> None:
16
+ """Called when a workflow finishes execution."""
17
+
18
+ @abstractmethod
19
+ def start_task(self, task: Any) -> None:
20
+ """Called when a task starts execution."""
21
+
22
+ @abstractmethod
23
+ def end_task(self, task: Any) -> None:
24
+ """Called when a task finishes execution."""
@@ -0,0 +1,18 @@
1
+ """Commands __init__ module."""
2
+
3
+ from dotflow.cli.commands.cloud import CloudGenerateCommand, CloudListCommand
4
+ from dotflow.cli.commands.deploy import DeployCommand
5
+ from dotflow.cli.commands.init import InitCommand
6
+ from dotflow.cli.commands.log import LogCommand
7
+ from dotflow.cli.commands.schedule import ScheduleCommand
8
+ from dotflow.cli.commands.start import StartCommand
9
+
10
+ __all__ = [
11
+ "CloudGenerateCommand",
12
+ "CloudListCommand",
13
+ "DeployCommand",
14
+ "InitCommand",
15
+ "LogCommand",
16
+ "ScheduleCommand",
17
+ "StartCommand",
18
+ ]
@@ -0,0 +1,194 @@
1
+ """Command cloud module"""
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+ from subprocess import run
7
+
8
+ from rich import print # type: ignore
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+
12
+ from dotflow.cli.command import Command
13
+ from dotflow.settings import Settings as settings
14
+
15
+
16
+ def _get_template_dir() -> Path | None:
17
+ import tempfile
18
+
19
+ target = Path(tempfile.gettempdir()) / "dotflow-template"
20
+ cloud_dir = target / settings.TEMPLATE_CLOUD_DIR
21
+
22
+ if cloud_dir.exists():
23
+ return cloud_dir
24
+
25
+ try:
26
+ result = run(
27
+ [
28
+ "git",
29
+ "clone",
30
+ "--depth",
31
+ "1",
32
+ "--branch",
33
+ settings.TEMPLATE_BRANCH,
34
+ settings.TEMPLATE_REPO,
35
+ str(target),
36
+ ],
37
+ capture_output=True,
38
+ text=True,
39
+ )
40
+ if result.returncode == 0:
41
+ return cloud_dir
42
+ except Exception as err:
43
+ print(
44
+ settings.ERROR_ALERT,
45
+ f"Failed to clone template repository: {err}",
46
+ )
47
+
48
+ return None
49
+
50
+
51
+ def _load_registry(cloud_dir: Path) -> dict | None:
52
+ registry_path = cloud_dir / "registry.json"
53
+ if not registry_path.exists():
54
+ print(settings.ERROR_ALERT, "Registry not found in template.")
55
+ return None
56
+
57
+ try:
58
+ return json.loads(registry_path.read_text())
59
+ except Exception as err:
60
+ print(settings.ERROR_ALERT, f"Failed to read registry: {err}")
61
+ return None
62
+
63
+
64
+ def _get_platforms(registry: dict) -> dict | None:
65
+ platforms = registry.get("platforms")
66
+ if not isinstance(platforms, dict):
67
+ print(settings.ERROR_ALERT, "Unexpected registry format.")
68
+ return None
69
+
70
+ return platforms
71
+
72
+
73
+ def _read_project_name(pyproject: Path) -> str | None:
74
+ try:
75
+ in_project = False
76
+ for line in pyproject.read_text().splitlines():
77
+ stripped = line.strip()
78
+ if stripped.startswith("["):
79
+ in_project = stripped in ("[project]", "[tool.poetry]")
80
+ if in_project and re.match(r"^name\s*=", stripped):
81
+ return stripped.split("=", 1)[1].strip().strip('"').strip("'")
82
+ except Exception:
83
+ return None
84
+ return None
85
+
86
+
87
+ class CloudGenerateCommand(Command):
88
+ def setup(self):
89
+ platform = self.params.platform
90
+
91
+ cloud_dir = _get_template_dir()
92
+ if cloud_dir is None:
93
+ return
94
+
95
+ registry = _load_registry(cloud_dir)
96
+ if registry is None:
97
+ return
98
+
99
+ platforms = _get_platforms(registry)
100
+ if platforms is None:
101
+ return
102
+
103
+ if platform not in platforms:
104
+ available = ", ".join(sorted(platforms.keys()))
105
+ print(
106
+ settings.ERROR_ALERT,
107
+ f"Unknown platform '{platform}'. Available: {available}",
108
+ )
109
+ return
110
+
111
+ platform_info = platforms[platform]
112
+ files = platform_info.get("files", [])
113
+ output = Path(self.params.output).resolve()
114
+ output.mkdir(parents=True, exist_ok=True)
115
+
116
+ project_name = self.params.project
117
+ if not project_name:
118
+ pyproject = Path.cwd() / "pyproject.toml"
119
+ if pyproject.exists():
120
+ project_name = _read_project_name(pyproject)
121
+ if not project_name:
122
+ project_name = Path.cwd().name
123
+
124
+ module_name = project_name.replace("-", "_")
125
+
126
+ print(
127
+ settings.INFO_ALERT,
128
+ f"Generating {platform_info.get('name', platform)} files"
129
+ f" for '{project_name}'...",
130
+ )
131
+
132
+ source_dir = cloud_dir / platform
133
+
134
+ for filename in files:
135
+ filepath = (output / filename).resolve()
136
+ if not str(filepath).startswith(str(output)):
137
+ print(
138
+ settings.ERROR_ALERT,
139
+ f" Skipped (unsafe filename): {filename}",
140
+ )
141
+ continue
142
+
143
+ if filepath.exists():
144
+ print(
145
+ settings.WARNING_ALERT,
146
+ f" Skipped (already exists): {filepath}",
147
+ )
148
+ continue
149
+
150
+ source = source_dir / filename
151
+ if not source.exists():
152
+ print(
153
+ settings.ERROR_ALERT,
154
+ f" Skipped (template not found): {filename}",
155
+ )
156
+ continue
157
+
158
+ content = source.read_text()
159
+ content = content.replace("{{PROJECT_NAME}}", project_name)
160
+ content = content.replace("{{MODULE_NAME}}", module_name)
161
+
162
+ filepath.write_text(content)
163
+ print(f" Created: {filepath}")
164
+
165
+ print(settings.INFO_ALERT, "Done.")
166
+
167
+
168
+ class CloudListCommand(Command):
169
+ def setup(self):
170
+ cloud_dir = _get_template_dir()
171
+ if cloud_dir is None:
172
+ return
173
+
174
+ registry = _load_registry(cloud_dir)
175
+ if registry is None:
176
+ return
177
+
178
+ platforms = _get_platforms(registry)
179
+ if platforms is None:
180
+ return
181
+
182
+ table = Table(title="Available Platforms")
183
+ table.add_column("Platform", style="bold cyan")
184
+ table.add_column("Name", style="bold")
185
+ table.add_column("Description")
186
+
187
+ for key, info in platforms.items():
188
+ table.add_row(
189
+ key,
190
+ info.get("name", ""),
191
+ info.get("description", ""),
192
+ )
193
+
194
+ Console().print(table)
@@ -0,0 +1,110 @@
1
+ """Command deploy module."""
2
+
3
+ from rich import print # type: ignore
4
+
5
+ from dotflow.cli.command import Command
6
+ from dotflow.settings import Settings as settings
7
+
8
+ SAM_PLATFORMS = {
9
+ "ecs-scheduled",
10
+ }
11
+
12
+ DEFAULT_REGIONS = {
13
+ "lambda": "us-east-1",
14
+ "lambda-scheduled": "us-east-1",
15
+ "lambda-s3-trigger": "us-east-1",
16
+ "lambda-sqs-trigger": "us-east-1",
17
+ "lambda-api-trigger": "us-east-1",
18
+ "ecs": "us-east-1",
19
+ "cloud-run": "us-central1",
20
+ "cloud-run-scheduled": "us-central1",
21
+ }
22
+
23
+
24
+ class DeployCommand(Command):
25
+ def setup(self):
26
+ platform = self.params.platform
27
+ name = self.params.project
28
+ region = self.params.region or DEFAULT_REGIONS.get(
29
+ platform, "us-east-1"
30
+ )
31
+ schedule = getattr(self.params, "schedule", None)
32
+
33
+ if platform in SAM_PLATFORMS:
34
+ print(
35
+ settings.INFO_ALERT,
36
+ f"'{platform}' requires SAM for full trigger setup.",
37
+ )
38
+ print(" Run: sam build && sam deploy")
39
+ return
40
+
41
+ deployers = {
42
+ "lambda": self._deploy_lambda,
43
+ "lambda-scheduled": self._deploy_lambda,
44
+ "lambda-s3-trigger": self._deploy_lambda_s3,
45
+ "lambda-sqs-trigger": self._deploy_lambda_sqs,
46
+ "lambda-api-trigger": self._deploy_lambda_api,
47
+ "ecs": self._deploy_ecs,
48
+ "cloud-run": self._deploy_cloud_run,
49
+ "cloud-run-scheduled": self._deploy_cloud_run,
50
+ "github-actions": self._deploy_github_actions,
51
+ }
52
+
53
+ handler = deployers.get(platform)
54
+ if not handler:
55
+ print(
56
+ settings.ERROR_ALERT,
57
+ f"Deploy not supported for '{platform}'.",
58
+ )
59
+ return
60
+
61
+ handler(name=name, region=region, schedule=schedule)
62
+
63
+ def _deploy_lambda(self, name: str, region: str, schedule: str = None):
64
+ """Deploy to AWS Lambda."""
65
+ from dotflow.cloud.aws import LambdaDeployer
66
+
67
+ deployer = LambdaDeployer(region=region)
68
+ deployer.deploy(name, schedule=schedule)
69
+
70
+ def _deploy_lambda_api(self, name: str, region: str, **kwargs):
71
+ """Deploy to AWS Lambda + API Gateway."""
72
+ from dotflow.cloud.aws import LambdaApiDeployer
73
+
74
+ deployer = LambdaApiDeployer(region=region)
75
+ deployer.deploy(name)
76
+
77
+ def _deploy_lambda_s3(self, name: str, region: str, **kwargs):
78
+ """Deploy to AWS Lambda + S3 Trigger."""
79
+ from dotflow.cloud.aws import LambdaS3Deployer
80
+
81
+ deployer = LambdaS3Deployer(region=region)
82
+ deployer.deploy(name)
83
+
84
+ def _deploy_lambda_sqs(self, name: str, region: str, **kwargs):
85
+ """Deploy to AWS Lambda + SQS Trigger."""
86
+ from dotflow.cloud.aws import LambdaSQSDeployer
87
+
88
+ deployer = LambdaSQSDeployer(region=region)
89
+ deployer.deploy(name)
90
+
91
+ def _deploy_ecs(self, name: str, region: str, **kwargs):
92
+ """Deploy to AWS ECS Fargate."""
93
+ from dotflow.cloud.aws import ECSDeployer
94
+
95
+ deployer = ECSDeployer(region=region)
96
+ deployer.deploy(name)
97
+
98
+ def _deploy_cloud_run(self, name: str, region: str, **kwargs):
99
+ """Deploy to Google Cloud Run."""
100
+ from dotflow.cloud.gcp import CloudRunDeployer
101
+
102
+ deployer = CloudRunDeployer(region=region)
103
+ deployer.deploy(name)
104
+
105
+ def _deploy_github_actions(self, name: str, **kwargs):
106
+ """Deploy to GitHub Actions."""
107
+ from dotflow.cloud.github import ActionsDeployer
108
+
109
+ deployer = ActionsDeployer()
110
+ deployer.deploy(name)
@@ -4,6 +4,9 @@ from rich import print # type: ignore
4
4
 
5
5
  from dotflow import __description__, __version__
6
6
  from dotflow.cli.commands import (
7
+ CloudGenerateCommand,
8
+ CloudListCommand,
9
+ DeployCommand,
7
10
  InitCommand,
8
11
  LogCommand,
9
12
  ScheduleCommand,
@@ -39,6 +42,8 @@ class Command:
39
42
  self.setup_logs()
40
43
  self.setup_start()
41
44
  self.setup_schedule()
45
+ self.setup_cloud()
46
+ self.setup_deploy()
42
47
  self.command()
43
48
 
44
49
  def setup_init(self):
@@ -145,6 +150,66 @@ class Command:
145
150
 
146
151
  self.cmd_schedule.set_defaults(exec=ScheduleCommand)
147
152
 
153
+ def setup_cloud(self):
154
+ self.cmd_cloud = self.subparsers.add_parser(
155
+ "cloud",
156
+ help="Generate cloud infrastructure files for a target platform",
157
+ )
158
+ cloud_subparsers = self.cmd_cloud.add_subparsers()
159
+
160
+ cmd_generate = cloud_subparsers.add_parser(
161
+ "generate", help="Generate infrastructure files"
162
+ )
163
+ cmd_generate.add_argument(
164
+ "--platform",
165
+ required=True,
166
+ help="Target platform (e.g. docker, lambda, cloud-run, ecs, kubernetes)",
167
+ )
168
+ cmd_generate.add_argument(
169
+ "--project",
170
+ default=None,
171
+ help="Project name (defaults to current directory name)",
172
+ )
173
+ cmd_generate.add_argument(
174
+ "--output",
175
+ default=".",
176
+ help="Output directory (defaults to current directory)",
177
+ )
178
+ cmd_generate.set_defaults(exec=CloudGenerateCommand)
179
+
180
+ cmd_list = cloud_subparsers.add_parser(
181
+ "list", help="List available cloud platforms"
182
+ )
183
+ cmd_list.set_defaults(exec=CloudListCommand)
184
+
185
+ def setup_deploy(self):
186
+ self.cmd_deploy = self.subparsers.add_parser(
187
+ "deploy",
188
+ help="Deploy pipeline to a cloud platform",
189
+ )
190
+ self.cmd_deploy.add_argument(
191
+ "--platform",
192
+ required=True,
193
+ help="Target platform (e.g. lambda, ecs)",
194
+ )
195
+ self.cmd_deploy.add_argument(
196
+ "--project",
197
+ default=None,
198
+ required=True,
199
+ help="Project name",
200
+ )
201
+ self.cmd_deploy.add_argument(
202
+ "--region",
203
+ default=None,
204
+ help="Cloud region (e.g. us-east-1 for AWS, us-central1 for GCP)",
205
+ )
206
+ self.cmd_deploy.add_argument(
207
+ "--schedule",
208
+ default=None,
209
+ help="Schedule expression (e.g. 'rate(6 hours)')",
210
+ )
211
+ self.cmd_deploy.set_defaults(exec=DeployCommand)
212
+
148
213
  def setup_logs(self):
149
214
  self.cmd_logs = self.subparsers.add_parser("logs", help="Logs")
150
215
  self.cmd_logs = self.cmd_logs.add_argument_group(
@@ -0,0 +1 @@
1
+ """Deploy module."""
@@ -0,0 +1,15 @@
1
+ """AWS deploy module."""
2
+
3
+ from dotflow.cloud.aws.ecs_deployer import ECSDeployer
4
+ from dotflow.cloud.aws.lambda_api_deployer import LambdaApiDeployer
5
+ from dotflow.cloud.aws.lambda_deployer import LambdaDeployer
6
+ from dotflow.cloud.aws.lambda_s3_deployer import LambdaS3Deployer
7
+ from dotflow.cloud.aws.lambda_sqs_deployer import LambdaSQSDeployer
8
+
9
+ __all__ = [
10
+ "LambdaDeployer",
11
+ "LambdaApiDeployer",
12
+ "LambdaS3Deployer",
13
+ "LambdaSQSDeployer",
14
+ "ECSDeployer",
15
+ ]