zsynctech-studio-sdk 0.1.1__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.
- zsynctech_studio_sdk/__init__.py +0 -0
- zsynctech_studio_sdk/client.py +54 -0
- zsynctech_studio_sdk/common/exceptions.py +26 -0
- zsynctech_studio_sdk/context.py +4 -0
- zsynctech_studio_sdk/enums/__init__.py +11 -0
- zsynctech_studio_sdk/enums/execution.py +11 -0
- zsynctech_studio_sdk/enums/operations.py +6 -0
- zsynctech_studio_sdk/enums/step.py +8 -0
- zsynctech_studio_sdk/enums/task.py +9 -0
- zsynctech_studio_sdk/execution.py +84 -0
- zsynctech_studio_sdk/loggers.py +56 -0
- zsynctech_studio_sdk/models/__init__.py +12 -0
- zsynctech_studio_sdk/models/config.py +52 -0
- zsynctech_studio_sdk/models/execution.py +102 -0
- zsynctech_studio_sdk/models/step.py +173 -0
- zsynctech_studio_sdk/models/task.py +169 -0
- zsynctech_studio_sdk/start.py +83 -0
- zsynctech_studio_sdk/task.py +0 -0
- zsynctech_studio_sdk/utils.py +40 -0
- zsynctech_studio_sdk-0.1.1.dist-info/METADATA +15 -0
- zsynctech_studio_sdk-0.1.1.dist-info/RECORD +22 -0
- zsynctech_studio_sdk-0.1.1.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from httpx import Client, Response
|
|
2
|
+
|
|
3
|
+
_instance_id = None
|
|
4
|
+
_secret_key = None
|
|
5
|
+
_client = None
|
|
6
|
+
_server = None
|
|
7
|
+
_gateway = "automation-gateway"
|
|
8
|
+
|
|
9
|
+
def set_credentials(
|
|
10
|
+
instance_id: str,
|
|
11
|
+
server: str,
|
|
12
|
+
secret_key: str
|
|
13
|
+
):
|
|
14
|
+
global _instance_id, _client, _server, _secret_key, _gateway
|
|
15
|
+
|
|
16
|
+
_secret_key = secret_key
|
|
17
|
+
_instance_id = instance_id
|
|
18
|
+
_server = str(server).rstrip("/")
|
|
19
|
+
base_url = f"{_server}/{_gateway}/{_instance_id}"
|
|
20
|
+
|
|
21
|
+
_client = Client(
|
|
22
|
+
base_url=base_url,
|
|
23
|
+
headers={
|
|
24
|
+
"Authorization": f"Bearer {_secret_key}",
|
|
25
|
+
"Content-Type": "application/json"
|
|
26
|
+
},
|
|
27
|
+
timeout=10
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def request(method: str, endpoint: str, **kwargs):
|
|
32
|
+
if _client is None:
|
|
33
|
+
raise RuntimeError("Credentials not set. Call set_credentials() first.")
|
|
34
|
+
|
|
35
|
+
response = _client.request(method, endpoint, **kwargs)
|
|
36
|
+
response.raise_for_status()
|
|
37
|
+
|
|
38
|
+
return response
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get(endpoint: str, params: dict = None) -> Response:
|
|
42
|
+
return request("GET", endpoint, params=params)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def post(endpoint: str, json: dict = None) -> Response:
|
|
46
|
+
return request("POST", endpoint, json=json)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def put(endpoint: str, json: dict = None) -> Response:
|
|
50
|
+
return request("PUT", endpoint, json=json)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def patch(endpoint: str, json: dict = None) -> Response:
|
|
54
|
+
return request("PATCH", endpoint, json=json)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class ExecutionError(Exception):
|
|
2
|
+
"""Exceção base para erros relacionados à execução."""
|
|
3
|
+
|
|
4
|
+
class ExecutionAlreadyFinishedError(ExecutionError):
|
|
5
|
+
"""Lançada quando uma operação é chamada em uma execução já finalizada."""
|
|
6
|
+
|
|
7
|
+
class ExecutionFieldError(ExecutionError):
|
|
8
|
+
"""Lançada quando o campo informado não existe no ExecutionModel."""
|
|
9
|
+
|
|
10
|
+
class ExecutionUpdateError(ExecutionError):
|
|
11
|
+
"""Lançada quando ocorre falha ao atualizar a execução no servidor."""
|
|
12
|
+
|
|
13
|
+
class ExecutionNotStardedError(ExecutionError):
|
|
14
|
+
"""Lançada quando ocorre falha ao atualizar a execução no servidor."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TaskError(Exception):
|
|
18
|
+
"""Exceção base para erros relacionados à task."""
|
|
19
|
+
|
|
20
|
+
class TaskUpdateError(TaskError):
|
|
21
|
+
"""Lançada quando ocorre falha ao atualizar a execução no servidor."""
|
|
22
|
+
|
|
23
|
+
class TaksNotStardedError(TaskError):
|
|
24
|
+
"""Lançada quando ocorre falha ao atualizar a execução no servidor."""
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from zsynctech_studio_sdk.models.execution import ExecutionModel, ExecutionStatus
|
|
2
|
+
from zsynctech_studio_sdk.common.exceptions import ExecutionUpdateError
|
|
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
|
+
|
|
8
|
+
EXECUTION_STATUS_COMPLETED = [
|
|
9
|
+
ExecutionStatus.OUT_OF_OPERATING_HOURS,
|
|
10
|
+
ExecutionStatus.INTERRUPTED,
|
|
11
|
+
ExecutionStatus.FINISHED,
|
|
12
|
+
ExecutionStatus.ERROR,
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Execution:
|
|
17
|
+
def __init__(self, execution_id: Optional[str] = None):
|
|
18
|
+
self.__execution_id = execution_id
|
|
19
|
+
self.__execution: ExecutionModel = ExecutionModel()
|
|
20
|
+
|
|
21
|
+
if self.execution_id:
|
|
22
|
+
self.execution.id = self.execution_id
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def execution(self) -> ExecutionModel:
|
|
26
|
+
return self.__execution
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def execution_id(self) -> str:
|
|
30
|
+
return self.__execution_id
|
|
31
|
+
|
|
32
|
+
@execution_id.setter
|
|
33
|
+
def execution_id(self, value: str):
|
|
34
|
+
self.__execution_id = value
|
|
35
|
+
|
|
36
|
+
def _update(
|
|
37
|
+
self,
|
|
38
|
+
status: Optional[ExecutionStatus] = None,
|
|
39
|
+
observation: Optional[str] = None,
|
|
40
|
+
end_date: Optional[str] = None,
|
|
41
|
+
total_task_count: Optional[int] = None,
|
|
42
|
+
current_task_count: Optional[int] = None,
|
|
43
|
+
) -> dict:
|
|
44
|
+
|
|
45
|
+
self.execution.status = status or self.execution.status
|
|
46
|
+
self.execution.endDate = end_date or self.execution.endDate
|
|
47
|
+
self.execution.observation = observation or self.execution.observation
|
|
48
|
+
self.execution.totalTaskCount = total_task_count or self.execution.totalTaskCount
|
|
49
|
+
self.execution.currentTaskCount = current_task_count or self.execution.currentTaskCount
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = client.patch(
|
|
53
|
+
endpoint=f"{self.execution_id}/executions",
|
|
54
|
+
json=self.execution.model_dump()
|
|
55
|
+
)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise ExecutionUpdateError(f"Erro ao atualizar execução: {str(e)}") from e
|
|
59
|
+
|
|
60
|
+
return self.execution.model_dump()
|
|
61
|
+
|
|
62
|
+
def set_total_task_count(self, total_task_count: int) -> dict[str, Any]:
|
|
63
|
+
return self._update(total_task_count=total_task_count)
|
|
64
|
+
|
|
65
|
+
def update_current_task_count(self, current_task_count: int) -> dict[str, Any]:
|
|
66
|
+
return self._update(current_task_count=current_task_count)
|
|
67
|
+
|
|
68
|
+
def update_observation(self, observation: str) -> dict[str, Any]:
|
|
69
|
+
return self._update(observation=observation)
|
|
70
|
+
|
|
71
|
+
def start(self, observation: Optional[str] = None) -> dict:
|
|
72
|
+
return self._update(ExecutionStatus.RUNNING, observation)
|
|
73
|
+
|
|
74
|
+
def error(self, observation: Optional[str] = None) -> dict:
|
|
75
|
+
return self._update(ExecutionStatus.ERROR, observation=observation, end_date=get_utc_now())
|
|
76
|
+
|
|
77
|
+
def out_of_operating_hours(self, observation: Optional[str] = None) -> dict:
|
|
78
|
+
return self._update(ExecutionStatus.OUT_OF_OPERATING_HOURS, observation=observation, end_date=get_utc_now())
|
|
79
|
+
|
|
80
|
+
def finished(self, observation: Optional[str] = None) -> dict:
|
|
81
|
+
return self._update(ExecutionStatus.FINISHED, observation=observation, end_date=get_utc_now())
|
|
82
|
+
|
|
83
|
+
def interrupted(self, observation: Optional[str] = None) -> dict:
|
|
84
|
+
return self._update(ExecutionStatus.INTERRUPTED, observation=observation, end_date=get_utc_now())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.config import dictConfig
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
LOGGING_CONFIG: dict[str, Any] = {
|
|
7
|
+
"version": 1,
|
|
8
|
+
"disable_existing_loggers": False,
|
|
9
|
+
"formatters": {
|
|
10
|
+
"file": {
|
|
11
|
+
"format": "%(asctime)s %(levelname)s [module:(%(module)s) line:%(lineno)d]: %(message)s",
|
|
12
|
+
"datefmt": "%d-%m-%Y %H:%M"
|
|
13
|
+
},
|
|
14
|
+
"console": {
|
|
15
|
+
"format": "[module:(%(module)s) %(lineno)d]: %(message)s",
|
|
16
|
+
"datefmt": "[%X]"
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"handlers": {
|
|
20
|
+
"console": {
|
|
21
|
+
"()": "rich.logging.RichHandler",
|
|
22
|
+
"formatter": "console",
|
|
23
|
+
"rich_tracebacks": False,
|
|
24
|
+
"tracebacks_show_locals": False,
|
|
25
|
+
"show_time": True,
|
|
26
|
+
"show_level": True,
|
|
27
|
+
"omit_repeated_times": False,
|
|
28
|
+
"markup": False,
|
|
29
|
+
"enable_link_path": True,
|
|
30
|
+
"show_path": True,
|
|
31
|
+
},
|
|
32
|
+
"file": {
|
|
33
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
34
|
+
"formatter": "file",
|
|
35
|
+
"filename": "log.log",
|
|
36
|
+
"maxBytes": 1024 * 1024 * 5,
|
|
37
|
+
"backupCount": 5,
|
|
38
|
+
"encoding": "utf-8",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
"root": {
|
|
42
|
+
"handlers": [
|
|
43
|
+
"console",
|
|
44
|
+
"file"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"loggers": {
|
|
48
|
+
"zsynctech-studio-sdk": {
|
|
49
|
+
"level": "DEBUG"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
dictConfig(LOGGING_CONFIG)
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger("zsynctech-studio-sdk")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .execution import ExecutionModel, ExecutionStatus
|
|
2
|
+
from .step import StepModel, StepStatus
|
|
3
|
+
from .task import TaskModel, TaskStatus
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"ExecutionModel",
|
|
7
|
+
"ExecutionStatus",
|
|
8
|
+
"StepModel",
|
|
9
|
+
"StepStatus",
|
|
10
|
+
"TaskModel",
|
|
11
|
+
"TaskStatus"
|
|
12
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from uuid_extensions import uuid7
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InputOutputTypes(StrEnum):
|
|
8
|
+
FTP = 'FTP'
|
|
9
|
+
API = 'API'
|
|
10
|
+
FILA = 'FILA'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Credential(BaseModel):
|
|
14
|
+
key: str
|
|
15
|
+
value: str
|
|
16
|
+
encrypted: bool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Config(BaseModel):
|
|
20
|
+
instanceId: str
|
|
21
|
+
automationName: Optional[str] = "System"
|
|
22
|
+
clientId: Optional[str] = None
|
|
23
|
+
userId: Optional[str] = "System"
|
|
24
|
+
executionId: Optional[str] = str(uuid7())
|
|
25
|
+
outputPath: Optional[str] = None
|
|
26
|
+
inputPath: Optional[str] = None
|
|
27
|
+
inputMetaData: Optional[dict] = None
|
|
28
|
+
inputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
|
|
29
|
+
outputType: Optional[InputOutputTypes] = InputOutputTypes.FTP
|
|
30
|
+
outputMetaData: Optional[dict] = None
|
|
31
|
+
keepAlive: Optional[bool] = False
|
|
32
|
+
keepAliveInterval: Optional[int] = 30
|
|
33
|
+
credentials: Optional[List[Credential]] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
print(Config(
|
|
38
|
+
instanceId='0198cdc7-6e20-74b9-89f8-2a6e975da3db',
|
|
39
|
+
automationName='Robô Benner Eventos',
|
|
40
|
+
clientId='0198c254-dec1-7bc2-9ed2-33f046155d13',
|
|
41
|
+
userId='0198c254-df28-7074-9b48-2567993dcf12',
|
|
42
|
+
executionId='0198d8d5-548f-7c00-baba-12f514b6419a',
|
|
43
|
+
inputPath='/benner/eventos/input',
|
|
44
|
+
inputMetaData=None,
|
|
45
|
+
inputType='FTP',
|
|
46
|
+
outputPath='/benner/eventos/output',
|
|
47
|
+
outputMetaData=None,
|
|
48
|
+
outputType='FTP',
|
|
49
|
+
keepAlive=False,
|
|
50
|
+
keepAliveInterval=None,
|
|
51
|
+
credentials=None
|
|
52
|
+
).model_dump())
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from pydantic import Field, field_validator, model_validator, BaseModel
|
|
2
|
+
from zsynctech_studio_sdk.enums.execution import ExecutionStatus
|
|
3
|
+
from uuid_extensions.uuid7 import uuid7
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ExecutionModel(BaseModel):
|
|
10
|
+
id: str = Field(
|
|
11
|
+
default_factory=lambda: str(uuid7()),
|
|
12
|
+
min_length=1,
|
|
13
|
+
description="ID único da execução"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
observation: Optional[str] = Field(
|
|
17
|
+
default=None,
|
|
18
|
+
description="Observação sobre a execução"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
status: Optional[ExecutionStatus] = Field(
|
|
22
|
+
default=ExecutionStatus.WAITING,
|
|
23
|
+
description="Status atual da execução"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
endDate: Optional[str] = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Data de término no formato ISO 8601 com Z"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
totalTaskCount: Optional[int] = Field(
|
|
32
|
+
ge=0,
|
|
33
|
+
default=0,
|
|
34
|
+
description="Número total de tarefas"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
currentTaskCount: Optional[int] = Field(
|
|
38
|
+
ge=0,
|
|
39
|
+
default=0,
|
|
40
|
+
description="Número de tarefas executadas"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@field_validator('endDate')
|
|
44
|
+
@classmethod
|
|
45
|
+
def validate_end_date_format(cls, v):
|
|
46
|
+
"""Valida o formato da data de término"""
|
|
47
|
+
if v is None:
|
|
48
|
+
return v
|
|
49
|
+
|
|
50
|
+
# Regex para formato ISO 8601 com Z no final
|
|
51
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
52
|
+
if not re.match(pattern, v):
|
|
53
|
+
raise ValueError('endDate deve estar no formato ISO 8601 com Z (ex: 2024-01-15T10:30:00.000Z)')
|
|
54
|
+
|
|
55
|
+
# Valida se é uma data válida
|
|
56
|
+
try:
|
|
57
|
+
datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
58
|
+
except ValueError:
|
|
59
|
+
raise ValueError('endDate deve ser uma data válida')
|
|
60
|
+
|
|
61
|
+
return v
|
|
62
|
+
|
|
63
|
+
@field_validator('id')
|
|
64
|
+
@classmethod
|
|
65
|
+
def validate_id_format(cls, v):
|
|
66
|
+
"""Valida se o ID é um UUID7 válido"""
|
|
67
|
+
# Verifica se é um UUID válido
|
|
68
|
+
|
|
69
|
+
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):
|
|
70
|
+
raise ValueError('ID deve ser um UUID válido')
|
|
71
|
+
|
|
72
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
73
|
+
version = int(v[14], 16)
|
|
74
|
+
if version != 7:
|
|
75
|
+
raise ValueError('ID deve ser um UUID7 válido')
|
|
76
|
+
|
|
77
|
+
return v
|
|
78
|
+
|
|
79
|
+
@model_validator(mode='after')
|
|
80
|
+
def validate_business_rules(self):
|
|
81
|
+
"""Valida regras de negócio que dependem de múltiplos campos"""
|
|
82
|
+
# Valida se current_task_count não excede total_task_count
|
|
83
|
+
current = self.currentTaskCount or 0
|
|
84
|
+
total = self.totalTaskCount or 0
|
|
85
|
+
if current > total:
|
|
86
|
+
raise ValueError('currentTaskCount não pode ser maior que totalTaskCount')
|
|
87
|
+
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
class Config:
|
|
91
|
+
extra = "forbid"
|
|
92
|
+
validate_assignment = True
|
|
93
|
+
json_schema_extra = {
|
|
94
|
+
"example": {
|
|
95
|
+
"id": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
96
|
+
"observation": "Execução de teste bem-sucedida",
|
|
97
|
+
"status": "FINISHED",
|
|
98
|
+
"endDate": "2024-01-15T10:30:00.000Z",
|
|
99
|
+
"totalTaskCount": 10,
|
|
100
|
+
"currentTaskCount": 10
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from zsynctech_studio_sdk.enums.operations import Operations
|
|
2
|
+
from pydantic import Field, field_validator, BaseModel
|
|
3
|
+
from zsynctech_studio_sdk.enums.step import StepStatus
|
|
4
|
+
from zsynctech_studio_sdk.utils import get_utc_now
|
|
5
|
+
from uuid_extensions.uuid7 import uuid7
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StepModel(BaseModel):
|
|
12
|
+
id: str = Field(
|
|
13
|
+
default_factory=lambda: str(uuid7()),
|
|
14
|
+
min_length=1,
|
|
15
|
+
description="ID único do step"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
observation: Optional[str] = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Observação sobre o step"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
operation: Operations = Field(
|
|
24
|
+
default=Operations.CREATE,
|
|
25
|
+
description="Operação a ser realizada no step"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
taskId: Optional[str] = Field(
|
|
29
|
+
min_length=1,
|
|
30
|
+
default=None,
|
|
31
|
+
description="ID da tarefa a ser realizada no step"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
automationOnClientId: Optional[str] = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
min_length=1,
|
|
37
|
+
description="ID do cliente da automação"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
status: StepStatus = Field(
|
|
41
|
+
default=StepStatus.UNPROCESSED,
|
|
42
|
+
description="Status do step"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
stepCode: Optional[str] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
min_length=1,
|
|
48
|
+
description="Código do step"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
startDate: Optional[str] = Field(
|
|
52
|
+
default=get_utc_now(),
|
|
53
|
+
description="Data de término do step"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
endDate: Optional[str] = Field(
|
|
57
|
+
default=None,
|
|
58
|
+
description="Data de término do step"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@field_validator('startDate')
|
|
62
|
+
@classmethod
|
|
63
|
+
def validate_start_date_format(cls, v):
|
|
64
|
+
"""Valida o formato da data de término"""
|
|
65
|
+
if v is None:
|
|
66
|
+
return v
|
|
67
|
+
|
|
68
|
+
# Regex para formato ISO 8601 com Z no final
|
|
69
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
70
|
+
if not re.match(pattern, v):
|
|
71
|
+
raise ValueError('endDate deve estar no formato ISO 8601 com Z (ex: 2024-01-15T10:30:00.000Z)')
|
|
72
|
+
|
|
73
|
+
# Valida se é uma data válida
|
|
74
|
+
try:
|
|
75
|
+
datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
76
|
+
except ValueError:
|
|
77
|
+
raise ValueError('end_date deve ser uma data válida')
|
|
78
|
+
|
|
79
|
+
return v
|
|
80
|
+
|
|
81
|
+
@field_validator('endDate')
|
|
82
|
+
@classmethod
|
|
83
|
+
def validate_end_date_format(cls, v):
|
|
84
|
+
"""Valida o formato da data de término"""
|
|
85
|
+
if v is None:
|
|
86
|
+
return v
|
|
87
|
+
|
|
88
|
+
# Regex para formato ISO 8601 com Z no final
|
|
89
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
90
|
+
if not re.match(pattern, v):
|
|
91
|
+
raise ValueError('endDate deve estar no formato ISO 8601 com Z (ex: 2024-01-15T10:30:00.000Z)')
|
|
92
|
+
|
|
93
|
+
# Valida se é uma data válida
|
|
94
|
+
try:
|
|
95
|
+
datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
96
|
+
except ValueError:
|
|
97
|
+
raise ValueError('end_date deve ser uma data válida')
|
|
98
|
+
|
|
99
|
+
return v
|
|
100
|
+
|
|
101
|
+
@field_validator('status')
|
|
102
|
+
@classmethod
|
|
103
|
+
def validate_status(cls, v):
|
|
104
|
+
"""Valida se o status é um status válido"""
|
|
105
|
+
if v not in StepStatus:
|
|
106
|
+
raise ValueError('status deve ser um status válido')
|
|
107
|
+
return v
|
|
108
|
+
|
|
109
|
+
@field_validator('id')
|
|
110
|
+
@classmethod
|
|
111
|
+
def validate_id_format(cls, v):
|
|
112
|
+
"""Valida se o ID é um UUID7 válido"""
|
|
113
|
+
# Verifica se é um UUID válido
|
|
114
|
+
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):
|
|
115
|
+
raise ValueError('ID deve ser um UUID válido')
|
|
116
|
+
|
|
117
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
118
|
+
version = int(v[14], 16)
|
|
119
|
+
if version != 7:
|
|
120
|
+
raise ValueError('ID deve ser um UUID7 válido')
|
|
121
|
+
|
|
122
|
+
return v
|
|
123
|
+
|
|
124
|
+
@field_validator('taskId')
|
|
125
|
+
@classmethod
|
|
126
|
+
def validate_task_id_format(cls, v):
|
|
127
|
+
"""Valida se o task_id é um UUID7 válido"""
|
|
128
|
+
# Verifica se é um UUID válido
|
|
129
|
+
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):
|
|
130
|
+
raise ValueError('taskId deve ser um UUID válido')
|
|
131
|
+
|
|
132
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
133
|
+
version = int(v[14], 16)
|
|
134
|
+
if version != 7:
|
|
135
|
+
raise ValueError('taskId deve ser um UUID7 válido')
|
|
136
|
+
|
|
137
|
+
return v
|
|
138
|
+
|
|
139
|
+
@field_validator('automationOnClientId')
|
|
140
|
+
@classmethod
|
|
141
|
+
def validate_automation_on_cliente_id_format(cls, v):
|
|
142
|
+
"""Valida se o automationOnClientId é um UUID7 válido"""
|
|
143
|
+
if v is None:
|
|
144
|
+
return v
|
|
145
|
+
|
|
146
|
+
# Verifica se é um UUID válido
|
|
147
|
+
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):
|
|
148
|
+
raise ValueError('automationOnClientId deve ser um UUID válido')
|
|
149
|
+
|
|
150
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
151
|
+
version = int(v[14], 16)
|
|
152
|
+
if version != 7:
|
|
153
|
+
raise ValueError('automationOnClientId deve ser um UUID7 válido')
|
|
154
|
+
|
|
155
|
+
return v
|
|
156
|
+
|
|
157
|
+
class Config:
|
|
158
|
+
extra = "forbid"
|
|
159
|
+
validate_assignment = True
|
|
160
|
+
json_schema_extra = {
|
|
161
|
+
"example": {
|
|
162
|
+
"id": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
163
|
+
"observation": "Execução bem-sucedida",
|
|
164
|
+
"operation": "CREATE",
|
|
165
|
+
"taskId": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
166
|
+
"automationOnClienteId": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
167
|
+
"status": "SUCCESS",
|
|
168
|
+
"stepCode": "0001",
|
|
169
|
+
"endDate": "2024-01-15T10:30:00.000Z"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from zsynctech_studio_sdk.enums.operations import Operations
|
|
2
|
+
from pydantic import Field, field_validator, BaseModel
|
|
3
|
+
from zsynctech_studio_sdk.enums.task import TaskStatus
|
|
4
|
+
from zsynctech_studio_sdk.utils import get_utc_now
|
|
5
|
+
from uuid_extensions.uuid7 import uuid7
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskModel(BaseModel):
|
|
12
|
+
id: str = Field(
|
|
13
|
+
default_factory=lambda: str(uuid7()),
|
|
14
|
+
min_length=1,
|
|
15
|
+
description="ID único da task"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
operation: Operations = Field(
|
|
19
|
+
default=Operations.CREATE,
|
|
20
|
+
description="Operação a ser realizada na task"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
description: Optional[str] = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
max_length=60,
|
|
26
|
+
description="Descrição da task"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
jsonData: Optional[dict] = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="Dados JSON da task"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
observation: Optional[str] = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="Observação sobre a task"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
code: Optional[str] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Código da task"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
executionId: Optional[str] = Field(
|
|
45
|
+
min_length=1,
|
|
46
|
+
default=None,
|
|
47
|
+
description="ID da execução"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
status: TaskStatus = Field(
|
|
51
|
+
default=TaskStatus.UNPROCESSED,
|
|
52
|
+
description="Status da task"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
startDate: Optional[str] = Field(
|
|
56
|
+
default=get_utc_now(),
|
|
57
|
+
description="Data de início da task"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
endDate: Optional[str] = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
description="Data de término da task"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@field_validator('startDate')
|
|
66
|
+
@classmethod
|
|
67
|
+
def validate_start_date_format(cls, v):
|
|
68
|
+
"""Valida o formato da data de término"""
|
|
69
|
+
if v is None:
|
|
70
|
+
return v
|
|
71
|
+
|
|
72
|
+
# Regex para formato ISO 8601 com Z no final
|
|
73
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
74
|
+
if not re.match(pattern, v):
|
|
75
|
+
raise ValueError('endDate deve estar no formato ISO 8601 com Z (ex: 2024-01-15T10:30:00.000Z)')
|
|
76
|
+
|
|
77
|
+
# Valida se é uma data válida
|
|
78
|
+
try:
|
|
79
|
+
datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
80
|
+
except ValueError:
|
|
81
|
+
raise ValueError('end_date deve ser uma data válida')
|
|
82
|
+
|
|
83
|
+
return v
|
|
84
|
+
|
|
85
|
+
@field_validator('endDate')
|
|
86
|
+
@classmethod
|
|
87
|
+
def validate_end_date_format(cls, v):
|
|
88
|
+
"""Valida o formato da data de término"""
|
|
89
|
+
if v is None:
|
|
90
|
+
return v
|
|
91
|
+
|
|
92
|
+
# Regex para formato ISO 8601 com Z no final
|
|
93
|
+
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
94
|
+
if not re.match(pattern, v):
|
|
95
|
+
raise ValueError('endDate deve estar no formato ISO 8601 com Z (ex: 2024-01-15T10:30:00.000Z)')
|
|
96
|
+
|
|
97
|
+
# Valida se é uma data válida
|
|
98
|
+
try:
|
|
99
|
+
datetime.fromisoformat(v.replace('Z', '+00:00'))
|
|
100
|
+
except ValueError:
|
|
101
|
+
raise ValueError('end_date deve ser uma data válida')
|
|
102
|
+
|
|
103
|
+
return v
|
|
104
|
+
|
|
105
|
+
@field_validator('status')
|
|
106
|
+
@classmethod
|
|
107
|
+
def validate_status(cls, v):
|
|
108
|
+
"""Valida se o status é um status válido"""
|
|
109
|
+
if v not in TaskStatus:
|
|
110
|
+
raise ValueError('status deve ser um status válido')
|
|
111
|
+
return v
|
|
112
|
+
|
|
113
|
+
@field_validator('id')
|
|
114
|
+
@classmethod
|
|
115
|
+
def validate_id_format(cls, v):
|
|
116
|
+
"""Valida se o ID é um UUID7 válido"""
|
|
117
|
+
# Verifica se é um UUID válido
|
|
118
|
+
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):
|
|
119
|
+
raise ValueError('ID deve ser um UUID válido')
|
|
120
|
+
|
|
121
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
122
|
+
version = int(v[14], 16)
|
|
123
|
+
if version != 7:
|
|
124
|
+
raise ValueError('ID deve ser um UUID7 válido')
|
|
125
|
+
|
|
126
|
+
return v
|
|
127
|
+
|
|
128
|
+
@field_validator('executionId')
|
|
129
|
+
@classmethod
|
|
130
|
+
def validate_executionid_format(cls, v):
|
|
131
|
+
"""Valida se o executionId é um UUID7 válido"""
|
|
132
|
+
# Verifica se é um UUID válido
|
|
133
|
+
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):
|
|
134
|
+
raise ValueError('executionId deve ser um UUID válido')
|
|
135
|
+
|
|
136
|
+
# Verifica se é um UUID7 (primeiro dígito da versão deve ser 7)
|
|
137
|
+
version = int(v[14], 16)
|
|
138
|
+
if version != 7:
|
|
139
|
+
raise ValueError('executionId deve ser um UUID7 válido')
|
|
140
|
+
|
|
141
|
+
return v
|
|
142
|
+
|
|
143
|
+
@field_validator('description')
|
|
144
|
+
@classmethod
|
|
145
|
+
def validate_description_length(cls, v):
|
|
146
|
+
"""Valida se a descrição tem no máximo 60 caracteres"""
|
|
147
|
+
|
|
148
|
+
if v is None:
|
|
149
|
+
return v
|
|
150
|
+
|
|
151
|
+
if len(v) > 60:
|
|
152
|
+
raise ValueError('description deve ter no máximo 60 caracteres')
|
|
153
|
+
return v
|
|
154
|
+
|
|
155
|
+
class Config:
|
|
156
|
+
extra = "forbid"
|
|
157
|
+
validate_assignment = True
|
|
158
|
+
json_schema_extra = {
|
|
159
|
+
"example": {
|
|
160
|
+
"id": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
161
|
+
"operation": "CREATE",
|
|
162
|
+
"description": "Processar dados do cliente",
|
|
163
|
+
"jsonData": {"clientId": "12345", "action": "process"},
|
|
164
|
+
"observation": "Task executada com sucesso",
|
|
165
|
+
"executionId": "0685b254-3a1f-7bf6-8000-3a0ce7a0a52f",
|
|
166
|
+
"status": "SUCCESS",
|
|
167
|
+
"endDate": "2024-01-15T10:30:00.000Z"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from zsynctech_studio_sdk.utils import wait_until_file_stable
|
|
2
|
+
from watchdog.events import PatternMatchingEventHandler
|
|
3
|
+
from zsynctech_studio_sdk.loggers import logger
|
|
4
|
+
from watchdog.observers import Observer
|
|
5
|
+
from zsynctech_studio_sdk import client
|
|
6
|
+
from appdirs import AppDirs
|
|
7
|
+
from queue import Queue
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
APPS_DIR = AppDirs()
|
|
13
|
+
SDK_DIR = os.path.join(APPS_DIR.user_data_dir, "zsynctech")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StartEventHandler:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.observer = Observer()
|
|
19
|
+
self._recursive = False
|
|
20
|
+
self.queue = Queue()
|
|
21
|
+
|
|
22
|
+
if client._instance_id is None:
|
|
23
|
+
raise RuntimeError("Credentials not set. Call set_credentials() first.")
|
|
24
|
+
|
|
25
|
+
self.sdk_path = os.path.join(SDK_DIR, client._instance_id)
|
|
26
|
+
os.makedirs(self.sdk_path, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def recursive(self):
|
|
30
|
+
return self._recursive
|
|
31
|
+
|
|
32
|
+
class JSONHandler(PatternMatchingEventHandler):
|
|
33
|
+
def __init__(self, queue):
|
|
34
|
+
super().__init__(patterns=["*.json"], ignore_directories=True)
|
|
35
|
+
self.queue = queue
|
|
36
|
+
self._processed_files = {}
|
|
37
|
+
|
|
38
|
+
def _load_json(self, src_path):
|
|
39
|
+
if wait_until_file_stable(src_path, check_interval=0.5, timeout=10):
|
|
40
|
+
try:
|
|
41
|
+
with open(src_path, "r", encoding="utf-8") as f:
|
|
42
|
+
return json.load(f)
|
|
43
|
+
except Exception:
|
|
44
|
+
logger.exception("An error occurred while loading the file", stack_info=True)
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def _should_process(self, src_path):
|
|
48
|
+
try:
|
|
49
|
+
mtime = os.path.getmtime(src_path)
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
return False
|
|
52
|
+
last_mtime = self._processed_files.get(src_path, 0)
|
|
53
|
+
if mtime > last_mtime:
|
|
54
|
+
self._processed_files[src_path] = mtime
|
|
55
|
+
return True
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def on_modified(self, event):
|
|
59
|
+
if self._should_process(event.src_path):
|
|
60
|
+
data = self._load_json(event.src_path)
|
|
61
|
+
if data:
|
|
62
|
+
self.queue.put(data)
|
|
63
|
+
|
|
64
|
+
def __start_observer(self):
|
|
65
|
+
event_handler = self.JSONHandler(self.queue)
|
|
66
|
+
self.observer.schedule(event_handler, self.sdk_path, recursive=self.recursive)
|
|
67
|
+
self.observer.start()
|
|
68
|
+
logger.info(f"Aguardando eventos em: {self.sdk_path}")
|
|
69
|
+
|
|
70
|
+
def listen_events(self, timeout=None):
|
|
71
|
+
self.__start_observer()
|
|
72
|
+
try:
|
|
73
|
+
while True:
|
|
74
|
+
try:
|
|
75
|
+
data = self.queue.get(timeout=timeout)
|
|
76
|
+
yield data
|
|
77
|
+
except KeyboardInterrupt:
|
|
78
|
+
break
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
finally:
|
|
82
|
+
self.observer.stop()
|
|
83
|
+
self.observer.join()
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def wait_until_file_stable(path, check_interval=0.5, timeout=10):
|
|
7
|
+
"""
|
|
8
|
+
Aguarda até que o arquivo 'path' não mude de tamanho por 'check_interval' segundos.
|
|
9
|
+
Retorna True se o arquivo estabilizar, False se estourar o timeout.
|
|
10
|
+
"""
|
|
11
|
+
start_time = time.time()
|
|
12
|
+
|
|
13
|
+
if not os.path.exists(path):
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
last_size = -1
|
|
17
|
+
while True:
|
|
18
|
+
try:
|
|
19
|
+
current_size = os.path.getsize(path)
|
|
20
|
+
except FileNotFoundError:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
if current_size == last_size and current_size > 0:
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
last_size = current_size
|
|
27
|
+
time.sleep(check_interval)
|
|
28
|
+
|
|
29
|
+
if time.time() - start_time > timeout:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_utc_now() -> str:
|
|
34
|
+
"""
|
|
35
|
+
Get the current date and time in UTC format as an ISO string.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
str: Current UTC datetime in ISO format with 'Z' suffix (e.g., '2024-01-15T10:30:45.123Z')
|
|
39
|
+
"""
|
|
40
|
+
return datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zsynctech-studio-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: SDK Python para integração com plataforma ZSync Studio
|
|
5
|
+
Author-email: Rodrigo Zavan <rodrigo.zavan@zsynctech.com>
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: appdirs>=1.4.4
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: playwright>=1.54.0
|
|
10
|
+
Requires-Dist: pydantic>=2.11.7
|
|
11
|
+
Requires-Dist: uuid7>=0.1.0
|
|
12
|
+
Requires-Dist: watchdog>=6.0.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
zsynctech_studio_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
zsynctech_studio_sdk/client.py,sha256=1Y5X1LxFWsC6oy1rhX8pynbngMfd9ng01Li1MdMNAAk,1401
|
|
3
|
+
zsynctech_studio_sdk/context.py,sha256=_ffe036XK2esyMMVI8P-ZcagJYLn8wi5T6vfbm9P3jc,70
|
|
4
|
+
zsynctech_studio_sdk/execution.py,sha256=o72kqf9IrQID4gKlf9H9JBpwLMFey2-BosYadgh8lBk,3413
|
|
5
|
+
zsynctech_studio_sdk/loggers.py,sha256=ZowaCfqcHLZsLi5j1mf1Tkjk8RoKJkz50SrDhFfMG0M,1517
|
|
6
|
+
zsynctech_studio_sdk/start.py,sha256=BMMZJqb2v2KG7_Sj6eT4iR1fPtx3Bhq6JLIt-0q4dTE,2895
|
|
7
|
+
zsynctech_studio_sdk/task.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
zsynctech_studio_sdk/utils.py,sha256=e2LOKQQVNTzsgUJy6nurG-15lhFA4T6ryazd0_H6aGM,1136
|
|
9
|
+
zsynctech_studio_sdk/common/exceptions.py,sha256=Aio_w8ZAb7V-LHuu73egk3sk-umbLaISMljXquNEjwQ,952
|
|
10
|
+
zsynctech_studio_sdk/enums/__init__.py,sha256=xaRgVmXIenVCybZCC7OvwJei00-cb0XJjqBpxy79Khw,234
|
|
11
|
+
zsynctech_studio_sdk/enums/execution.py,sha256=TVsUzRzdNXNj96pLzms3tskva01iwXx9fKJqj8xaoIQ,278
|
|
12
|
+
zsynctech_studio_sdk/enums/operations.py,sha256=MbE486aNRFCWqZj1gS61TIssCPfCG6ISdXvYaQ3wDcc,104
|
|
13
|
+
zsynctech_studio_sdk/enums/step.py,sha256=iHUWHdjqBZwP3YE_yZWpEkYKdVuwew-JwWMPO3kR9PQ,160
|
|
14
|
+
zsynctech_studio_sdk/enums/task.py,sha256=6PMjcQclVoZObjkGqjMh6mJVMcpzLXQ3dLWWkjMkg2o,203
|
|
15
|
+
zsynctech_studio_sdk/models/__init__.py,sha256=gPF2pDK84uJAzSHluOfzBrqpFuLV_GKM_PDB2pKsTiQ,276
|
|
16
|
+
zsynctech_studio_sdk/models/config.py,sha256=CeKmy7lvziuqgU35rM3WvU0v9aa5ybk3FQbJpm233QE,1589
|
|
17
|
+
zsynctech_studio_sdk/models/execution.py,sha256=Ig9Lsr9WsCeHeMvPihiOzABqzgeqzYQfxRJvOykOVtU,3302
|
|
18
|
+
zsynctech_studio_sdk/models/step.py,sha256=ya1tMr1iXt1DAGQ6Mk2RRnlzDL_ojzKKEGrRw4IjUwM,5677
|
|
19
|
+
zsynctech_studio_sdk/models/task.py,sha256=EgUmbuTRwEvv-w9qpes7YXDBmewpy_VfzSuXPauuA6o,5418
|
|
20
|
+
zsynctech_studio_sdk-0.1.1.dist-info/METADATA,sha256=AvidlA0lRH00PV3rwSp1OMSQCbOjSNpHA9FXu4vWikc,438
|
|
21
|
+
zsynctech_studio_sdk-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
zsynctech_studio_sdk-0.1.1.dist-info/RECORD,,
|