dotflow 0.14.1.dev1__tar.gz → 0.14.1.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 (69) hide show
  1. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/PKG-INFO +2 -2
  2. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/README.md +1 -1
  3. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/__init__.py +1 -1
  4. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/notify.py +2 -2
  5. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/task.py +1 -1
  6. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/__init__.py +2 -0
  7. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/notify_default.py +1 -1
  8. dotflow-0.14.1.dev2/dotflow/providers/notify_discord.py +112 -0
  9. dotflow-0.14.1.dev2/dotflow/providers/notify_telegram.py +109 -0
  10. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/pyproject.toml +2 -2
  11. dotflow-0.14.1.dev1/dotflow/providers/notify_telegram.py +0 -59
  12. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/LICENSE +0 -0
  13. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/__init__.py +0 -0
  14. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/api.py +0 -0
  15. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/file.py +0 -0
  16. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/flow.py +0 -0
  17. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/http.py +0 -0
  18. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/log.py +0 -0
  19. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/scheduler.py +0 -0
  20. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/storage.py +0 -0
  21. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/abc/tcp.py +0 -0
  22. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/__init__.py +0 -0
  23. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/command.py +0 -0
  24. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/commands/__init__.py +0 -0
  25. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/commands/init.py +0 -0
  26. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/commands/log.py +0 -0
  27. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/commands/schedule.py +0 -0
  28. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/commands/start.py +0 -0
  29. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/setup.py +0 -0
  30. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/validators/__init__.py +0 -0
  31. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/cli/validators/start.py +0 -0
  32. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/__init__.py +0 -0
  33. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/action.py +0 -0
  34. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/config.py +0 -0
  35. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/context.py +0 -0
  36. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/decorators/__init__.py +0 -0
  37. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/decorators/time.py +0 -0
  38. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/dotflow.py +0 -0
  39. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/exception.py +0 -0
  40. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/execution.py +0 -0
  41. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/module.py +0 -0
  42. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/serializers/__init__.py +0 -0
  43. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/serializers/task.py +0 -0
  44. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/serializers/transport.py +0 -0
  45. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/serializers/workflow.py +0 -0
  46. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/__init__.py +0 -0
  47. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/execution.py +0 -0
  48. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/overlap.py +0 -0
  49. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/status.py +0 -0
  50. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/storage.py +0 -0
  51. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/types/workflow.py +0 -0
  52. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/core/workflow.py +0 -0
  53. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/logging.py +0 -0
  54. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/main.py +0 -0
  55. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/api_default.py +0 -0
  56. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/log_default.py +0 -0
  57. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/scheduler_cron.py +0 -0
  58. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/scheduler_default.py +0 -0
  59. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/storage_default.py +0 -0
  60. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/storage_file.py +0 -0
  61. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/storage_gcs.py +0 -0
  62. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/providers/storage_s3.py +0 -0
  63. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/settings.py +0 -0
  64. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/storage.py +0 -0
  65. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/types.py +0 -0
  66. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/utils/__init__.py +0 -0
  67. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/utils/basic_functions.py +0 -0
  68. {dotflow-0.14.1.dev1 → dotflow-0.14.1.dev2}/dotflow/utils/error_handler.py +0 -0
  69. {dotflow-0.14.1.dev1 → dotflow-0.14.1.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.dev1
3
+ Version: 0.14.1.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
 
@@ -648,7 +648,7 @@ Extend Dotflow by implementing the abstract base classes:
648
648
  | ABC | Methods | Purpose |
649
649
  |-----|---------|---------|
650
650
  | `Storage` | `post`, `get`, `key` | Custom storage backends |
651
- | `Notify` | `send` | Custom notification channels |
651
+ | `Notify` | `hook_status_task` | Custom notification channels |
652
652
  | `Log` | `info`, `error` | Custom logging |
653
653
  | `Scheduler` | `start`, `stop` | Custom scheduling strategies |
654
654
 
@@ -587,7 +587,7 @@ Extend Dotflow by implementing the abstract base classes:
587
587
  | ABC | Methods | Purpose |
588
588
  |-----|---------|---------|
589
589
  | `Storage` | `post`, `get`, `key` | Custom storage backends |
590
- | `Notify` | `send` | Custom notification channels |
590
+ | `Notify` | `hook_status_task` | Custom notification channels |
591
591
  | `Log` | `info`, `error` | Custom logging |
592
592
  | `Scheduler` | `start`, `stop` | Custom scheduling strategies |
593
593
 
@@ -1,6 +1,6 @@
1
1
  """Dotflow __init__ module."""
2
2
 
3
- __version__ = "0.14.1.dev1"
3
+ __version__ = "0.14.1.dev2"
4
4
  __description__ = "🎲 Dotflow turns an idea into flow!"
5
5
 
6
6
  from .core.action import Action as action
@@ -8,5 +8,5 @@ class Notify(ABC):
8
8
  """Notify"""
9
9
 
10
10
  @abstractmethod
11
- def send(self, task: Any) -> None:
12
- """Send"""
11
+ def hook_status_task(self, task: Any) -> None:
12
+ """Hook called when a task status changes."""
@@ -248,7 +248,7 @@ class Task(TaskInstance):
248
248
  def status(self, value: TypeStatus) -> None:
249
249
  self._status = value
250
250
 
251
- self.config.notify.send(task=self)
251
+ self.config.notify.hook_status_task(task=self)
252
252
  self.config.log.info(task=self)
253
253
 
254
254
  @property
@@ -2,6 +2,7 @@
2
2
 
3
3
  from dotflow.providers.log_default import LogDefault
4
4
  from dotflow.providers.notify_default import NotifyDefault
5
+ from dotflow.providers.notify_discord import NotifyDiscord
5
6
  from dotflow.providers.notify_telegram import NotifyTelegram
6
7
  from dotflow.providers.scheduler_default import SchedulerDefault
7
8
  from dotflow.providers.storage_default import StorageDefault
@@ -10,6 +11,7 @@ from dotflow.providers.storage_file import StorageFile
10
11
  __all__ = [
11
12
  "LogDefault",
12
13
  "NotifyDefault",
14
+ "NotifyDiscord",
13
15
  "NotifyTelegram",
14
16
  "SchedulerCron",
15
17
  "SchedulerDefault",
@@ -6,5 +6,5 @@ from dotflow.abc.notify import Notify
6
6
 
7
7
 
8
8
  class NotifyDefault(Notify):
9
- def send(self, task: Any) -> None:
9
+ def hook_status_task(self, task: Any) -> None:
10
10
  pass
@@ -0,0 +1,112 @@
1
+ """Notify Discord"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from json import dumps
6
+ from typing import Any
7
+
8
+ from requests import post
9
+
10
+ from dotflow.abc.notify import Notify
11
+ from dotflow.core.types.status import TypeStatus
12
+ from dotflow.logging import logger
13
+
14
+
15
+ class NotifyDiscord(Notify):
16
+ """
17
+ Import:
18
+ You can import the **NotifyDiscord** class with:
19
+
20
+ from dotflow.providers import NotifyDiscord
21
+
22
+ Example:
23
+ `class` dotflow.providers.notify_discord.NotifyDiscord
24
+
25
+ from dotflow import Config, DotFlow
26
+ from dotflow.providers import NotifyDiscord
27
+ from dotflow.core.types.status import TypeStatus
28
+
29
+ config = Config(
30
+ notify=NotifyDiscord(
31
+ webhook_url="https://discord.com/api/webhooks/...",
32
+ notification_type=TypeStatus.FAILED,
33
+ )
34
+ )
35
+
36
+ workflow = DotFlow(config=config)
37
+
38
+ Args:
39
+ webhook_url (str): Discord webhook URL.
40
+
41
+ notification_type (Optional[TypeStatus]): Filter notifications
42
+ by task status. If None, all statuses are notified.
43
+
44
+ show_result (bool): Include task result in the notification.
45
+ Defaults to False.
46
+
47
+ timeout (float): Request timeout in seconds.
48
+ """
49
+
50
+ COLORS = {
51
+ TypeStatus.COMPLETED: 0x4CAF50,
52
+ TypeStatus.FAILED: 0xF44336,
53
+ TypeStatus.RETRY: 0xFF9800,
54
+ TypeStatus.IN_PROGRESS: 0x2196F3,
55
+ TypeStatus.NOT_STARTED: 0x9E9E9E,
56
+ TypeStatus.PAUSED: 0x607D8B,
57
+ }
58
+
59
+ def __init__(
60
+ self,
61
+ webhook_url: str,
62
+ notification_type: TypeStatus | None = None,
63
+ show_result: bool = False,
64
+ timeout: float = 1.5,
65
+ ):
66
+ self.webhook_url = webhook_url
67
+ self.notification_type = notification_type
68
+ self.show_result = show_result
69
+ self.timeout = timeout
70
+
71
+ def hook_status_task(self, task: Any) -> None:
72
+ if self.notification_type and self.notification_type != task.status:
73
+ return
74
+
75
+ try:
76
+ response = post(
77
+ url=self.webhook_url,
78
+ headers={"Content-Type": "application/json"},
79
+ data=dumps({"embeds": [self._build_embed(task)]}),
80
+ timeout=self.timeout,
81
+ )
82
+ response.raise_for_status()
83
+ except Exception as error:
84
+ logger.error(
85
+ "Internal problem sending notification on Discord: %s",
86
+ str(error),
87
+ )
88
+
89
+ def _build_embed(self, task: Any) -> dict:
90
+ embed = {
91
+ "title": f"{TypeStatus.get_symbol(task.status)} {task.status}",
92
+ "color": self.COLORS.get(task.status, 0x9E9E9E),
93
+ "description": f"`{task.workflow_id}` — Task {task.task_id}",
94
+ "fields": [],
95
+ }
96
+
97
+ if self.show_result:
98
+ embed["fields"].append(
99
+ {
100
+ "name": "Result",
101
+ "value": f"```json\n{task.result(max=1024)}```",
102
+ }
103
+ )
104
+
105
+ if task.status == TypeStatus.FAILED and task.errors:
106
+ last_error = task.errors[-1]
107
+ error_value = f"`{last_error.exception}`: {last_error.message}"
108
+ embed["fields"].append(
109
+ {"name": "Error", "value": error_value[:1024]}
110
+ )
111
+
112
+ return embed
@@ -0,0 +1,109 @@
1
+ """Notify Telegram"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from json import dumps
6
+ from typing import Any
7
+
8
+ from requests import post
9
+
10
+ from dotflow.abc.notify import Notify
11
+ from dotflow.core.types.status import TypeStatus
12
+ from dotflow.logging import logger
13
+
14
+
15
+ class NotifyTelegram(Notify):
16
+ """
17
+ Import:
18
+ You can import the **NotifyTelegram** class with:
19
+
20
+ from dotflow.providers import NotifyTelegram
21
+
22
+ Example:
23
+ `class` dotflow.providers.notify_telegram.NotifyTelegram
24
+
25
+ from dotflow import Config, DotFlow
26
+ from dotflow.providers import NotifyTelegram
27
+ from dotflow.core.types.status import TypeStatus
28
+
29
+ config = Config(
30
+ notify=NotifyTelegram(
31
+ token="YOUR_BOT_TOKEN",
32
+ chat_id=123456789,
33
+ notification_type=TypeStatus.FAILED,
34
+ )
35
+ )
36
+
37
+ workflow = DotFlow(config=config)
38
+
39
+ Args:
40
+ token (str): Telegram bot token from BotFather.
41
+
42
+ chat_id (int): Telegram chat ID to send messages to.
43
+
44
+ notification_type (Optional[TypeStatus]): Filter notifications
45
+ by task status. If None, all statuses are notified.
46
+
47
+ show_result (bool): Include task result in the notification.
48
+ Defaults to False.
49
+
50
+ timeout (float): Request timeout in seconds.
51
+ """
52
+
53
+ API_URL = "https://api.telegram.org/bot{token}/sendMessage"
54
+
55
+ def __init__(
56
+ self,
57
+ token: str,
58
+ chat_id: int,
59
+ notification_type: TypeStatus | None = None,
60
+ show_result: bool = False,
61
+ timeout: float = 1.5,
62
+ ):
63
+ self.token = token
64
+ self.chat_id = chat_id
65
+ self.notification_type = notification_type
66
+ self.show_result = show_result
67
+ self.timeout = timeout
68
+
69
+ def hook_status_task(self, task: Any) -> None:
70
+ if self.notification_type and self.notification_type != task.status:
71
+ return
72
+
73
+ try:
74
+ response = post(
75
+ url=self.API_URL.format(token=self.token),
76
+ headers={"Content-Type": "application/json"},
77
+ data=dumps(
78
+ {
79
+ "chat_id": self.chat_id,
80
+ "text": self._build_message(task),
81
+ "parse_mode": "markdown",
82
+ }
83
+ ),
84
+ timeout=self.timeout,
85
+ )
86
+ response.raise_for_status()
87
+ except Exception as error:
88
+ logger.error(
89
+ "Internal problem sending notification on Telegram: %s",
90
+ str(error),
91
+ )
92
+
93
+ def _build_message(self, task: Any) -> str:
94
+ symbol = TypeStatus.get_symbol(task.status)
95
+ header = f"{symbol} {task.status}"
96
+ footer = f"`{task.workflow_id}` — Task {task.task_id}"
97
+
98
+ parts = [header]
99
+
100
+ if self.show_result:
101
+ parts.append(f"```json\n{task.result(max=4000)}```")
102
+
103
+ if task.status == TypeStatus.FAILED and task.errors:
104
+ last_error = task.errors[-1]
105
+ parts.append(f"`{last_error.exception}`: {last_error.message}")
106
+
107
+ parts.append(footer)
108
+
109
+ return "\n".join(parts)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dotflow"
3
- version = "0.14.1.dev1"
3
+ version = "0.14.1.dev2"
4
4
  authors = [
5
5
  { name="Fernando Celmer", email="email@fernandocelmer.com" },
6
6
  ]
@@ -60,7 +60,7 @@ scheduler = ["croniter"]
60
60
 
61
61
  [tool.poetry]
62
62
  name = "dotflow"
63
- version = "0.14.1.dev1"
63
+ version = "0.14.1.dev2"
64
64
  description = "🎲 Dotflow turns an idea into flow! Lightweight Python library for execution pipelines with retry, parallel, cron and async support."
65
65
  authors = ["Fernando Celmer <email@fernandocelmer.com>"]
66
66
  readme = "README.md"
@@ -1,59 +0,0 @@
1
- """Notify Default"""
2
-
3
- from __future__ import annotations
4
-
5
- from json import dumps
6
- from typing import Any
7
-
8
- from requests import post
9
-
10
- from dotflow.abc.notify import Notify
11
- from dotflow.core.types.status import TypeStatus
12
- from dotflow.logging import logger
13
-
14
-
15
- class NotifyTelegram(Notify):
16
- MESSAGE = "{symbol} {status}\n```json\n{task}```\n{workflow_id}-{task_id}"
17
- API_TELEGRAM = "https://api.telegram.org/bot{token}/sendMessage"
18
-
19
- def __init__(
20
- self,
21
- token: str,
22
- chat_id: int,
23
- notification_type: TypeStatus | None = None,
24
- timeout: int = 1.5,
25
- ):
26
- self.token = token
27
- self.chat_id = chat_id
28
- self.notification_type = notification_type
29
- self.timeout = timeout
30
-
31
- def send(self, task: Any) -> None:
32
- if not self.notification_type or self.notification_type == task.status:
33
- data = {
34
- "chat_id": self.chat_id,
35
- "text": self._get_text(task=task),
36
- "parse_mode": "markdown",
37
- }
38
- try:
39
- response = post(
40
- url=self.API_TELEGRAM.format(token=self.token),
41
- headers={"Content-Type": "application/json"},
42
- data=dumps(data),
43
- timeout=self.timeout,
44
- )
45
- response.raise_for_status()
46
- except Exception as error:
47
- logger.error(
48
- "Internal problem sending notification on Telegram: %s",
49
- str(error),
50
- )
51
-
52
- def _get_text(self, task: Any) -> str:
53
- return self.MESSAGE.format(
54
- symbol=TypeStatus.get_symbol(task.status),
55
- status=task.status,
56
- workflow_id=task.workflow_id,
57
- task_id=task.task_id,
58
- task=task.result(max=4000),
59
- )
File without changes