zsynctech-studio-sdk 0.1.1__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 (25) hide show
  1. zsynctech_studio_sdk-0.1.1/.gitignore +10 -0
  2. zsynctech_studio_sdk-0.1.1/.python-version +1 -0
  3. zsynctech_studio_sdk-0.1.1/PKG-INFO +15 -0
  4. zsynctech_studio_sdk-0.1.1/README.md +1 -0
  5. zsynctech_studio_sdk-0.1.1/pyproject.toml +21 -0
  6. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/__init__.py +0 -0
  7. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/client.py +54 -0
  8. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/common/exceptions.py +26 -0
  9. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/context.py +4 -0
  10. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/enums/__init__.py +11 -0
  11. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/enums/execution.py +11 -0
  12. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/enums/operations.py +6 -0
  13. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/enums/step.py +8 -0
  14. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/enums/task.py +9 -0
  15. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/execution.py +84 -0
  16. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/loggers.py +56 -0
  17. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/models/__init__.py +12 -0
  18. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/models/config.py +52 -0
  19. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/models/execution.py +102 -0
  20. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/models/step.py +173 -0
  21. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/models/task.py +169 -0
  22. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/start.py +83 -0
  23. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/task.py +0 -0
  24. zsynctech_studio_sdk-0.1.1/src/zsynctech_studio_sdk/utils.py +40 -0
  25. zsynctech_studio_sdk-0.1.1/uv.lock +270 -0
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.13
@@ -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 @@
1
+
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "zsynctech-studio-sdk"
3
+ version = "0.1.1"
4
+ description = "SDK Python para integração com plataforma ZSync Studio"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Rodrigo Zavan", email = "rodrigo.zavan@zsynctech.com" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "appdirs>=1.4.4",
12
+ "httpx>=0.28.1",
13
+ "playwright>=1.54.0",
14
+ "pydantic>=2.11.7",
15
+ "uuid7>=0.1.0",
16
+ "watchdog>=6.0.0",
17
+ ]
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
@@ -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,4 @@
1
+ class Context:
2
+ execution = None
3
+ task = None
4
+ step = None
@@ -0,0 +1,11 @@
1
+ from .execution import ExecutionStatus
2
+ from .task import TaskStatus
3
+ from .step import StepStatus
4
+ from .operations import Operations
5
+
6
+ __all__ = [
7
+ "ExecutionStatus",
8
+ "TaskStatus",
9
+ "StepStatus",
10
+ "Operations"
11
+ ]
@@ -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,6 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class Operations(StrEnum):
5
+ CREATE = 'create'
6
+ UPDATE = 'update'
@@ -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,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()
@@ -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,270 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.13"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.10.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "idna" },
20
+ { name = "sniffio" },
21
+ ]
22
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" }
23
+ wheels = [
24
+ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "appdirs"
29
+ version = "1.4.4"
30
+ source = { registry = "https://pypi.org/simple" }
31
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "certifi"
38
+ version = "2025.8.3"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
43
+ ]
44
+
45
+ [[package]]
46
+ name = "greenlet"
47
+ version = "3.2.4"
48
+ source = { registry = "https://pypi.org/simple" }
49
+ sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
50
+ wheels = [
51
+ { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
52
+ { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
53
+ { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" },
54
+ { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" },
55
+ { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" },
56
+ { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
57
+ { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
58
+ { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
59
+ { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
60
+ { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
61
+ { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
62
+ { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
63
+ { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
64
+ { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
65
+ { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
66
+ { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
67
+ ]
68
+
69
+ [[package]]
70
+ name = "h11"
71
+ version = "0.16.0"
72
+ source = { registry = "https://pypi.org/simple" }
73
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
74
+ wheels = [
75
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
76
+ ]
77
+
78
+ [[package]]
79
+ name = "httpcore"
80
+ version = "1.0.9"
81
+ source = { registry = "https://pypi.org/simple" }
82
+ dependencies = [
83
+ { name = "certifi" },
84
+ { name = "h11" },
85
+ ]
86
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
87
+ wheels = [
88
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
89
+ ]
90
+
91
+ [[package]]
92
+ name = "httpx"
93
+ version = "0.28.1"
94
+ source = { registry = "https://pypi.org/simple" }
95
+ dependencies = [
96
+ { name = "anyio" },
97
+ { name = "certifi" },
98
+ { name = "httpcore" },
99
+ { name = "idna" },
100
+ ]
101
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
102
+ wheels = [
103
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
104
+ ]
105
+
106
+ [[package]]
107
+ name = "idna"
108
+ version = "3.10"
109
+ source = { registry = "https://pypi.org/simple" }
110
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
111
+ wheels = [
112
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
113
+ ]
114
+
115
+ [[package]]
116
+ name = "playwright"
117
+ version = "1.54.0"
118
+ source = { registry = "https://pypi.org/simple" }
119
+ dependencies = [
120
+ { name = "greenlet" },
121
+ { name = "pyee" },
122
+ ]
123
+ wheels = [
124
+ { url = "https://files.pythonhosted.org/packages/f3/09/33d5bfe393a582d8dac72165a9e88b274143c9df411b65ece1cc13f42988/playwright-1.54.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bf3b845af744370f1bd2286c2a9536f474cc8a88dc995b72ea9a5be714c9a77d", size = 40439034, upload-time = "2025-07-22T13:58:04.816Z" },
125
+ { url = "https://files.pythonhosted.org/packages/e1/7b/51882dc584f7aa59f446f2bb34e33c0e5f015de4e31949e5b7c2c10e54f0/playwright-1.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:780928b3ca2077aea90414b37e54edd0c4bbb57d1aafc42f7aa0b3fd2c2fac02", size = 38702308, upload-time = "2025-07-22T13:58:08.211Z" },
126
+ { url = "https://files.pythonhosted.org/packages/73/a1/7aa8ae175b240c0ec8849fcf000e078f3c693f9aa2ffd992da6550ea0dff/playwright-1.54.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:81d0b6f28843b27f288cfe438af0a12a4851de57998009a519ea84cee6fbbfb9", size = 40439037, upload-time = "2025-07-22T13:58:11.37Z" },
127
+ { url = "https://files.pythonhosted.org/packages/34/a9/45084fd23b6206f954198296ce39b0acf50debfdf3ec83a593e4d73c9c8a/playwright-1.54.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:09919f45cc74c64afb5432646d7fef0d19fff50990c862cb8d9b0577093f40cc", size = 45920135, upload-time = "2025-07-22T13:58:14.494Z" },
128
+ { url = "https://files.pythonhosted.org/packages/02/d4/6a692f4c6db223adc50a6e53af405b45308db39270957a6afebddaa80ea2/playwright-1.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ae206c55737e8e3eae51fb385d61c0312eeef31535643bb6232741b41b6fdc", size = 45302695, upload-time = "2025-07-22T13:58:18.901Z" },
129
+ { url = "https://files.pythonhosted.org/packages/72/7a/4ee60a1c3714321db187bebbc40d52cea5b41a856925156325058b5fca5a/playwright-1.54.0-py3-none-win32.whl", hash = "sha256:0b108622ffb6906e28566f3f31721cd57dda637d7e41c430287804ac01911f56", size = 35469309, upload-time = "2025-07-22T13:58:21.917Z" },
130
+ { url = "https://files.pythonhosted.org/packages/aa/77/8f8fae05a242ef639de963d7ae70a69d0da61d6d72f1207b8bbf74ffd3e7/playwright-1.54.0-py3-none-win_amd64.whl", hash = "sha256:9e5aee9ae5ab1fdd44cd64153313a2045b136fcbcfb2541cc0a3d909132671a2", size = 35469311, upload-time = "2025-07-22T13:58:24.707Z" },
131
+ { url = "https://files.pythonhosted.org/packages/33/ff/99a6f4292a90504f2927d34032a4baf6adb498dc3f7cf0f3e0e22899e310/playwright-1.54.0-py3-none-win_arm64.whl", hash = "sha256:a975815971f7b8dca505c441a4c56de1aeb56a211290f8cc214eeef5524e8d75", size = 31239119, upload-time = "2025-07-22T13:58:27.56Z" },
132
+ ]
133
+
134
+ [[package]]
135
+ name = "pydantic"
136
+ version = "2.11.7"
137
+ source = { registry = "https://pypi.org/simple" }
138
+ dependencies = [
139
+ { name = "annotated-types" },
140
+ { name = "pydantic-core" },
141
+ { name = "typing-extensions" },
142
+ { name = "typing-inspection" },
143
+ ]
144
+ sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
145
+ wheels = [
146
+ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
147
+ ]
148
+
149
+ [[package]]
150
+ name = "pydantic-core"
151
+ version = "2.33.2"
152
+ source = { registry = "https://pypi.org/simple" }
153
+ dependencies = [
154
+ { name = "typing-extensions" },
155
+ ]
156
+ sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
157
+ wheels = [
158
+ { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
159
+ { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
160
+ { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
161
+ { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
162
+ { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
163
+ { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
164
+ { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
165
+ { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
166
+ { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
167
+ { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
168
+ { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
169
+ { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
170
+ { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
171
+ { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
172
+ { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
173
+ { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
174
+ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
175
+ ]
176
+
177
+ [[package]]
178
+ name = "pyee"
179
+ version = "13.0.0"
180
+ source = { registry = "https://pypi.org/simple" }
181
+ dependencies = [
182
+ { name = "typing-extensions" },
183
+ ]
184
+ sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" }
185
+ wheels = [
186
+ { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" },
187
+ ]
188
+
189
+ [[package]]
190
+ name = "sniffio"
191
+ version = "1.3.1"
192
+ source = { registry = "https://pypi.org/simple" }
193
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
194
+ wheels = [
195
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
196
+ ]
197
+
198
+ [[package]]
199
+ name = "typing-extensions"
200
+ version = "4.14.1"
201
+ source = { registry = "https://pypi.org/simple" }
202
+ sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
203
+ wheels = [
204
+ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
205
+ ]
206
+
207
+ [[package]]
208
+ name = "typing-inspection"
209
+ version = "0.4.1"
210
+ source = { registry = "https://pypi.org/simple" }
211
+ dependencies = [
212
+ { name = "typing-extensions" },
213
+ ]
214
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
215
+ wheels = [
216
+ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
217
+ ]
218
+
219
+ [[package]]
220
+ name = "uuid7"
221
+ version = "0.1.0"
222
+ source = { registry = "https://pypi.org/simple" }
223
+ sdist = { url = "https://files.pythonhosted.org/packages/5c/19/7472bd526591e2192926247109dbf78692e709d3e56775792fec877a7720/uuid7-0.1.0.tar.gz", hash = "sha256:8c57aa32ee7456d3cc68c95c4530bc571646defac01895cfc73545449894a63c", size = 14052, upload-time = "2021-12-29T01:38:21.897Z" }
224
+ wheels = [
225
+ { url = "https://files.pythonhosted.org/packages/b5/77/8852f89a91453956582a85024d80ad96f30a41fed4c2b3dce0c9f12ecc7e/uuid7-0.1.0-py2.py3-none-any.whl", hash = "sha256:5e259bb63c8cb4aded5927ff41b444a80d0c7124e8a0ced7cf44efa1f5cccf61", size = 7477, upload-time = "2021-12-29T01:38:20.418Z" },
226
+ ]
227
+
228
+ [[package]]
229
+ name = "watchdog"
230
+ version = "6.0.0"
231
+ source = { registry = "https://pypi.org/simple" }
232
+ sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
233
+ wheels = [
234
+ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
235
+ { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
236
+ { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
237
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
238
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
239
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
240
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
241
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
242
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
243
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
244
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
245
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
246
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
247
+ ]
248
+
249
+ [[package]]
250
+ name = "zsynctech-studio-sdk"
251
+ version = "0.1.1"
252
+ source = { editable = "." }
253
+ dependencies = [
254
+ { name = "appdirs" },
255
+ { name = "httpx" },
256
+ { name = "playwright" },
257
+ { name = "pydantic" },
258
+ { name = "uuid7" },
259
+ { name = "watchdog" },
260
+ ]
261
+
262
+ [package.metadata]
263
+ requires-dist = [
264
+ { name = "appdirs", specifier = ">=1.4.4" },
265
+ { name = "httpx", specifier = ">=0.28.1" },
266
+ { name = "playwright", specifier = ">=1.54.0" },
267
+ { name = "pydantic", specifier = ">=2.11.7" },
268
+ { name = "uuid7", specifier = ">=0.1.0" },
269
+ { name = "watchdog", specifier = ">=6.0.0" },
270
+ ]