konecty-sdk-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +7 -0
- cli/apply.py +455 -0
- cli/backup.py +129 -0
- cli/pull.py +262 -0
- konecty_sdk_python-0.1.0.dist-info/METADATA +30 -0
- konecty_sdk_python-0.1.0.dist-info/RECORD +13 -0
- konecty_sdk_python-0.1.0.dist-info/WHEEL +4 -0
- lib/client.py +423 -0
- lib/file_manager.py +248 -0
- lib/filters.py +155 -0
- lib/model.py +76 -0
- lib/settings.py +115 -0
- lib/types.py +303 -0
lib/filters.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FilterMatch(str, Enum):
|
|
9
|
+
"""Tipo de correspondência do filtro."""
|
|
10
|
+
|
|
11
|
+
AND = "and"
|
|
12
|
+
OR = "or"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilterOperator(str, Enum):
|
|
16
|
+
"""Operadores disponíveis para filtros."""
|
|
17
|
+
|
|
18
|
+
EQUALS = "equals"
|
|
19
|
+
NOT_EQUALS = "not_equals"
|
|
20
|
+
STARTS_WITH = "starts_with"
|
|
21
|
+
END_WITH = "end_with"
|
|
22
|
+
CONTAINS = "contains"
|
|
23
|
+
NOT_CONTAINS = "not_contains"
|
|
24
|
+
LESS_THAN = "less_than"
|
|
25
|
+
GREATER_THAN = "greater_than"
|
|
26
|
+
LESS_OR_EQUALS = "less_or_equals"
|
|
27
|
+
GREATER_OR_EQUALS = "greater_or_equals"
|
|
28
|
+
BETWEEN = "between"
|
|
29
|
+
IN = "in"
|
|
30
|
+
NOT_IN = "not_in"
|
|
31
|
+
EXISTS = "exists"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DateValue(BaseModel):
|
|
35
|
+
"""Valor de data para filtros."""
|
|
36
|
+
|
|
37
|
+
date: datetime
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BetweenValue(BaseModel):
|
|
41
|
+
"""Valor para filtros do tipo between."""
|
|
42
|
+
|
|
43
|
+
greater_or_equals: Union[int, float, DateValue, datetime, None]
|
|
44
|
+
less_or_equals: Union[int, float, DateValue, datetime, None]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FilterCondition(BaseModel):
|
|
48
|
+
"""Condição de filtro."""
|
|
49
|
+
|
|
50
|
+
term: str = Field(..., description="Campo a ser filtrado")
|
|
51
|
+
operator: FilterOperator = Field(..., description="Operador de comparação")
|
|
52
|
+
value: Any = Field(..., description="Valor para comparação")
|
|
53
|
+
disabled: bool = Field(False, description="Se a condição está desativada")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class KonectyFilter(BaseModel):
|
|
57
|
+
"""Filtro Konecty."""
|
|
58
|
+
|
|
59
|
+
match: FilterMatch = Field(FilterMatch.AND, description="Tipo de correspondência")
|
|
60
|
+
conditions: List[FilterCondition] = Field(default_factory=list, description="Lista de condições")
|
|
61
|
+
filters: List["KonectyFilter"] = Field(default_factory=list, description="Lista de filtros aninhados")
|
|
62
|
+
|
|
63
|
+
def to_json(self) -> Dict[str, Any]:
|
|
64
|
+
"""Converte o filtro para formato JSON."""
|
|
65
|
+
return self.model_dump(mode="json")
|
|
66
|
+
|
|
67
|
+
def is_empty(self) -> bool:
|
|
68
|
+
"""Verifica se o filtro está vazio."""
|
|
69
|
+
return len(self.conditions) == 0 and len(self.filters) == 0
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, data: Dict[str, Any]) -> "KonectyFilter":
|
|
73
|
+
"""Converte dicionário para filtro."""
|
|
74
|
+
return cls(**data)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def create(cls, match: Union[FilterMatch, str] = FilterMatch.AND) -> "KonectyFilter":
|
|
78
|
+
"""Cria uma nova instância de filtro.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
match: Tipo de correspondência ("and" ou "or")
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Nova instância de KonectyFilter
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(match, str):
|
|
87
|
+
match = FilterMatch(match.lower())
|
|
88
|
+
return cls(match=match)
|
|
89
|
+
|
|
90
|
+
def add_condition(
|
|
91
|
+
self, term: str, operator: Union[FilterOperator, str], value: Any, disabled: bool = False
|
|
92
|
+
) -> "KonectyFilter":
|
|
93
|
+
"""Adiciona uma condição ao filtro.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
term: Campo a ser filtrado
|
|
97
|
+
operator: Operador de comparação
|
|
98
|
+
value: Valor para comparação
|
|
99
|
+
disabled: Se a condição está desativada
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Self para encadeamento
|
|
103
|
+
"""
|
|
104
|
+
if isinstance(operator, str):
|
|
105
|
+
operator = FilterOperator(operator.lower())
|
|
106
|
+
|
|
107
|
+
self.conditions.append(
|
|
108
|
+
FilterCondition(
|
|
109
|
+
term=term,
|
|
110
|
+
operator=operator,
|
|
111
|
+
value=value,
|
|
112
|
+
disabled=disabled,
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def add_filter(self, match: Union[FilterMatch, str] = FilterMatch.AND) -> "KonectyFilter":
|
|
118
|
+
"""Adiciona um filtro aninhado.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
match: Tipo de correspondência do filtro aninhado
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Novo filtro aninhado
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(match, str):
|
|
127
|
+
match = FilterMatch(match.lower())
|
|
128
|
+
|
|
129
|
+
nested_filter = KonectyFilter(match=match)
|
|
130
|
+
self.filters.append(nested_filter)
|
|
131
|
+
return nested_filter
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SortDirection(str, Enum):
|
|
135
|
+
"""Direção da ordenação."""
|
|
136
|
+
|
|
137
|
+
ASC = "ASC"
|
|
138
|
+
DESC = "DESC"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SortOrder(BaseModel):
|
|
142
|
+
"""Ordenação de resultados."""
|
|
143
|
+
|
|
144
|
+
property: str = Field(..., description="Campo para ordenação")
|
|
145
|
+
direction: SortDirection = Field(..., description="Direção da ordenação")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class KonectyFindParams(BaseModel):
|
|
149
|
+
"""Parâmetros para busca no Konecty."""
|
|
150
|
+
|
|
151
|
+
filter: KonectyFilter
|
|
152
|
+
start: Optional[int] = None
|
|
153
|
+
limit: Optional[int] = None
|
|
154
|
+
sort: Optional[List[SortOrder]] = None
|
|
155
|
+
fields: Optional[List[Union[str, int]]] = None
|
lib/model.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar, cast
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field, create_model
|
|
4
|
+
from pydantic.fields import FieldInfo
|
|
5
|
+
|
|
6
|
+
from konecty.types import Address, KonectyDateTime, KonectyEmail, KonectyLookup, KonectyPersonName, KonectyPhone
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
ModelType = TypeVar("ModelType", bound=BaseModel)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KonectyModelGenerator:
|
|
13
|
+
def __init__(self, schema: Dict[str, Any]):
|
|
14
|
+
self.schema = schema
|
|
15
|
+
self.type_mapping: Dict[str, Type[Any]] = {
|
|
16
|
+
"json": dict,
|
|
17
|
+
"richText": str,
|
|
18
|
+
"lookup": KonectyLookup,
|
|
19
|
+
"picklist": str,
|
|
20
|
+
"url": str,
|
|
21
|
+
"boolean": bool,
|
|
22
|
+
"text": str,
|
|
23
|
+
"dateTime": KonectyDateTime,
|
|
24
|
+
"address": Address,
|
|
25
|
+
"email": KonectyEmail,
|
|
26
|
+
"phone": KonectyPhone,
|
|
27
|
+
"personName": KonectyPersonName,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def generate_model(self, language: str = "en") -> Type[BaseModel]:
|
|
31
|
+
fields: Dict[str, tuple[Type[Any], FieldInfo]] = {}
|
|
32
|
+
for field in self.schema["fields"]:
|
|
33
|
+
field_name = field["name"]
|
|
34
|
+
field_raw_type = field["type"]
|
|
35
|
+
description = (
|
|
36
|
+
field["help"].get(language, field["help"].get("en", ""))
|
|
37
|
+
if "help" in field
|
|
38
|
+
else field["label"].get(language, field["label"].get("en", ""))
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
base_type = self._get_field_type(field_raw_type)
|
|
42
|
+
current_type = base_type
|
|
43
|
+
|
|
44
|
+
if field.get("isList"):
|
|
45
|
+
current_type = List[base_type] # type: ignore
|
|
46
|
+
|
|
47
|
+
if field.get("minSelected", 1) > 1 or field.get("maxSelected", 1) > 1:
|
|
48
|
+
current_type = List[base_type] # type: ignore
|
|
49
|
+
|
|
50
|
+
is_required = field.get("isRequired", False) or field.get("minSelected", 0) > 0
|
|
51
|
+
|
|
52
|
+
if not is_required and "defaultValue" not in field:
|
|
53
|
+
current_type = Optional[base_type] # type: ignore
|
|
54
|
+
|
|
55
|
+
field_params = {
|
|
56
|
+
"alias": field_name,
|
|
57
|
+
"description": description,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if "defaultValue" in field:
|
|
61
|
+
field_params["default"] = field["defaultValue"]
|
|
62
|
+
|
|
63
|
+
if not is_required and "defaultValue" not in field:
|
|
64
|
+
field_params["default"] = None
|
|
65
|
+
|
|
66
|
+
fields[self._clean_name_for_pydantic(field_name)] = (current_type, Field(**field_params))
|
|
67
|
+
|
|
68
|
+
model_name = self.schema["name"]
|
|
69
|
+
return create_model(model_name, **fields)
|
|
70
|
+
|
|
71
|
+
def _clean_name_for_pydantic(self, name: str) -> str:
|
|
72
|
+
return name.replace("_", "")
|
|
73
|
+
|
|
74
|
+
def _get_field_type(self, field_type: str) -> Type[Any]:
|
|
75
|
+
result = self.type_mapping.get(field_type, Any)
|
|
76
|
+
return cast(Type[Any], result)
|
lib/settings.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Módulo para gerenciar configurações do Konecty."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Type, TypeVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .client import KonectyClient
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T", bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _convert_value(value: str, field_type: Type[Any] | None) -> Any:
|
|
15
|
+
"""Converte um valor string para o tipo correto.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
value: Valor string a ser convertido
|
|
19
|
+
field_type: Tipo para converter o valor
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Valor convertido para o tipo correto
|
|
23
|
+
"""
|
|
24
|
+
if field_type is None:
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
if field_type is bool:
|
|
29
|
+
return value.lower() == "true"
|
|
30
|
+
elif field_type is int:
|
|
31
|
+
return int(value)
|
|
32
|
+
elif field_type is float:
|
|
33
|
+
return float(value)
|
|
34
|
+
elif field_type is list:
|
|
35
|
+
return [item.strip() for item in value.split(",")]
|
|
36
|
+
elif field_type is dict:
|
|
37
|
+
return json.loads(value)
|
|
38
|
+
else:
|
|
39
|
+
return value
|
|
40
|
+
except (ValueError, TypeError, json.JSONDecodeError):
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def fill_settings(settings_class: Type[T]) -> T:
|
|
45
|
+
"""Preenche as configurações de uma classe com valores do Konecty.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
settings_class: Classe de configurações que herda de BaseModel.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Instância da classe de configurações preenchida com valores do Konecty.
|
|
52
|
+
"""
|
|
53
|
+
client = KonectyClient(
|
|
54
|
+
base_url=os.getenv("KONECTY_URL", "http://localhost:3000"),
|
|
55
|
+
token=os.getenv("KONECTY_TOKEN", "invalid_key"),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
settings_dict = {}
|
|
59
|
+
|
|
60
|
+
for field_name in settings_class.model_fields.keys():
|
|
61
|
+
# Primeiro verifica se existe na variável de ambiente
|
|
62
|
+
env_value = os.getenv(field_name.upper())
|
|
63
|
+
if env_value is not None and env_value.strip():
|
|
64
|
+
field_type = settings_class.model_fields[field_name].annotation
|
|
65
|
+
converted_value = _convert_value(env_value, field_type)
|
|
66
|
+
if converted_value is not None:
|
|
67
|
+
settings_dict[field_name] = converted_value
|
|
68
|
+
continue
|
|
69
|
+
# Se não encontrou na variável de ambiente, busca no Konecty
|
|
70
|
+
if field_name not in settings_dict:
|
|
71
|
+
value = await client.get_setting(field_name)
|
|
72
|
+
if value is not None and value.strip():
|
|
73
|
+
field_type = settings_class.model_fields[field_name].annotation
|
|
74
|
+
converted_value = _convert_value(value, field_type)
|
|
75
|
+
if converted_value is not None:
|
|
76
|
+
settings_dict[field_name] = converted_value
|
|
77
|
+
|
|
78
|
+
return settings_class.model_construct(**settings_dict)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def fill_settings_sync(settings_class: Type[T]) -> T:
|
|
82
|
+
"""Versão síncrona de fill_settings.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
settings_class: Classe de configurações que herda de BaseModel.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Instância da classe de configurações preenchida com valores do Konecty.
|
|
89
|
+
"""
|
|
90
|
+
client = KonectyClient(
|
|
91
|
+
base_url=os.getenv("KONECTY_URL", "http://localhost:3000"),
|
|
92
|
+
token=os.getenv("KONECTY_TOKEN", "invalid_key"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
settings_dict = {}
|
|
96
|
+
|
|
97
|
+
for field_name in settings_class.model_fields.keys():
|
|
98
|
+
# Primeiro verifica se existe na variável de ambiente
|
|
99
|
+
env_value = os.getenv(field_name.upper())
|
|
100
|
+
if env_value is not None and env_value.strip():
|
|
101
|
+
field_type = settings_class.model_fields[field_name].annotation
|
|
102
|
+
converted_value = _convert_value(env_value, field_type)
|
|
103
|
+
if converted_value is not None:
|
|
104
|
+
settings_dict[field_name] = converted_value
|
|
105
|
+
continue
|
|
106
|
+
# Se não encontrou na variável de ambiente, busca no Konecty
|
|
107
|
+
if field_name not in settings_dict:
|
|
108
|
+
value = client.get_setting_sync(field_name)
|
|
109
|
+
if value is not None and value.strip():
|
|
110
|
+
field_type = settings_class.model_fields[field_name].annotation
|
|
111
|
+
converted_value = _convert_value(value, field_type)
|
|
112
|
+
if converted_value is not None:
|
|
113
|
+
settings_dict[field_name] = converted_value
|
|
114
|
+
|
|
115
|
+
return settings_class.model_construct(**settings_dict)
|
lib/types.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Optional, Self
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
7
|
+
from pydantic_core import CoreSchema, core_schema
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class KonectyDateTimeError(Exception):
|
|
11
|
+
"""Exceção base para erros de data/hora."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class KonectyDateTimeFormatError(KonectyDateTimeError):
|
|
17
|
+
"""Exceção para erros de formato de data/hora."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__("Data em formato inválido")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KonectyDateTimeTypeError(KonectyDateTimeError):
|
|
24
|
+
"""Exceção para erros de tipo de data/hora."""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
super().__init__("Tipo inválido para KonectyDateTime")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KonectyDateTime(datetime):
|
|
31
|
+
"""Classe personalizada para manipular datetime com o formato {'$date': 'ISO8601 string'}."""
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_datetime(cls, dt: datetime) -> Self:
|
|
35
|
+
return cls(
|
|
36
|
+
dt.year,
|
|
37
|
+
dt.month,
|
|
38
|
+
dt.day,
|
|
39
|
+
dt.hour,
|
|
40
|
+
dt.minute,
|
|
41
|
+
dt.second,
|
|
42
|
+
dt.microsecond,
|
|
43
|
+
dt.tzinfo,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_json(cls, json: dict[str, Any]) -> Self:
|
|
48
|
+
date_str = json["$date"]
|
|
49
|
+
date = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
|
|
50
|
+
return cls(
|
|
51
|
+
date.year,
|
|
52
|
+
date.month,
|
|
53
|
+
date.day,
|
|
54
|
+
date.hour,
|
|
55
|
+
date.minute,
|
|
56
|
+
date.second,
|
|
57
|
+
date.microsecond,
|
|
58
|
+
date.tzinfo,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_any(cls, value: Any) -> Self:
|
|
63
|
+
if isinstance(value, dict) and "$date" in value:
|
|
64
|
+
return cls.from_json(value)
|
|
65
|
+
if isinstance(value, datetime):
|
|
66
|
+
return cls.from_datetime(value)
|
|
67
|
+
if isinstance(value, str):
|
|
68
|
+
return cls.from_isoformat(value)
|
|
69
|
+
raise KonectyDateTimeTypeError
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_isoformat(cls, value: str) -> Self:
|
|
73
|
+
date = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
74
|
+
return cls(
|
|
75
|
+
date.year,
|
|
76
|
+
date.month,
|
|
77
|
+
date.day,
|
|
78
|
+
date.hour,
|
|
79
|
+
date.minute,
|
|
80
|
+
date.second,
|
|
81
|
+
date.microsecond,
|
|
82
|
+
date.tzinfo,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def __get_validators__(cls):
|
|
87
|
+
yield cls.validate
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def validate(cls, v):
|
|
91
|
+
if isinstance(v, dict) and "$date" in v:
|
|
92
|
+
try:
|
|
93
|
+
return datetime.fromisoformat(v["$date"].replace("Z", "+00:00"))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise KonectyDateTimeFormatError from e
|
|
96
|
+
elif isinstance(v, datetime):
|
|
97
|
+
return v
|
|
98
|
+
raise KonectyDateTimeTypeError
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> CoreSchema:
|
|
102
|
+
"""Define o schema para serialização/deserialização do Pydantic."""
|
|
103
|
+
return core_schema.json_or_python_schema(
|
|
104
|
+
json_schema=core_schema.typed_dict_schema(
|
|
105
|
+
{
|
|
106
|
+
"$date": core_schema.typed_dict_field(core_schema.str_schema()),
|
|
107
|
+
},
|
|
108
|
+
total=True,
|
|
109
|
+
),
|
|
110
|
+
python_schema=core_schema.datetime_schema(),
|
|
111
|
+
serialization=core_schema.plain_serializer_function_ser_schema(
|
|
112
|
+
lambda x: {"$date": x.isoformat()}
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def __get_pydantic_json_schema__(
|
|
118
|
+
cls, core_schema: Any, handler: Any
|
|
119
|
+
) -> JsonSchemaValue:
|
|
120
|
+
"""Define o schema JSON para documentação."""
|
|
121
|
+
json_schema = handler(core_schema)
|
|
122
|
+
json_schema.update(
|
|
123
|
+
examples=["2023-01-01T00:00:00Z"],
|
|
124
|
+
type="string",
|
|
125
|
+
format="date-time",
|
|
126
|
+
)
|
|
127
|
+
return json_schema
|
|
128
|
+
|
|
129
|
+
def to_json(self):
|
|
130
|
+
return {"$date": self.isoformat()}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Address(BaseModel):
|
|
134
|
+
"""Representa um endereço completo.
|
|
135
|
+
|
|
136
|
+
Esta classe modela informações detalhadas de um endereço, incluindo
|
|
137
|
+
dados geográficos e informações de localização específicas.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
number: str | None = Field(None, description="Número do endereço.")
|
|
141
|
+
postal_code: str | None = Field(
|
|
142
|
+
None, description="Código postal ou CEP do endereço."
|
|
143
|
+
)
|
|
144
|
+
street: str | None = Field(None, description="Nome da rua, avenida ou logradouro.")
|
|
145
|
+
district: str | None = Field(None, description="Bairro ou distrito.")
|
|
146
|
+
city: str | None = Field(None, description="Cidade.")
|
|
147
|
+
state: str | None = Field(None, description="Estado ou província.")
|
|
148
|
+
place_type: str | None = Field(
|
|
149
|
+
None, description="Tipo de logradouro (por exemplo, Rua, Avenida, Praça)."
|
|
150
|
+
)
|
|
151
|
+
complement: str | None = Field(
|
|
152
|
+
None, description="Informações complementares do endereço."
|
|
153
|
+
)
|
|
154
|
+
country: str | None = Field(None, description="País.")
|
|
155
|
+
|
|
156
|
+
def to_dict(self) -> dict[str, Any]:
|
|
157
|
+
return self.model_dump(by_alias=True)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class KonectyUser(BaseModel):
|
|
161
|
+
"""Representa um usuário do Konecty."""
|
|
162
|
+
|
|
163
|
+
id: str = Field(alias="_id")
|
|
164
|
+
name: str = Field(alias="name")
|
|
165
|
+
active: bool = Field(alias="active")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class KonectyBaseModel(BaseModel):
|
|
169
|
+
"""Modelo base para documentos do Konecty."""
|
|
170
|
+
|
|
171
|
+
id: str = Field(alias="_id")
|
|
172
|
+
created_at: KonectyDateTime = Field(alias="_createdAt")
|
|
173
|
+
created_by: KonectyUser = Field(alias="_createdBy")
|
|
174
|
+
updated_at: KonectyDateTime = Field(alias="_updatedAt")
|
|
175
|
+
updated_by: KonectyUser = Field(alias="_updatedBy")
|
|
176
|
+
user: list[KonectyUser] = Field(alias="_user")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class KonectyLabel(BaseModel):
|
|
180
|
+
pt_br: str = Field(alias="pt_BR")
|
|
181
|
+
en: str = Field(alias="en")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class KonectyPhone(BaseModel):
|
|
185
|
+
country_code: Optional[int] = Field(None, ge=1, le=999, alias="countryCode")
|
|
186
|
+
phone_number: Optional[str] = Field(
|
|
187
|
+
None, max_length=11, min_length=8, alias="phoneNumber"
|
|
188
|
+
)
|
|
189
|
+
type: Optional[str] = Field(
|
|
190
|
+
None,
|
|
191
|
+
description="Tipo do telefone (por exemplo, celular, fixo, comercial)",
|
|
192
|
+
alias="type",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
model_config = {
|
|
196
|
+
"populate_by_name": True,
|
|
197
|
+
"extra": "allow",
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def empty(cls) -> Self:
|
|
202
|
+
return cls(countryCode=None, phoneNumber=None, type=None)
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
206
|
+
return cls.model_validate(data)
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def from_string(cls, value: str) -> Self:
|
|
210
|
+
return cls(
|
|
211
|
+
phoneNumber=re.sub(r"\D", "", value),
|
|
212
|
+
countryCode=55,
|
|
213
|
+
type="mobile",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def from_any(cls, value: Any) -> Self | None:
|
|
218
|
+
if value is None:
|
|
219
|
+
return None
|
|
220
|
+
if isinstance(value, cls):
|
|
221
|
+
return value
|
|
222
|
+
if isinstance(value, str):
|
|
223
|
+
return cls.from_string(value)
|
|
224
|
+
if isinstance(value, dict):
|
|
225
|
+
return cls.from_dict(value)
|
|
226
|
+
raise ValueError("Invalid value for KonectyPhone")
|
|
227
|
+
|
|
228
|
+
def to_dict(self) -> dict[str, Any]:
|
|
229
|
+
return self.model_dump(by_alias=True)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class KonectyLookup(BaseModel):
|
|
233
|
+
"""Representa uma referência a outro documento no Konecty."""
|
|
234
|
+
|
|
235
|
+
id: str = Field(alias="_id")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class KonectyEmail(BaseModel):
|
|
239
|
+
"""Representa um endereço de e-mail."""
|
|
240
|
+
|
|
241
|
+
address: Optional[str] = Field(None, description="Endereço de e-mail válido")
|
|
242
|
+
|
|
243
|
+
model_config = {
|
|
244
|
+
"populate_by_name": True,
|
|
245
|
+
"extra": "allow",
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def empty(cls) -> Self:
|
|
250
|
+
return cls(address=None)
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
254
|
+
return cls.model_validate(data)
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def from_string(cls, value: str) -> Self:
|
|
258
|
+
return cls(address=value)
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def from_any(cls, value: Any) -> Self | None:
|
|
262
|
+
if value is None:
|
|
263
|
+
return None
|
|
264
|
+
if isinstance(value, str):
|
|
265
|
+
return cls.from_string(value)
|
|
266
|
+
if isinstance(value, dict):
|
|
267
|
+
return cls.from_dict(value)
|
|
268
|
+
if isinstance(value, cls):
|
|
269
|
+
return value
|
|
270
|
+
raise ValueError("Invalid value for KonectyEmail")
|
|
271
|
+
|
|
272
|
+
def to_dict(self) -> dict[str, Any]:
|
|
273
|
+
return self.model_dump(by_alias=True)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class KonectyPersonName(BaseModel):
|
|
277
|
+
"""Representa um nome completo de pessoa."""
|
|
278
|
+
|
|
279
|
+
first: str | None = Field(None, description="Primeiro nome")
|
|
280
|
+
last: str | None = Field(None, description="Sobrenome")
|
|
281
|
+
full: str = Field(description="Nome completo")
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_dict(cls, data: dict[str, Any]) -> Self:
|
|
285
|
+
return cls.model_validate(data)
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def from_string(cls, value: str) -> Self:
|
|
289
|
+
name = value.split(" ")
|
|
290
|
+
return cls(first=name[0], last=" ".join(name[1:]), full=value)
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def from_any(cls, value: Any) -> Self:
|
|
294
|
+
if isinstance(value, str):
|
|
295
|
+
return cls.from_string(value)
|
|
296
|
+
if isinstance(value, dict):
|
|
297
|
+
return cls.from_dict(value)
|
|
298
|
+
if isinstance(value, cls):
|
|
299
|
+
return value
|
|
300
|
+
raise ValueError("Invalid value for KonectyPersonName")
|
|
301
|
+
|
|
302
|
+
def to_dict(self) -> dict[str, Any]:
|
|
303
|
+
return self.model_dump(by_alias=True)
|