zsynctech-studio-sdk 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.
@@ -0,0 +1,21 @@
1
+ from zsynctech_studio_sdk.client import set_credentials
2
+ from zsynctech_studio_sdk.models.config import Config
3
+ from zsynctech_studio_sdk.start import StartService
4
+ from zsynctech_studio_sdk.execution import Execution
5
+ from zsynctech_studio_sdk.task import Task
6
+ from zsynctech_studio_sdk.step import Step
7
+ import tomllib
8
+
9
+ with open("pyproject.toml", "rb") as f:
10
+ config = tomllib.load(f)
11
+
12
+ __version__ = config.get("project").get("version")
13
+
14
+ __all__ = [
15
+ "set_credentials",
16
+ "StartService",
17
+ "Execution",
18
+ "Task",
19
+ "Step",
20
+ "Config"
21
+ ]
@@ -0,0 +1,39 @@
1
+ import httpx
2
+
3
+ _client = None
4
+ _secret_key = None
5
+ _instance_id = None
6
+ _server = None
7
+
8
+
9
+ def set_credentials(secret_key: str, instance_id: str, server: str):
10
+ global _client, _secret_key, _server, _instance_id
11
+ _secret_key = secret_key
12
+ _instance_id = instance_id
13
+ _server = str(server).rstrip("/")
14
+ _client = httpx.Client(
15
+ base_url=f"{_server}/automation-gateway/",
16
+ headers={
17
+ "Authorization": f"Bearer {_secret_key}::{_instance_id}"
18
+ }
19
+ )
20
+
21
+
22
+ def request(method: str, endpoint: str, **kwargs) -> httpx.Response:
23
+ if _client is None:
24
+ raise RuntimeError("Credentials not set. Call set_credentials() first.")
25
+ response = _client.request(method, endpoint, **kwargs)
26
+ response.raise_for_status()
27
+ return response
28
+
29
+ def get(endpoint: str, params: dict = None) -> httpx.Response:
30
+ return request("GET", endpoint, params=params)
31
+
32
+ def post(endpoint: str, json: dict = None) -> httpx.Response:
33
+ return request("POST", endpoint, json=json)
34
+
35
+ def put(endpoint: str, json: dict = None) -> httpx.Response:
36
+ return request("PUT", endpoint, json=json)
37
+
38
+ def delete(endpoint: str) -> httpx.Response:
39
+ return request("DELETE", endpoint)
@@ -0,0 +1,10 @@
1
+ from .execution import ExecutionStatus
2
+ from .task import TaskStatus
3
+ from .step import StepStatus
4
+
5
+
6
+ __all__ = [
7
+ "ExecutionStatus",
8
+ "TaskStatus",
9
+ "StepStatus",
10
+ ]
@@ -0,0 +1,11 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class ExecutionStatus(StrEnum):
5
+ WAITING = 'WAITING'
6
+ RUNNING = 'RUNNING'
7
+ FINISHED = 'FINISHED'
8
+ ERROR = 'ERROR'
9
+ SCHEDULED = 'SCHEDULED'
10
+ INTERRUPTED = 'INTERRUPTED'
11
+ OUT_OF_OPERATING_HOURS = 'OUT_OF_OPERATING_HOURS'
@@ -0,0 +1,8 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class StepStatus(StrEnum):
5
+ UNPROCESSED = 'UNPROCESSED'
6
+ RUNNING = 'RUNNING'
7
+ SUCCESS = 'SUCCESS'
8
+ FAIL = 'FAIL'
@@ -0,0 +1,9 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class TaskStatus(StrEnum):
5
+ UNPROCESSED = 'UNPROCESSED'
6
+ VALIDATION_ERROR = 'VALIDATION_ERROR'
7
+ SUCCESS = 'SUCCESS'
8
+ FAIL = 'FAIL'
9
+ RUNNING = 'RUNNING'
@@ -0,0 +1,156 @@
1
+ from zsynctech_studio_sdk.models import ExecutionModel
2
+ from zsynctech_studio_sdk.enums import ExecutionStatus
3
+ from zsynctech_studio_sdk.utils import get_utc_now
4
+ from zsynctech_studio_sdk import client
5
+ from typing import Optional, Any
6
+
7
+ EXECUTION_STATUS_COMPLETED = [
8
+ ExecutionStatus.ERROR,
9
+ ExecutionStatus.FINISHED,
10
+ ExecutionStatus.OUT_OF_OPERATING_HOURS,
11
+ ExecutionStatus.INTERRUPTED,
12
+ ]
13
+
14
+
15
+ class Execution:
16
+ def __init__(self, execution_id: str):
17
+ self._current_execution = ExecutionModel(
18
+ id=execution_id
19
+ )
20
+ self._resource_path = "executions"
21
+
22
+ @property
23
+ def execution_id(self):
24
+ return self._current_execution.id
25
+
26
+ def _update(
27
+ self,
28
+ status: Optional[ExecutionStatus] = None,
29
+ observation: Optional[str] = None,
30
+ total_task_count: Optional[int] = None,
31
+ current_task_count: Optional[int] = None,
32
+ ) -> dict:
33
+
34
+ if self._current_execution.status in EXECUTION_STATUS_COMPLETED:
35
+ return self._current_execution.model_dump()
36
+
37
+ if status in EXECUTION_STATUS_COMPLETED:
38
+ self._current_execution.endDate = get_utc_now()
39
+
40
+ if status is not None:
41
+ self._current_execution.status = status
42
+
43
+ if observation is not None:
44
+ self._current_execution.observation = observation
45
+
46
+ if total_task_count is not None:
47
+ self._current_execution.totalTaskCount = total_task_count
48
+
49
+ if current_task_count is not None:
50
+ self._current_execution.currentTaskCount = current_task_count
51
+
52
+ client.post(
53
+ endpoint=self._resource_path,
54
+ json=self._current_execution.model_dump()
55
+ )
56
+
57
+ return self._current_execution.model_dump()
58
+
59
+ def set_total_task_count(self, total_task_count: int) -> dict[str, Any]:
60
+ """Update the total number of tasks to be processed
61
+
62
+ Args:
63
+ total_task_count (int): total number of tasks to be processed.
64
+
65
+ Returns:
66
+ dict: Dictionary containing the information of the current execution
67
+ """
68
+ return self._update(total_task_count=total_task_count)
69
+
70
+ def update_current_task_count(self, current_task_count: int) -> dict[str, Any]:
71
+ """Update the number of tasks currently processed
72
+
73
+ Args:
74
+ current_task_count (int): Number of tasks processed.
75
+
76
+ Returns:
77
+ dict: Dictionary containing the information of the current execution
78
+ """
79
+ return self._update(current_task_count=current_task_count)
80
+
81
+ def update_observation(self, observation: str) -> dict[str, Any]:
82
+ """Updates the execution observation text
83
+
84
+ Args:
85
+ observation (str): Execution observation text.
86
+
87
+ Returns:
88
+ dict: Dictionary containing the information of the current execution
89
+ """
90
+ return self._update(observation=observation)
91
+
92
+ def start(self, observation: Optional[str] = None) -> dict:
93
+ """Updates the execution status to running
94
+
95
+ Args:
96
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
97
+
98
+ Returns:
99
+ dict: Dictionary containing the information of the current execution
100
+ """
101
+ return self._update(ExecutionStatus.RUNNING, observation)
102
+
103
+ def error(self, observation: Optional[str] = None) -> dict:
104
+ """Updates the execution status to error
105
+
106
+ Args:
107
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
108
+
109
+ Returns:
110
+ dict: Dictionary containing the information of the current execution
111
+ """
112
+ return self._update(ExecutionStatus.ERROR, observation=observation)
113
+
114
+ def waiting(self, observation: Optional[str] = None) -> dict:
115
+ """Updates the execution status to waiting
116
+
117
+ Args:
118
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
119
+
120
+ Returns:
121
+ dict: Dictionary containing the information of the current execution
122
+ """
123
+ return self._update(ExecutionStatus.WAITING, observation=observation)
124
+
125
+ def out_of_operating_hours(self, observation: Optional[str] = None) -> dict:
126
+ """Updates the execution status to out_of_operating_hours
127
+
128
+ Args:
129
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
130
+
131
+ Returns:
132
+ dict: Dictionary containing the information of the current execution
133
+ """
134
+ return self._update(ExecutionStatus.OUT_OF_OPERATING_HOURS, observation=observation)
135
+
136
+ def finished(self, observation: Optional[str] = None) -> dict:
137
+ """Updates the execution status to finished
138
+
139
+ Args:
140
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
141
+
142
+ Returns:
143
+ dict: Dictionary containing the information of the current execution
144
+ """
145
+ return self._update(ExecutionStatus.FINISHED, observation=observation)
146
+
147
+ def interrupted(self, observation: Optional[str] = None) -> dict:
148
+ """Updates the execution status to finished
149
+
150
+ Args:
151
+ observation (Optional[str], optional): Execution observation text. Defaults to None.
152
+
153
+ Returns:
154
+ dict: Dictionary containing the information of the current execution
155
+ """
156
+ return self._update(ExecutionStatus.INTERRUPTED, observation=observation)
@@ -0,0 +1,12 @@
1
+ from .execution import ExecutionModel
2
+ from .step import StepModel
3
+ from .task import TaskModel
4
+ from .config import Config
5
+
6
+
7
+ __all__ = [
8
+ "ExecutionModel",
9
+ "StepModel",
10
+ "TaskModel",
11
+ "Config",
12
+ ]
@@ -0,0 +1,47 @@
1
+ from zsynctech_studio_sdk.enums.execution import ExecutionStatus
2
+ from pydantic import BaseModel, Field, field_validator
3
+ from zsynctech_studio_sdk.enums.step import StepStatus
4
+ from zsynctech_studio_sdk.enums.task import TaskStatus
5
+ from uuid_extensions.uuid7 import uuid7
6
+ from typing import Optional, Union
7
+ from datetime import datetime
8
+ import re
9
+
10
+
11
+ class BaseEntity(BaseModel):
12
+ id: str = Field(
13
+ default_factory=lambda: str(uuid7()),
14
+ min_length=1,
15
+ description="ID único da execução"
16
+ )
17
+ observation: Optional[str] = None
18
+ status: Optional[Union[ExecutionStatus, StepStatus, TaskStatus]] = None
19
+ endDate: Optional[str] = None
20
+
21
+ @field_validator('id')
22
+ @classmethod
23
+ def validate_id_format(cls, v):
24
+ if not re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', v):
25
+ raise ValueError('ID deve ser um UUID válido')
26
+ version = int(v[14], 16)
27
+ if version != 7:
28
+ raise ValueError('ID deve ser um UUID7 válido')
29
+ return v
30
+
31
+ @field_validator('endDate')
32
+ @classmethod
33
+ def validate_end_date_format(cls, v):
34
+ if v is None:
35
+ return v
36
+ pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
37
+ if not re.match(pattern, v):
38
+ raise ValueError('endDate deve estar no formato ISO 8601 com Z')
39
+ try:
40
+ datetime.fromisoformat(v.replace('Z', '+00:00'))
41
+ except ValueError:
42
+ raise ValueError('endDate deve ser uma data válida')
43
+ return v
44
+
45
+ class Config:
46
+ extra = "forbid"
47
+ validate_assignment = True
@@ -0,0 +1,32 @@
1
+ from typing import Optional, List
2
+ from pydantic import BaseModel
3
+ from enum import StrEnum
4
+
5
+
6
+ class InputOutputTypes(StrEnum):
7
+ FTP = 'FTP'
8
+ API = 'API'
9
+ FILA = 'FILA'
10
+
11
+
12
+ class Credential(BaseModel):
13
+ key: str
14
+ value: str
15
+ encrypted: bool
16
+
17
+
18
+ class Config(BaseModel):
19
+ instanceId: str
20
+ executionId: str
21
+ automationName: Optional[str] = "System"
22
+ clientId: Optional[str] = None
23
+ userId: Optional[str] = "System"
24
+ outputPath: Optional[str] = None
25
+ inputPath: Optional[str] = None
26
+ inputMetaData: Optional[dict] = None
27
+ inputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
28
+ outputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
29
+ outputMetaData: Optional[dict] = None
30
+ keepAlive: Optional[bool] = False
31
+ keepAliveInterval: Optional[int] = 30
32
+ credentials: Optional[List[Credential]] = None
@@ -0,0 +1,18 @@
1
+ from zsynctech_studio_sdk.enums.execution import ExecutionStatus
2
+ from zsynctech_studio_sdk.models.base import BaseEntity
3
+ from pydantic import model_validator
4
+
5
+
6
+ class ExecutionModel(BaseEntity):
7
+ totalTaskCount: int = 0
8
+ currentTaskCount: int = 0
9
+ status: ExecutionStatus = ExecutionStatus.WAITING
10
+
11
+ @model_validator(mode='after')
12
+ def validate_business_rules(self):
13
+ current = self.currentTaskCount or 0
14
+ total = self.totalTaskCount or 0
15
+ if current > total:
16
+ raise ValueError('currentTaskCount não pode ser maior que totalTaskCount')
17
+
18
+ return self
@@ -0,0 +1,28 @@
1
+ from zsynctech_studio_sdk.models.base import BaseEntity
2
+ from zsynctech_studio_sdk.enums.step import StepStatus
3
+ from pydantic import field_validator
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ import re
7
+
8
+
9
+ class StepModel(BaseEntity):
10
+ status: StepStatus = StepStatus.UNPROCESSED
11
+ taskId: Optional[str] = None
12
+ stepCode: Optional[str] = None
13
+ startDate: Optional[str] = None
14
+ automationOnClientId: str
15
+
16
+ @field_validator('startDate')
17
+ @classmethod
18
+ def validate_end_date_format(cls, v):
19
+ if v is None:
20
+ return v
21
+ pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
22
+ if not re.match(pattern, v):
23
+ raise ValueError('endDate deve estar no formato ISO 8601 com Z')
24
+ try:
25
+ datetime.fromisoformat(v.replace('Z', '+00:00'))
26
+ except ValueError:
27
+ raise ValueError('endDate deve ser uma data válida')
28
+ return v
@@ -0,0 +1,29 @@
1
+ from zsynctech_studio_sdk.models.base import BaseEntity
2
+ from zsynctech_studio_sdk.enums.task import TaskStatus
3
+ from pydantic import field_validator
4
+ from datetime import datetime
5
+ from typing import Optional
6
+ import re
7
+
8
+
9
+ class TaskModel(BaseEntity):
10
+ status: TaskStatus = TaskStatus.UNPROCESSED
11
+ description: Optional[str] = None
12
+ jsonData: Optional[dict] = {}
13
+ code: Optional[str] = None
14
+ executionId: Optional[str] = None
15
+ startDate: Optional[str] = None
16
+
17
+ @field_validator('startDate')
18
+ @classmethod
19
+ def validate_end_date_format(cls, v):
20
+ if v is None:
21
+ return v
22
+ pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
23
+ if not re.match(pattern, v):
24
+ raise ValueError('endDate deve estar no formato ISO 8601 com Z')
25
+ try:
26
+ datetime.fromisoformat(v.replace('Z', '+00:00'))
27
+ except ValueError:
28
+ raise ValueError('endDate deve ser uma data válida')
29
+ return v
@@ -0,0 +1,88 @@
1
+ from zsynctech_studio_sdk.utils import validate_id_format
2
+ from zsynctech_studio_sdk.models import Config
3
+ from zsynctech_studio_sdk import client
4
+ from typing import Callable, Optional
5
+ import pika
6
+ import json
7
+
8
+ EXCHANGE_NAME = "start"
9
+
10
+
11
+ class StartService:
12
+ def __init__(self, rabbitmq_url: str, heartbeat: int = 5400):
13
+ if client._instance_id is None:
14
+ raise RuntimeError("Credentials not set. Call set_credentials() first.")
15
+ self._instance_id = validate_id_format(client._instance_id)
16
+ self._queue_name = f"robot_{self._instance_id}"
17
+ self._connection = pika.BlockingConnection(
18
+ parameters=pika.URLParameters(
19
+ f"{rabbitmq_url}?heartbeat={heartbeat}"
20
+ )
21
+ )
22
+ self._channel = self._connection.channel()
23
+
24
+ self._channel.exchange_declare(
25
+ exchange=EXCHANGE_NAME,
26
+ exchange_type='direct',
27
+ durable=True
28
+ )
29
+ self._channel.queue_declare(
30
+ queue=self._queue_name,
31
+ durable=True
32
+ )
33
+ self._channel.queue_bind(
34
+ exchange=EXCHANGE_NAME,
35
+ queue=self._queue_name,
36
+ routing_key=f"instance.{self._instance_id}"
37
+ )
38
+
39
+ def get_start_config(self) -> Optional[Config]:
40
+ """Checks if there are start events in the queue
41
+
42
+ Returns:
43
+ Optional[Config]: Returns the configuration json,
44
+ if the queue is empty it returns None
45
+ """
46
+ method_frame, _, body = self._channel.basic_get(
47
+ queue=self._queue_name,
48
+ auto_ack=True
49
+ )
50
+ if method_frame:
51
+ try:
52
+ message = json.loads(body)
53
+ except json.JSONDecodeError:
54
+ message = body.decode()
55
+ return Config(**message)
56
+ return None
57
+
58
+ def start_listener(self, callback: Callable):
59
+ """Starts a consumer to check for start events
60
+
61
+ Args:
62
+ callback (Callable): Function that will be
63
+ called when there is an event.
64
+ """
65
+ def _internal_callback(ch, method, properties, body):
66
+ try:
67
+ message = json.loads(body)
68
+ except json.JSONDecodeError:
69
+ message = body.decode()
70
+ callback(Config(**message))
71
+
72
+ self._channel.basic_consume(
73
+ queue=self._queue_name,
74
+ on_message_callback=_internal_callback,
75
+ auto_ack=True
76
+ )
77
+
78
+ print(f"[StartService] Waiting for events in queue: {self._queue_name}...")
79
+
80
+ try:
81
+ self._channel.start_consuming()
82
+ except KeyboardInterrupt:
83
+ self.close()
84
+
85
+ def close(self):
86
+ if self._connection and not self._connection.is_closed:
87
+ self._connection.close()
88
+ print("[StartService] Connection closed.")
@@ -0,0 +1,90 @@
1
+ from zsynctech_studio_sdk.utils import get_utc_now
2
+ from zsynctech_studio_sdk.models import StepModel
3
+ from zsynctech_studio_sdk.enums import StepStatus
4
+ from zsynctech_studio_sdk import client
5
+ from typing import Optional
6
+
7
+
8
+ STEP_STATUS_COMPLETED = [
9
+ StepStatus.FAIL,
10
+ StepStatus.SUCCESS,
11
+ ]
12
+
13
+
14
+ class Step(StepModel):
15
+ def __init__(self, task_id: str, code: str, observation: Optional[str] = None):
16
+ self._current_step = StepModel(
17
+ stepCode=code,
18
+ taskId=task_id,
19
+ startDate=get_utc_now(),
20
+ observation=observation,
21
+ automationOnClientId=client._instance_id
22
+ )
23
+ self._resource_path = "taskSteps"
24
+
25
+ def _update(
26
+ self,
27
+ status: Optional[StepStatus] = None,
28
+ observation: Optional[str] = None,
29
+ ) -> dict:
30
+ if status in STEP_STATUS_COMPLETED:
31
+ self._current_step.endDate = get_utc_now()
32
+
33
+ if observation is not None:
34
+ self._current_step.observation = observation
35
+
36
+ if status is not None:
37
+ self._current_step.status = status
38
+
39
+ client.post(
40
+ endpoint=f"{self._resource_path}",
41
+ json=self._current_step.model_dump()
42
+ )
43
+
44
+ return self._current_step.model_dump()
45
+
46
+ def _start(self, observation: Optional[str] = None) -> dict:
47
+ """Updates the step status to running
48
+
49
+ Args:
50
+ observation (Optional[str], optional): Step observation text. Defaults to None.
51
+
52
+ Returns:
53
+ dict: Dictionary containing the information of the current step
54
+ """
55
+ return self._update(status=StepStatus.RUNNING, observation=observation)
56
+
57
+ def fail(self, observation: Optional[str] = None) -> dict:
58
+ """Updates the step status to fail
59
+
60
+ Args:
61
+ observation (Optional[str], optional): Step observation text. Defaults to None.
62
+
63
+ Returns:
64
+ dict: Dictionary containing the information of the current step
65
+ """
66
+ return self._update(status=StepStatus.FAIL, observation=observation)
67
+
68
+ def success(self, observation: Optional[str] = None) -> dict:
69
+ """Updates the step status to success
70
+
71
+ Args:
72
+ observation (Optional[str], optional): Step observation text. Defaults to None.
73
+
74
+ Returns:
75
+ dict: Dictionary containing the information of the current step
76
+ """
77
+ return self._update(status=StepStatus.SUCCESS, observation=observation)
78
+
79
+ def __enter__(self):
80
+ self._start()
81
+ return self
82
+
83
+ def __exit__(self, exc_type, exc_value, traceback):
84
+ if self._current_step.status not in STEP_STATUS_COMPLETED:
85
+ if exc_type is not None:
86
+ self.fail(observation=str(exc_value))
87
+ else:
88
+ self.success()
89
+
90
+ return False
@@ -0,0 +1,103 @@
1
+ from zsynctech_studio_sdk.utils import get_utc_now
2
+ from zsynctech_studio_sdk.models import TaskModel
3
+ from zsynctech_studio_sdk.enums import TaskStatus
4
+ from zsynctech_studio_sdk import client
5
+ from uuid_extensions import uuid7
6
+ from typing import Optional
7
+
8
+
9
+ TASK_STATUS_COMPLETED = [
10
+ TaskStatus.FAIL,
11
+ TaskStatus.SUCCESS,
12
+ TaskStatus.VALIDATION_ERROR,
13
+ ]
14
+
15
+
16
+ class Task:
17
+ def __init__(self, execution_id: str, code: Optional[str] = None, description: Optional[str] = None):
18
+ self._current_task = TaskModel(
19
+ executionId=execution_id,
20
+ startDate=get_utc_now()
21
+ )
22
+ self._resource_path = "tasks"
23
+
24
+ if code:
25
+ self._current_task.code = code
26
+ else:
27
+ self._current_task.code = str(uuid7())
28
+
29
+ if description:
30
+ self._current_task.description = description
31
+ else:
32
+ self._current_task.description = "Descrição não informada"
33
+
34
+ @property
35
+ def task_id(self):
36
+ return self._current_task.id
37
+
38
+ def _update(
39
+ self,
40
+ status: Optional[TaskStatus] = None,
41
+ observation: Optional[str] = None,
42
+ ) -> dict:
43
+ if status in TASK_STATUS_COMPLETED:
44
+ self._current_task.endDate = get_utc_now()
45
+
46
+ if status is not None:
47
+ self._current_task.status = status
48
+
49
+ if observation is not None:
50
+ self._current_task.observation = observation
51
+
52
+ client.post(
53
+ endpoint=self._resource_path,
54
+ json=self._current_task.model_dump()
55
+ )
56
+
57
+ return self._current_task.model_dump()
58
+
59
+ def start(self, observation: Optional[str] = None) -> dict:
60
+ """Updates the task status to running
61
+
62
+ Args:
63
+ observation (Optional[str], optional): Task observation text. Defaults to None.
64
+
65
+ Returns:
66
+ dict: Dictionary containing the information of the current task
67
+ """
68
+ return self._update(TaskStatus.RUNNING, observation)
69
+
70
+ def fail(self, observation: Optional[str] = None) -> dict:
71
+ """Updates the task status to fail
72
+
73
+ Args:
74
+ observation (Optional[str], optional): Task observation text. Defaults to None.
75
+
76
+ Returns:
77
+ dict: Dictionary containing the information of the current task
78
+ """
79
+ return self._update(TaskStatus.FAIL, observation=observation)
80
+
81
+ def success(self, observation: Optional[str] = None) -> dict:
82
+ """Updates the task status to success
83
+
84
+ Args:
85
+ observation (Optional[str], optional): Task observation text. Defaults to None.
86
+
87
+ Returns:
88
+ dict: Dictionary containing the information of the current task
89
+ """
90
+ return self._update(TaskStatus.SUCCESS, observation=observation)
91
+
92
+ def __enter__(self):
93
+ self.start()
94
+ return self
95
+
96
+ def __exit__(self, exc_type, exc_value, traceback):
97
+ if self._current_task.status not in TASK_STATUS_COMPLETED:
98
+ if exc_type is not None:
99
+ self.fail(observation=str(exc_value))
100
+ else:
101
+ self.success()
102
+
103
+ return False
@@ -0,0 +1,21 @@
1
+ from datetime import datetime, timezone
2
+ import re
3
+
4
+
5
+ def get_utc_now() -> str:
6
+ """
7
+ Get the current date and time in UTC format as an ISO string.
8
+
9
+ Returns:
10
+ str: Current UTC datetime in ISO format with 'Z' suffix (e.g., '2024-01-15T10:30:45.123Z')
11
+ """
12
+ return datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')
13
+
14
+
15
+ def validate_id_format(v):
16
+ if not re.match(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', v):
17
+ raise ValueError('ID deve ser um UUID válido')
18
+ version = int(v[14], 16)
19
+ if version != 7:
20
+ raise ValueError('ID deve ser um UUID7 válido')
21
+ return v
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: zsynctech-studio-sdk
3
+ Version: 0.1.0
4
+ Summary: Lib
5
+ Author-email: ZSync Tech LTDA <contato@zsynctech.com>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: pika>=1.3.2
9
+ Requires-Dist: pydantic>=2.11.7
10
+ Requires-Dist: rich>=14.1.0
11
+ Requires-Dist: uuid7>=0.1.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # ZSync Tech Studio SDK
15
+
16
+ SDK oficial da ZSync Tech para integração com o ZSync Tech Studio, uma plataforma de automação de processos. Este SDK permite que você desenvolva robôs de automação que se integram perfeitamente com o ecossistema ZSync Tech.
17
+
18
+ ![PyPI](https://img.shields.io/badge/version-0.1.0-green)
19
+
20
+ ## 📋 Índice
21
+
22
+ - [Instalação](#instalação)
23
+ - [Configuração Inicial](#configuração-inicial)
24
+ - [Conceitos Básicos](#conceitos-básicos)
25
+ - [Guia de Uso](#guia-de-uso)
26
+ - [StartService - Recebendo Configurações](#startservice---recebendo-configurações)
27
+ - [Execution - Gerenciando Execuções](#execution---gerenciando-execuções)
28
+ - [Task - Gerenciando Tarefas](#task---gerenciando-tarefas)
29
+ - [Step - Gerenciando Passos](#step---gerenciando-passos)
30
+ - [Exemplo Completo](#exemplo-completo)
31
+ - [API Reference](#api-reference)
32
+ - [Dependências](#dependências)
33
+ - [Suporte](#suporte)
34
+
35
+ ## 🚀 Instalação via pip
36
+
37
+ ```bash
38
+ pip install zsynctech-studio-sdk
39
+ ```
40
+
41
+ ## 🚀 Instalação via uv
42
+
43
+ ```bash
44
+ uv add zsynctech-studio-sdk
45
+ ```
46
+
47
+ ## ⚙️ Configuração Inicial
48
+
49
+ Antes de usar o SDK, você precisa configurar suas credenciais:
50
+
51
+ ```python
52
+ from zsynctech_studio_sdk import set_credentials
53
+
54
+ # Configure suas credenciais
55
+ set_credentials(
56
+ secret_key="sua_secret_key",
57
+ instance_id="seu_instance_id",
58
+ server="https://seu-servidor.com"
59
+ )
60
+ ```
61
+
62
+ ## 🧠 Conceitos Básicos
63
+
64
+ O ZSync Tech Studio SDK trabalha com uma hierarquia de conceitos:
65
+
66
+ 1. **Execution (Execução)**: Representa uma execução completa de um robô
67
+ 2. **Task (Tarefa)**: Uma tarefa específica dentro de uma execução
68
+ 3. **Step (Passo)**: Um passo individual dentro de uma tarefa
69
+
70
+ Cada nível pode ter diferentes status e observações para rastreamento detalhado.
71
+
72
+ ## 📖 Guia de Uso
73
+
74
+ ### StartService - Recebendo Configurações
75
+
76
+ O `StartService` permite que seu robô receba configurações via RabbitMQ:
77
+
78
+ ```python
79
+ from zsynctech_studio_sdk import StartService
80
+
81
+ # Configure o serviço
82
+ start_service = StartService(
83
+ rabbitmq_url="amqp://usuario:senha@servidor:5672/",
84
+ heartbeat=5400
85
+ )
86
+
87
+ # Verificar se há configurações disponíveis
88
+ config = start_service.get_start_config()
89
+ if config:
90
+ print(f"Execução recebida: {config.executionId}")
91
+ # Processar a configuração...
92
+
93
+ # Ou usar um listener contínuo
94
+ def process_config(config):
95
+ print(f"Processando execução: {config.executionId}")
96
+ # Sua lógica aqui...
97
+
98
+ start_service.start_listener(process_config)
99
+ ```
100
+
101
+ ### Execution - Gerenciando Execuções
102
+
103
+ A classe `Execution` gerencia o ciclo de vida de uma execução:
104
+
105
+ ```python
106
+ from zsynctech_studio_sdk import Execution, ExecutionStatus
107
+
108
+ # Criar uma execução
109
+ execution = Execution(config.executionId)
110
+
111
+ # Iniciar a execução
112
+ execution.start("Iniciando processamento...")
113
+
114
+ # Atualizar progresso
115
+ execution.set_total_task_count(100)
116
+ execution.update_current_task_count(50)
117
+
118
+ # Atualizar observação
119
+ execution.update_observation("Processando dados...")
120
+
121
+ # Finalizar com sucesso
122
+ execution.finished("Processamento concluído com sucesso!")
123
+
124
+ # Ou em caso de erro
125
+ execution.error("Erro ao processar dados")
126
+ ```
127
+
128
+ ### Task - Gerenciando Tarefas
129
+
130
+ A classe `Task` gerencia tarefas individuais:
131
+
132
+ ```python
133
+ from zsynctech_studio_sdk import Task
134
+
135
+ # Criar uma tarefa
136
+ task = Task(
137
+ execution_id=execution.execution_id,
138
+ code="TASK_001",
139
+ description="Processar arquivo de dados"
140
+ )
141
+
142
+ # Usar como context manager (recomendado)
143
+ with task:
144
+ # Sua lógica aqui
145
+ process_file()
146
+ # Status será automaticamente SUCCESS ou FAIL
147
+
148
+ # Ou gerenciar manualmente
149
+ task.start("Iniciando processamento do arquivo")
150
+ try:
151
+ process_file()
152
+ task.success("Arquivo processado com sucesso")
153
+ except Exception as e:
154
+ task.fail(f"Erro ao processar arquivo: {str(e)}")
155
+ ```
156
+
157
+ ### Step - Gerenciando Passos
158
+
159
+ A classe `Step` gerencia passos individuais dentro de uma tarefa:
160
+
161
+ ```python
162
+ from zsynctech_studio_sdk import Step
163
+
164
+ # Criar um passo
165
+ step = Step(
166
+ task_id=task.task_id,
167
+ code="STEP_001",
168
+ observation="Validando dados"
169
+ )
170
+
171
+ # Usar como context manager (recomendado)
172
+ with step:
173
+ # Sua lógica aqui
174
+ validate_data()
175
+ # Status será automaticamente SUCCESS ou FAIL
176
+
177
+ # Ou gerenciar manualmente
178
+ step._start("Iniciando validação")
179
+ try:
180
+ validate_data()
181
+ step.success("Dados validados com sucesso")
182
+ except Exception as e:
183
+ step.fail(f"Erro na validação: {str(e)}")
184
+ ```
185
+
186
+ ## 💡 Exemplo Completo
187
+
188
+ ```python
189
+ from zsynctech_studio_sdk import (
190
+ set_credentials, StartService, Execution, Task, Step
191
+ )
192
+
193
+ # 1. Configurar credenciais
194
+ set_credentials(
195
+ secret_key="sua_secret_key",
196
+ instance_id="seu_instance_id",
197
+ server="https://seu-servidor.com"
198
+ )
199
+
200
+ # 2. Configurar StartService
201
+ start_service = StartService("amqp://usuario:senha@servidor:5672/")
202
+
203
+ def process_automation(config):
204
+ """Função principal de processamento"""
205
+
206
+ # 3. Criar execução
207
+ execution = Execution(config.executionId)
208
+ execution.start("Iniciando automação")
209
+
210
+ try:
211
+ # 4. Definir total de tarefas
212
+ execution.set_total_task_count(3)
213
+
214
+ # 5. Processar cada tarefa
215
+ for i, data in enumerate(data_to_process):
216
+ with Task(execution.execution_id, f"TASK_{i:03d}", f"Processar {data}") as task:
217
+
218
+ # 6. Processar passos da tarefa
219
+ with Step(task.task_id, "VALIDATE", "Validando arquivo") as step:
220
+ validate_file(data)
221
+
222
+ with Step(task.task_id, "PROCESS", "Processando dados") as step:
223
+ process_file(data)
224
+
225
+ with Step(task.task_id, "SAVE", "Salvando resultado") as step:
226
+ save_result(data)
227
+
228
+ # 7. Atualizar progresso
229
+ execution.update_current_task_count(i)
230
+
231
+ # 8. Finalizar com sucesso
232
+ execution.finished("Automação concluída com sucesso!")
233
+
234
+ except Exception as e:
235
+ execution.error(f"Erro na automação: {str(e)}")
236
+
237
+ # 9. Iniciar listener
238
+ start_service.start_listener(process_automation)
239
+ ```
240
+
241
+ ## 📚 API Reference
242
+
243
+ ### StartService
244
+
245
+ - `__init__(rabbitmq_url: str, heartbeat: int = 5400)`: Inicializa o serviço
246
+ - `get_start_config() -> Optional[Config]`: Obtém configuração disponível
247
+ - `start_listener(callback: Callable)`: Inicia listener contínuo
248
+ - `close()`: Fecha conexão
249
+
250
+ ### Execution
251
+
252
+ - `start(observation: Optional[str] = None)`: Inicia execução
253
+ - `finished(observation: Optional[str] = None)`: Finaliza com sucesso
254
+ - `error(observation: Optional[str] = None)`: Marca como erro
255
+ - `waiting(observation: Optional[str] = None)`: Marca como aguardando
256
+ - `set_total_task_count(count: int)`: Define total de tarefas
257
+ - `update_current_task_count(count: int)`: Atualiza progresso
258
+ - `update_observation(observation: str)`: Atualiza observação
259
+
260
+ ### Task
261
+
262
+ - `start(observation: Optional[str] = None)`: Inicia tarefa
263
+ - `success(observation: Optional[str] = None)`: Marca como sucesso
264
+ - `fail(observation: Optional[str] = None)`: Marca como falha
265
+ - Suporte a context manager (`with`)
266
+
267
+ ### Step
268
+
269
+ - `_start(observation: Optional[str] = None)`: Inicia passo
270
+ - `success(observation: Optional[str] = None)`: Marca como sucesso
271
+ - `fail(observation: Optional[str] = None)`: Marca como falha
272
+ - Suporte a context manager (`with`)
273
+
274
+ ## 📦 Dependências
275
+
276
+ - `httpx>=0.28.1` - Cliente HTTP
277
+ - `pika>=1.3.2` - Cliente RabbitMQ
278
+ - `pydantic>=2.11.7` - Validação de dados
279
+ - `rich>=14.1.0` - Interface rica
280
+ - `uuid7>=0.1.0` - Geração de UUIDs
281
+
282
+ ## 🆘 Suporte
283
+
284
+ Para suporte técnico, entre em contato:
285
+
286
+ - **Email**: contato@zsynctech.com
287
+ - **Empresa**: ZSync Tech LTDA
288
+
289
+ ---
@@ -0,0 +1,21 @@
1
+ zsynctech_studio_sdk/__init__.py,sha256=ZAnvP6KLyduIxoA5aIPUcW2-WgAr7kq9V-Q5CFgTDuI,549
2
+ zsynctech_studio_sdk/client.py,sha256=FN4XAyaCISyzHnSdU6Fu3eY1X8h_UXpUpcjdfU6KAug,1195
3
+ zsynctech_studio_sdk/execution.py,sha256=-u-roSf8RHsS5EWPsAHTudH45qSeipi0aGO6BxmwVcg,5514
4
+ zsynctech_studio_sdk/start.py,sha256=XiNxtf-7BKKJ87eASIErgJH6nL72flpMMQV52OcGths,2845
5
+ zsynctech_studio_sdk/step.py,sha256=9jp_Byn-9hkkN67KqD7Cqvjd3Dqb_026FsHEtFihtoY,2849
6
+ zsynctech_studio_sdk/task.py,sha256=7p6UVLCyniHsI_pAusnq1LW_j39MaWNAi75V1SXMv3s,3150
7
+ zsynctech_studio_sdk/utils.py,sha256=uKgNffummtFJYnnozl9l4YRq9ViE9XkVFgU0KJLCdYc,664
8
+ zsynctech_studio_sdk/enums/__init__.py,sha256=bM5eghvamVTAvFR2RSF8h-zQrKPbs1oTIklpcRuNPtw,171
9
+ zsynctech_studio_sdk/enums/execution.py,sha256=DTKys4F3oXOxEZBef01gsgJYYea6t3Zfp0gLQ6_wJlo,267
10
+ zsynctech_studio_sdk/enums/step.py,sha256=gw1PoRk8oNZfDdwMKNAjYvQFRuUsZQSaueF0qpEGn0g,152
11
+ zsynctech_studio_sdk/enums/task.py,sha256=EaqTKCuQBRQ5P7R1U6ppa9Hcy8_d_KKB_ylsNoYteWQ,194
12
+ zsynctech_studio_sdk/models/__init__.py,sha256=oQfAKeDbTwvJXRsiBQ096v3AdqTjxCaat798vQfN3RM,206
13
+ zsynctech_studio_sdk/models/base.py,sha256=NTy3NVJaEt4Xhp0XXIXCRBPDLs_7AsyQVqvux_cyVrc,1615
14
+ zsynctech_studio_sdk/models/config.py,sha256=iCfwWji3rXTcZR2g5bMa0Ar0k5aG6jCy6DSLb1zv9ds,857
15
+ zsynctech_studio_sdk/models/execution.py,sha256=VAFkGxOwVEjGq1oCoLqPSRAvLxadMoJXCld93kDue40,604
16
+ zsynctech_studio_sdk/models/step.py,sha256=qq9dO42wHvyP1ABpiEjhLzuKb9ExclSX73ota4zycfA,931
17
+ zsynctech_studio_sdk/models/task.py,sha256=MNrbAovV-tRwoyrIUtfKgnchr6SZPtGd74FfRVoLHQU,973
18
+ zsynctech_studio_sdk-0.1.0.dist-info/METADATA,sha256=-a2hSnb0vaNecMxJy9zrQTy8ChUpWDHPzht2GzllVf4,8042
19
+ zsynctech_studio_sdk-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ zsynctech_studio_sdk-0.1.0.dist-info/entry_points.txt,sha256=I8nmQxT0vbevct2PeNvBGQOXZ5rAQqqFvZbRMihwrOM,69
21
+ zsynctech_studio_sdk-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,2 @@
1
+ [console_scripts]
2
+ zsynctech-studio-sdk = zsynctech_studio_sdk:master