Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LAST_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.0
0.13.0.dev1
2 changes: 2 additions & 0 deletions docs/blog/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Blog

1 change: 1 addition & 0 deletions docs/nav/development/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [📦 PyPI - Build 0.13.0](https://github.com/dotflow-io/dotflow/releases/tag/v0.13.0)
- [📌 Action with timeout, retry_delay and backoff](https://github.com/dotflow-io/dotflow/pull/56)
- [📌 Notification with Telegram](https://github.com/dotflow-io/dotflow/pull/59)

## v0.12.0

Expand Down
39 changes: 39 additions & 0 deletions docs/nav/learn/dotflow-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Dotflow CLI

May 4, 2025

---

The Dotflow CLI (Command-Line Interface) is a feature that simplifies the execution of workflows in environments that require running through terminal commands.

### Simple Start

```bash
dotflow start --step examples.cli_with_mode.simple_step
```

### With Initial Context

```bash
dotflow start --step examples.cli_with_initial_context.simple_step --initial-context abc
```

### With Callback

```bash
dotflow start --step examples.cli_with_callback.simple_step --callback examples.cli_with_callback.callback
```

### With Mode

```bash
dotflow start --step examples.cli_with_mode.simple_step --mode sequential
```

```bash
dotflow start --step examples.cli_with_mode.simple_step --mode background
```

```bash
dotflow start --step examples.cli_with_mode.simple_step --mode parallel
```
File renamed without changes.
104 changes: 104 additions & 0 deletions docs/nav/learn/notify-with-telegram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Notify with Telegram

May 4, 2025

---

## Bot Telegram

### Create a bot on Telegram:

1 - Open the **Telegram** app on your mobile device.

2 - Tap the **search** icon and look for **BotFather**.

3 - Open the contact and type `/newbot`.

4 - Enter a name for your bot.

### Generate Bot Token:

1 - Type the command `/token`.

2 - Select the bot you just created from the list.

3 - **BotFather** will return an access token — copy and save this token securely. It will be required to authenticate your bot.

### Retrieving Your Telegram Chat ID

1 - Send a message to your bot in Telegram to ensure it appears in the bot's update log.

2 - Run the following `curl` command to fetch the latest updates from your bot:

```bash
curl --location --globoff 'https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates'
```
> Replace `<YOUR_BOT_TOKEN>` with the token provided by BotFather.

3 - Inspect the API response, and locate the following key `result[0].channel_post.chat.id`. This is your chat ID.

4 - Copy and store the chat ID in a secure location. You will need it to configure the `NotifyTelegram` instance.


## DotFlow Config

### Import

Start with the basics, which is importing the necessary classes and methods.

```python
import os

from dotflow import Config, DotFlow, action
from dotflow.notify import NotifyTelegram
from dotflow.types import TypeStatus
```

### Task function

Use the `@action` decorator to define a simple task that will be executed in the workflow:

```python
@action
def simple_task():
return "ok"
```


### Notify Class

Instantiate the `NotifyTelegram` class with your Telegram bot credentials. You can use environment variables for security:

```python
notify = NotifyTelegram(
token=os.getenv("TOKEN"),
chat_id=os.getenv("CHAT_ID"),
notification_type=TypeStatus.FAILED # Notify only on failure
)
```

### Dotflow Class

Pass the `notify` instance into the `Config` class and initialize the `DotFlow` workflow:

```python
workflow = DotFlow(
config=Config(notify=notify)
)
```

### Add Task

Add your defined task as a step in the workflow:

```python
workflow.task.add(step=simple_task)
```

### Start

Start the workflow execution:

```python
workflow.start()
```
3 changes: 3 additions & 0 deletions docs/nav/reference/abc-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Log

::: dotflow.abc.log.Log
3 changes: 3 additions & 0 deletions docs/nav/reference/abc-notify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Notify

::: dotflow.abc.notify.Notify
4 changes: 2 additions & 2 deletions docs/nav/reference/type-status.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TaskStatus
# TypeStatus

::: dotflow.core.types.status.TaskStatus
::: dotflow.core.types.status.TypeStatus
options:
members:
- NOT_STARTED
Expand Down
2 changes: 1 addition & 1 deletion dotflow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dotflow __init__ module."""

__version__ = "0.12.0"
__version__ = "0.13.0.dev1"
__description__ = "🎲 Dotflow turns an idea into flow!"

from .core.action import Action as action
Expand Down
17 changes: 17 additions & 0 deletions dotflow/abc/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Notify ABC"""

from typing import Any

from abc import ABC, abstractmethod


class Log(ABC):
"""Log"""

@abstractmethod
def info(self, task: Any) -> None:
"""Info"""

@abstractmethod
def error(self, task: Any) -> None:
"""Error"""
13 changes: 13 additions & 0 deletions dotflow/abc/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Notify ABC"""

from typing import Any

from abc import ABC, abstractmethod


class Notify(ABC):
"""Notify"""

@abstractmethod
def send(self, task: Any) -> None:
"""Send"""
38 changes: 32 additions & 6 deletions dotflow/core/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
"""Config module"""

from typing import Optional

from dotflow.abc.log import Log
from dotflow.abc.storage import Storage
from dotflow.abc.notify import Notify

from dotflow.providers.log_default import LogDefault
from dotflow.providers.storage_default import StorageDefault
from dotflow.providers.notify_default import NotifyDefault


class Config:
Expand All @@ -10,19 +17,38 @@ class Config:
You can import the **Config** class with:

from dotflow import Config
from dotflow.storage import StorageDefault
from dotflow.providers import (
StorageDefault,
NotifyDefault,
LogDefault
)

Example:
`class` dotflow.core.config.Config

config = Config(storage=StorageDefault)
config = Config(
storage=StorageFile(path=".output"),
notify=NotifyDefault(),
log=LogDefault()
)

Args:
storage (Storage): Type of the storage.
storage (Optional[Storage]): Type of the storage.
notify (Optional[Notify]): Type of the notify.
log (Optional[Log]): Type of the notify.

Attributes:
storage (Storage):
storage (Optional[Storage]):
notify (Optional[Notify]):
log (Optional[Log]):
"""

def __init__(self, storage: Storage = StorageDefault()) -> None:
self.storage = storage
def __init__(
self,
storage: Optional[Storage] = None,
notify: Optional[Notify] = None,
log: Optional[Log] = None,
) -> None:
self.storage = storage if storage else StorageDefault()
self.notify = notify if notify else NotifyDefault()
self.log = log if log else LogDefault()
6 changes: 4 additions & 2 deletions dotflow/core/dotflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from uuid import uuid4
from functools import partial
from typing import Optional

from dotflow.core.config import Config
from dotflow.core.workflow import Manager
Expand All @@ -26,7 +27,7 @@ class DotFlow:
workflow = DotFlow(config=config)

Args:
config (Config): Configuration class.
config (Optional[Config]): Configuration class.

Attributes:
workflow_id (UUID):
Expand All @@ -38,9 +39,10 @@ class DotFlow:

def __init__(
self,
config: Config = Config()
config: Optional[Config] = None
) -> None:
self.workflow_id = uuid4()
config = config if config else Config()

self.task = TaskBuilder(
config=config,
Expand Down
21 changes: 12 additions & 9 deletions dotflow/core/execution.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Execution module"""

from uuid import UUID
from datetime import datetime
from typing import Callable, List, Tuple
from inspect import getsourcelines
from types import FunctionType
Expand All @@ -15,9 +16,8 @@
from dotflow.core.action import Action
from dotflow.core.context import Context
from dotflow.core.task import Task
from dotflow.core.types import TaskStatus
from dotflow.core.types import TypeStatus

from dotflow.core.decorators import time
from dotflow.utils import basic_callback


Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__(
_flow_callback: Callable = basic_callback,
) -> None:
self.task = task
self.task.status = TaskStatus.IN_PROGRESS
self.task.status = TypeStatus.IN_PROGRESS
self.task.previous_context = previous_context
self.task.workflow_id = workflow_id

Expand Down Expand Up @@ -106,14 +106,15 @@ def _execution_with_class(self, class_instance: Callable):
callable_list=callable_list, class_instance=class_instance
)

for _, new in ordered_list:
new_object = getattr(class_instance, new)
for index, new in enumerate(ordered_list):
new_object = getattr(class_instance, new[1])
try:
subcontext = new_object(
initial_context=self.task.initial_context,
previous_context=previous_context,
task=self.task,
)
subcontext.task_id = index
new_context.storage.append(subcontext)
previous_context = subcontext

Expand All @@ -127,6 +128,7 @@ def _execution_with_class(self, class_instance: Callable):
previous_context=previous_context,
task=self.task,
)
subcontext.task_id = index
new_context.storage.append(subcontext)
previous_context = subcontext

Expand All @@ -135,9 +137,9 @@ def _execution_with_class(self, class_instance: Callable):

return new_context

@time
def _excution(self, _flow_callback):
try:
start = datetime.now()
current_context = self.task.step(
initial_context=self.task.initial_context,
previous_context=self.task.previous_context,
Expand All @@ -149,16 +151,17 @@ def _excution(self, _flow_callback):
class_instance=current_context.storage
)

self.task.status = TaskStatus.COMPLETED
self.task.current_context = current_context
self.task.duration = (datetime.now() - start).total_seconds()
self.task.status = TypeStatus.COMPLETED

except AssertionError as err:
raise err

except Exception as err:
self.task.status = TaskStatus.FAILED
self.task.current_context = None
self.task.error = err
self.task.current_context = None
self.task.status = TypeStatus.FAILED

finally:
self.task.callback(task=self.task)
Expand Down
Loading