konecty_sdk_python 2.1.6__py3-none-any.whl → 2.1.7__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.
@@ -86,11 +86,20 @@ async def _read_json_body(response: "aiohttp.ClientResponse") -> Optional[Dict[s
86
86
  Lê o corpo como JSON sem explodir quando não é JSON (proxy, gateway, HTML).
87
87
 
88
88
  Devolve ``None`` nesse caso, e o chamador cai no tratamento por status.
89
+
90
+ Também devolve ``None`` quando o corpo é JSON válido mas **não é objeto** —
91
+ uma string ou lista, que é o que alguns proxies devolvem. O ``cast`` não
92
+ verifica nada em runtime, então sem esta checagem o chamador fazia
93
+ ``result.get(...)`` num ``str`` e recebia ``AttributeError`` no lugar do erro
94
+ de API.
89
95
  """
90
96
  try:
91
- return cast(Dict[str, Any], await response.json(content_type=None))
97
+ body = await response.json(content_type=None)
92
98
  except Exception: # noqa: BLE001 - corpo não-JSON é caso esperado aqui
93
99
  return None
100
+ if not isinstance(body, dict):
101
+ return None
102
+ return cast(Dict[str, Any], body)
94
103
 
95
104
 
96
105
  class KonectyClient:
@@ -458,6 +467,14 @@ class KonectyClient:
458
467
  """Get login method flags for the namespace (GET /api/auth/login-options)."""
459
468
  return await self._auth.get_login_options()
460
469
 
470
+ async def info(self) -> Dict[str, Any]:
471
+ """
472
+ Get the session user (GET /rest/auth/info), including ``user["redirectURL"]``.
473
+
474
+ Returns ``{"logged": False}`` without a token or when the server refuses the session.
475
+ """
476
+ return await self._auth.info()
477
+
461
478
  @property
462
479
  def _pat(self) -> PatService:
463
480
  if not hasattr(self, "_pat_service"):
@@ -719,7 +736,6 @@ class KonectyClient:
719
736
  if search:
720
737
  params["search"] = search
721
738
 
722
- print(f"params: {params}")
723
739
  async with (
724
740
  aiohttp.ClientSession() as session,
725
741
  session.get(
@@ -728,12 +744,16 @@ class KonectyClient:
728
744
  headers={"Authorization": self.headers["Authorization"]},
729
745
  ) as response,
730
746
  ):
731
- response.raise_for_status()
732
- result = await response.json()
747
+ # Mesma ordem do `find`: corpo primeiro, status depois. Ver `find`.
748
+ result = await _read_json_body(response)
749
+ if result is None:
750
+ response.raise_for_status()
751
+ raise KonectyAPIError(f"{response.status} {response.reason}")
733
752
  if not result.get("success", False):
734
753
  errors = result.get("errors", [])
735
754
  logger.error(errors)
736
- raise KonectyAPIError(errors)
755
+ raise_for_konecty_errors(errors)
756
+ response.raise_for_status()
737
757
  data = result.get("data", [])
738
758
  return cast(List[KonectyDict], data)
739
759
 
@@ -784,12 +804,16 @@ class KonectyClient:
784
804
  headers={"Authorization": self.headers["Authorization"]},
785
805
  ) as response,
786
806
  ):
787
- response.raise_for_status()
788
- result = await response.json()
807
+ # Mesma ordem do `find`: corpo primeiro, status depois. Ver `find`.
808
+ result = await _read_json_body(response)
809
+ if result is None:
810
+ response.raise_for_status()
811
+ raise KonectyAPIError(f"{response.status} {response.reason}")
789
812
  if not result.get("success", False):
790
813
  errors = result.get("errors", [])
791
814
  logger.error(errors)
792
- raise KonectyAPIError(errors)
815
+ raise_for_konecty_errors(errors)
816
+ response.raise_for_status()
793
817
  data = result.get("data", [None])
794
818
  return get_first_dict(data)
795
819
 
@@ -1022,12 +1046,16 @@ class KonectyClient:
1022
1046
  headers={"Authorization": self.headers["Authorization"]},
1023
1047
  ) as response,
1024
1048
  ):
1025
- response.raise_for_status()
1026
- result = await response.json()
1049
+ # Mesma ordem do `find`: corpo primeiro, status depois. Ver `find`.
1050
+ result = await _read_json_body(response)
1051
+ if result is None:
1052
+ response.raise_for_status()
1053
+ raise KonectyAPIError(f"{response.status} {response.reason}")
1027
1054
  if not result.get("success", False):
1028
1055
  errors = result.get("errors", [])
1029
1056
  logger.error(errors)
1030
- raise KonectyAPIError(errors)
1057
+ raise_for_konecty_errors(errors)
1058
+ response.raise_for_status()
1031
1059
  count = result.get("total", 0)
1032
1060
  return count
1033
1061
 
@@ -1,5 +1,7 @@
1
1
  """Exceptions for the Konecty SDK."""
2
2
 
3
+ from typing import Optional
4
+
3
5
 
4
6
  class KonectyError(Exception):
5
7
  """Base exception for Konecty errors."""
@@ -63,6 +65,163 @@ class KonectySortLimitError(KonectyAPIError):
63
65
  self.code = SORT_ABOVE_MAX_PAGE_SIZE
64
66
 
65
67
 
68
+ #: Códigos do operador de filtro ``within_radius``. Espelham as constantes de
69
+ #: mesmo nome no SDK TypeScript (``src/sdk/filters/withinRadius.ts``) e no
70
+ #: servidor (``src/imports/data/filters/withinRadius.ts`` no repo Konecty).
71
+ #:
72
+ #: Recusa de forma ou de faixa do valor: ``lat``/``lng`` (ou ``record``) ou
73
+ #: ``radius`` ausente, as duas formas juntas, chave desconhecida, coordenada fora
74
+ #: da faixa, string numérica no lugar de número, raio não positivo ou acima do
75
+ #: teto do servidor.
76
+ #: Nome do operador, usado nas mensagens default. Espelha `WITHIN_RADIUS` no SDK TS.
77
+ WITHIN_RADIUS_OPERATOR = "within_radius"
78
+
79
+ WITHIN_RADIUS_INVALID_VALUE = "WITHIN_RADIUS_INVALID_VALUE"
80
+
81
+ #: O centro por referência a registro não pôde ser resolvido. Um código só para
82
+ #: os três casos (registro inexistente, ilegível, ou sem geolocalização) é
83
+ #: decisão do servidor: distinguí-los na resposta transformaria o filtro num
84
+ #: oráculo de localização para quem não pode ler o registro.
85
+ WITHIN_RADIUS_CENTER_UNRESOLVED = "WITHIN_RADIUS_CENTER_UNRESOLVED"
86
+
87
+ #: A cadeia de centros por referência passou do teto de profundidade do servidor.
88
+ #: Acontece quando um centro aponta para um registro cujo filtro de leitura tem, ele
89
+ #: próprio, um `within_radius` com centro por referência — a resolução é recursiva e o
90
+ #: servidor corta. Não é erro do valor enviado: é configuração de metadado do documento
91
+ #: alvo. Espelha `WITHIN_RADIUS_CENTER_DEPTH_EXCEEDED` no SDK TS.
92
+ WITHIN_RADIUS_CENTER_DEPTH_EXCEEDED = "WITHIN_RADIUS_CENTER_DEPTH_EXCEEDED"
93
+
94
+ #: A requisição pediu mais centros por referência do que o servidor resolve de uma vez.
95
+ #: O SDK **não** duplica o teto: quem decide é o servidor, e um número copiado aqui
96
+ #: passaria a mentir assim que o backend mudasse. Espelha `WITHIN_RADIUS_TOO_MANY_CENTERS`
97
+ #: no SDK TS.
98
+ WITHIN_RADIUS_TOO_MANY_CENTERS = "WITHIN_RADIUS_TOO_MANY_CENTERS"
99
+
100
+ #: Recusa de ordenação por ``_distance``: o filtro não tem exatamente um
101
+ #: ``within_radius`` no caminho AND, ou o usuário não pode ler (ou só lê sob
102
+ #: condição) o campo ``address`` do centro. O servidor responde HTTP 400; a mensagem
103
+ #: diz qual dos dois casos é. Espelha ``DISTANCE_SORT_UNAVAILABLE`` no SDK TS.
104
+ DISTANCE_SORT_UNAVAILABLE = "DISTANCE_SORT_UNAVAILABLE"
105
+
106
+
107
+ class KonectyWithinRadiusValueError(KonectyAPIError):
108
+ """
109
+ Raised when the server refuses the shape or range of a ``within_radius`` value.
110
+
111
+ Carrega o ``code`` legível por máquina e preserva a mensagem do servidor, que
112
+ nomeia o termo e a chave exata que falhou. Subclasse de ``KonectyAPIError``,
113
+ então ``except KonectyAPIError`` existente continua pegando.
114
+
115
+ O SDK **não** duplica a validação de faixa nem o teto de raio: quem decide é
116
+ o servidor, e um teto copiado no cliente passa a mentir assim que o backend
117
+ muda. Mesma postura do ``SORT_ABOVE_MAX_PAGE_SIZE``.
118
+
119
+ Espelha ``KonectyWithinRadiusValueError`` no SDK TypeScript — manter os dois
120
+ em sincronia.
121
+ """
122
+
123
+ #: Default idêntico ao do SDK TS (`message ?? \`Invalid value for operator ${WITHIN_RADIUS}\``
124
+ #: em `src/sdk/filters/withinRadius.ts`), para que um erro sem `message` produza a MESMA
125
+ #: frase nos dois — antes o Python levantava com a string literal "None".
126
+ def __init__(self, message: Optional[str] = None) -> None:
127
+ super().__init__(
128
+ message or f"Invalid value for operator {WITHIN_RADIUS_OPERATOR}"
129
+ )
130
+ self.code = WITHIN_RADIUS_INVALID_VALUE
131
+
132
+
133
+ class KonectyWithinRadiusCenterError(KonectyAPIError):
134
+ """
135
+ Raised when a ``within_radius`` center given as a record reference could not
136
+ be resolved — the record does not exist, is not readable, or has no geolocation.
137
+
138
+ Espelha ``KonectyWithinRadiusCenterError`` no SDK TypeScript — manter os dois
139
+ em sincronia.
140
+ """
141
+
142
+ #: Mesmo default do SDK TS — ver `KonectyWithinRadiusValueError` acima.
143
+ def __init__(self, message: Optional[str] = None) -> None:
144
+ super().__init__(
145
+ message
146
+ or f"Could not resolve the center record for operator {WITHIN_RADIUS_OPERATOR}"
147
+ )
148
+ self.code = WITHIN_RADIUS_CENTER_UNRESOLVED
149
+
150
+
151
+ class KonectyWithinRadiusCenterDepthError(KonectyWithinRadiusCenterError):
152
+ """
153
+ Raised when the chain of center-by-reference resolutions exceeds the server depth cap.
154
+
155
+ **Herda de ``KonectyWithinRadiusCenterError`` de propósito**, como no SDK TS: é uma
156
+ falha de resolução de centro, e a própria mensagem do servidor começa com "Could not
157
+ resolve the center record". Quem escreveu ``except KonectyWithinRadiusCenterError``
158
+ antes deste código existir continua pegando.
159
+
160
+ Espelha ``KonectyWithinRadiusCenterDepthError`` no SDK TypeScript.
161
+ """
162
+
163
+ def __init__(self, message: Optional[str] = None) -> None:
164
+ super().__init__(
165
+ message
166
+ or f"Center hydration exceeded the maximum depth for operator {WITHIN_RADIUS_OPERATOR}"
167
+ )
168
+ self.code = WITHIN_RADIUS_CENTER_DEPTH_EXCEEDED
169
+
170
+
171
+ class KonectyWithinRadiusTooManyCentersError(KonectyAPIError):
172
+ """
173
+ Raised when the request asks for more center records than the server resolves at once.
174
+
175
+ **NÃO** herda de ``KonectyWithinRadiusCenterError``: nenhum centro específico falhou —
176
+ a requisição inteira foi recusada antes de qualquer leitura. Tratar cota como falha de
177
+ centro levaria o chamador a procurar um registro culpado que não existe.
178
+
179
+ Espelha ``KonectyWithinRadiusTooManyCentersError`` no SDK TypeScript.
180
+ """
181
+
182
+ def __init__(self, message: Optional[str] = None) -> None:
183
+ super().__init__(
184
+ message
185
+ or f"Too many center records requested for operator {WITHIN_RADIUS_OPERATOR}"
186
+ )
187
+ self.code = WITHIN_RADIUS_TOO_MANY_CENTERS
188
+
189
+
190
+ class KonectyDistanceSortUnavailableError(KonectyAPIError):
191
+ """
192
+ Raised when the server refuses a sort by ``_distance``.
193
+
194
+ **NÃO** herda de ``KonectySortLimitError``: não é teto de página, e a saída não é
195
+ ordenar por ``_id`` — é acertar o filtro (um ``within_radius`` só, no caminho AND)
196
+ ou a permissão de leitura do campo ``address``.
197
+
198
+ Espelha ``KonectyDistanceSortUnavailableError`` no SDK TypeScript.
199
+ """
200
+
201
+ #: Mesmo default do SDK TS (`src/sdk/filters/withinRadius.ts`).
202
+ def __init__(self, message: Optional[str] = None) -> None:
203
+ super().__init__(
204
+ message or "Sorting by _distance is not available for this query"
205
+ )
206
+ self.code = DISTANCE_SORT_UNAVAILABLE
207
+
208
+
209
+ #: Códigos que o SDK promove a exceção própria. A tabela existe para que um
210
+ #: código novo seja UMA linha em vez de mais um ``if`` no meio do fluxo — e para
211
+ #: que a lista de códigos suportados seja legível de uma vez, ao lado da lista
212
+ #: equivalente no SDK TypeScript (``ERROR_BY_CODE`` em ``src/sdk/errors.ts``).
213
+ #:
214
+ #: Todo código ausente daqui continua virando ``KonectyAPIError`` genérico.
215
+ _ERROR_BY_CODE = {
216
+ SORT_ABOVE_MAX_PAGE_SIZE: KonectySortLimitError,
217
+ WITHIN_RADIUS_INVALID_VALUE: KonectyWithinRadiusValueError,
218
+ WITHIN_RADIUS_CENTER_UNRESOLVED: KonectyWithinRadiusCenterError,
219
+ WITHIN_RADIUS_CENTER_DEPTH_EXCEEDED: KonectyWithinRadiusCenterDepthError,
220
+ WITHIN_RADIUS_TOO_MANY_CENTERS: KonectyWithinRadiusTooManyCentersError,
221
+ DISTANCE_SORT_UNAVAILABLE: KonectyDistanceSortUnavailableError,
222
+ }
223
+
224
+
66
225
  def raise_for_konecty_errors(errors: object) -> None:
67
226
  """
68
227
  Lança a exceção mais específica que a lista de ``errors`` da API permitir.
@@ -74,8 +233,15 @@ def raise_for_konecty_errors(errors: object) -> None:
74
233
  """
75
234
  items = errors if isinstance(errors, list) else []
76
235
  for error in items:
77
- if isinstance(error, dict) and error.get("code") == SORT_ABOVE_MAX_PAGE_SIZE:
78
- raise KonectySortLimitError(str(error.get("message", SORT_ABOVE_MAX_PAGE_SIZE)))
236
+ if not isinstance(error, dict):
237
+ continue
238
+ exception_class = _ERROR_BY_CODE.get(error.get("code"))
239
+ if exception_class is not None:
240
+ # `message` ausente OU `None`: cai no default da própria exceção, como o SDK
241
+ # TS faz (`new Klass(message ?? undefined)` usa o default do construtor).
242
+ # `str(None)` produziria a string literal "None" na mensagem do chamador.
243
+ message = error.get("message")
244
+ raise exception_class(message) if message else exception_class()
79
245
 
80
246
  raise KonectyAPIError(errors)
81
247
 
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Optional, Union
3
+ from typing import Any, Dict, List, Mapping, Optional, TypedDict, Union
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
@@ -29,6 +29,97 @@ class FilterOperator(str, Enum):
29
29
  IN = "in"
30
30
  NOT_IN = "not_in"
31
31
  EXISTS = "exists"
32
+ #: Busca por raio geográfico em campo de tipo ``address``. Espelha
33
+ #: ``WITHIN_RADIUS`` no SDK TypeScript — manter os dois em sincronia.
34
+ WITHIN_RADIUS = "within_radius"
35
+
36
+
37
+ #: Campo calculado que o servidor acrescenta a cada registro devolvido por ``find``
38
+ #: quando o filtro tem **um** centro de distância: a única condição ``within_radius``
39
+ #: habilitada no caminho AND do filtro da requisição. Valor em **metros**, inteiro.
40
+ #: Com zero ou duas+ condições nessa posição, ou quando o usuário não pode ler o campo
41
+ #: ``address`` do centro, o campo simplesmente não vem — não é erro.
42
+ #:
43
+ #: É também o ``property`` de ordenação por distância:
44
+ #: ``SortOrder(property=DISTANCE_FIELD, direction=SortDirection.ASC)``.
45
+ #:
46
+ #: Nunca é gravado: devolver o registro inteiro num ``update`` com ``_distance``
47
+ #: dentro é recusado pelo servidor como campo inexistente.
48
+ #:
49
+ #: Espelha ``DISTANCE_FIELD`` no SDK TypeScript (``src/sdk/filters/withinRadius.ts``).
50
+ DISTANCE_FIELD = "_distance"
51
+
52
+ #: Referência ao registro de onde tirar o centro, em vez de coordenadas literais:
53
+ #: "perto do empreendimento X". O servidor lê o registro sob controle de acesso
54
+ #: completo e substitui pelas coordenadas antes de compilar a query.
55
+ #:
56
+ #: ``field`` é obrigatório porque um documento pode ter mais de um campo
57
+ #: ``address`` e adivinhar seria escolher em silêncio.
58
+ WithinRadiusCenterRef = TypedDict(
59
+ "WithinRadiusCenterRef",
60
+ {"document": str, "_id": str, "field": str},
61
+ )
62
+
63
+
64
+ def within_radius_condition(
65
+ term: str,
66
+ *,
67
+ lat: Optional[float] = None,
68
+ lng: Optional[float] = None,
69
+ record: Optional[WithinRadiusCenterRef] = None,
70
+ radius: float,
71
+ ) -> "FilterCondition":
72
+ """
73
+ Monta a condição de filtro do operador ``within_radius``.
74
+
75
+ O ``term`` é o campo ``address`` puro: o sufixo ``.geolocation`` é
76
+ acrescentado pelo SERVIDOR, não pelo cliente. O ``radius`` é em **metros**.
77
+ O centro é ``lat``/``lng`` **ou** ``record`` — exatamente uma das duas formas.
78
+
79
+ .. code-block:: python
80
+
81
+ within_radius_condition("address", lat=-30.0346, lng=-51.2177, radius=5000)
82
+ # value={"lat": -30.0346, "lng": -51.2177, "radius": 5000}
83
+
84
+ within_radius_condition(
85
+ "address",
86
+ record={"document": "Development", "_id": "<id>", "field": "address"},
87
+ radius=2000,
88
+ )
89
+
90
+ Todos os parâmetros depois de ``term`` são nomeados: a forma antiga
91
+ ``within_radius_condition(term, (lng, lat), radius)`` deixou de existir e
92
+ levanta ``TypeError``.
93
+
94
+ O SDK **não** valida faixa, teto de raio nem a exclusão mútua das formas:
95
+ quem decide é o servidor, e um teto copiado aqui passaria a mentir assim que
96
+ o backend mudasse. O que o chamador passou vai para a requisição — as duas
97
+ formas juntas inclusive, para o servidor recusar nomeando o problema — e
98
+ nada é convertido para número. Valor recusado volta como
99
+ ``WITHIN_RADIUS_INVALID_VALUE``.
100
+
101
+ Espelha ``withinRadiusCondition`` no SDK TypeScript — a mesma entrada produz
102
+ a mesma saída (travado por teste: ``tests/test_within_radius.py`` e
103
+ ``src/__test__/api/withinRadius.test.ts``).
104
+
105
+ A ARIDADE difere de propósito: lá é ``withinRadiusCondition(term, {lat, lng,
106
+ radius})``, porque o SDK TypeScript passa objeto de opções em todo lugar;
107
+ aqui são parâmetros nomeados, que é a convenção deste SDK. A paridade que
108
+ importa é mesma entrada → mesma saída, não a forma de chamar.
109
+ """
110
+ value: Dict[str, Any] = {}
111
+ if lat is not None:
112
+ value["lat"] = lat
113
+ if lng is not None:
114
+ value["lng"] = lng
115
+ if record is not None:
116
+ value["record"] = dict(record) if isinstance(record, Mapping) else record
117
+ value["radius"] = radius
118
+ return FilterCondition(
119
+ term=term,
120
+ operator=FilterOperator.WITHIN_RADIUS,
121
+ value=value,
122
+ )
32
123
 
33
124
 
34
125
  class DateValue(BaseModel):
@@ -57,13 +148,17 @@ class KonectyFilter(BaseModel):
57
148
  """Filtro Konecty."""
58
149
 
59
150
  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")
151
+ conditions: List[FilterCondition] = Field(
152
+ default_factory=list, description="Lista de condições"
153
+ )
154
+ filters: List["KonectyFilter"] = Field(
155
+ default_factory=list, description="Lista de filtros aninhados"
156
+ )
62
157
 
63
158
  def to_json(self) -> Dict[str, Any]:
64
159
  """Converte o filtro para formato JSON."""
65
160
  return self.model_dump(mode="json")
66
-
161
+
67
162
  def is_empty(self) -> bool:
68
163
  """Verifica se o filtro está vazio."""
69
164
  return len(self.conditions) == 0 and len(self.filters) == 0
@@ -74,7 +169,9 @@ class KonectyFilter(BaseModel):
74
169
  return cls(**data)
75
170
 
76
171
  @classmethod
77
- def create(cls, match: Union[FilterMatch, str] = FilterMatch.AND) -> "KonectyFilter":
172
+ def create(
173
+ cls, match: Union[FilterMatch, str] = FilterMatch.AND
174
+ ) -> "KonectyFilter":
78
175
  """Cria uma nova instância de filtro.
79
176
 
80
177
  Args:
@@ -88,7 +185,11 @@ class KonectyFilter(BaseModel):
88
185
  return cls(match=match)
89
186
 
90
187
  def add_condition(
91
- self, term: str, operator: Union[FilterOperator, str], value: Any, disabled: bool = False
188
+ self,
189
+ term: str,
190
+ operator: Union[FilterOperator, str],
191
+ value: Any,
192
+ disabled: bool = False,
92
193
  ) -> "KonectyFilter":
93
194
  """Adiciona uma condição ao filtro.
94
195
 
@@ -114,7 +215,42 @@ class KonectyFilter(BaseModel):
114
215
  )
115
216
  return self
116
217
 
117
- def add_filter(self, match: Union[FilterMatch, str] = FilterMatch.AND) -> "KonectyFilter":
218
+ def add_within_radius(
219
+ self,
220
+ term: str,
221
+ *,
222
+ lat: Optional[float] = None,
223
+ lng: Optional[float] = None,
224
+ record: Optional[WithinRadiusCenterRef] = None,
225
+ radius: float,
226
+ ) -> "KonectyFilter":
227
+ """Adiciona uma condição ``within_radius`` ao filtro.
228
+
229
+ Args:
230
+ term: Campo de tipo ``address`` (sem o sufixo ``.geolocation``)
231
+ lat: Latitude do centro, em ``[-90, 90]``
232
+ lng: Longitude do centro, em ``[-180, 180]``
233
+ record: Ou, no lugar de ``lat``/``lng``, a referência
234
+ ``{"document", "_id", "field"}`` a outro registro
235
+ radius: Raio em **metros**
236
+
237
+ Returns:
238
+ Self para encadeamento
239
+
240
+ Nota: o SDK TypeScript **não** tem equivalente disto, e é escolha: lá o
241
+ filtro é objeto literal, não há classe de builder nenhuma, e criar uma só
242
+ para este operador seria superfície pública nova sem caso de uso.
243
+ """
244
+ self.conditions.append(
245
+ within_radius_condition(
246
+ term, lat=lat, lng=lng, record=record, radius=radius
247
+ )
248
+ )
249
+ return self
250
+
251
+ def add_filter(
252
+ self, match: Union[FilterMatch, str] = FilterMatch.AND
253
+ ) -> "KonectyFilter":
118
254
  """Adiciona um filtro aninhado.
119
255
 
120
256
  Args:
@@ -1,8 +1,10 @@
1
- """Auth API: Google hosted authorization code flow and login options."""
1
+ """Auth API: Google hosted authorization code flow, login options and session info."""
2
2
 
3
3
  from typing import Any, Dict, Optional, Union
4
4
  from urllib.parse import quote, urlencode
5
5
 
6
+ import aiohttp
7
+
6
8
  from ..exceptions import KonectyAPIError, KonectyGoogleSessionError
7
9
  from .base import BaseService
8
10
 
@@ -13,6 +15,7 @@ GOOGLE_START_PATH = "/api/auth/google/start"
13
15
  _ENCODE_URI_COMPONENT_SAFE = "!*'()"
14
16
  GOOGLE_SESSION_PATH = "/api/auth/google/session"
15
17
  LOGIN_OPTIONS_PATH = "/api/auth/login-options"
18
+ USER_INFO_PATH = "/rest/auth/info"
16
19
 
17
20
 
18
21
  _GOOGLE_SESSION_ERROR_CODES = (
@@ -132,3 +135,27 @@ class AuthService(BaseService):
132
135
  """GET /api/auth/login-options. Flags: passwordEnabled, emailOtpEnabled,
133
136
  whatsAppOtpEnabled, webauthnEnabled, webauthnRequired, googleEnabled."""
134
137
  return await self._get(LOGIN_OPTIONS_PATH)
138
+
139
+ async def info(self) -> Dict[str, Any]:
140
+ """
141
+ GET /rest/auth/info. The session user and namespace: ``{"logged": True, "user": {...}}``.
142
+
143
+ ``user`` carries ``_id``, ``name``, ``email``, ``login``, ``group``, ``role``, ``access``,
144
+ ``locale``, ``namespace`` and ``redirectURL`` — the page to open the interface on, or
145
+ None when the user record sets none. Keys are the wire names, as in the TypeScript SDK.
146
+
147
+ Same contract as ``KonectyClient.info()`` in the TypeScript SDK: without a token there is
148
+ no session to ask about, so no request goes out; a 4xx/5xx answer means not logged. Both
149
+ return ``{"logged": False}``.
150
+ """
151
+ if not self._client.auth_id:
152
+ return {"logged": False}
153
+ try:
154
+ return await self._get(USER_INFO_PATH)
155
+ except KonectyAPIError:
156
+ return {"logged": False}
157
+ except aiohttp.ContentTypeError as error:
158
+ # 401 sem corpo JSON: o http layer tenta ler JSON antes de olhar o status.
159
+ if error.status >= 400:
160
+ return {"logged": False}
161
+ raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: konecty_sdk_python
3
- Version: 2.1.6
3
+ Version: 2.1.7
4
4
  Summary: Konecty SDK Python
5
5
  Author-email: Leonardo Leal <leonardo.leal@konecty.com>, Derotino Silveira <derotino.silveira@konecty.com>
6
6
  License: MIT
@@ -4,10 +4,10 @@ KonectySdkPython/cli/apply.py,sha256=OSCsMZ6t8TCWvdlHksjUmTESjfZQV7FFvVvA0K6dL6w
4
4
  KonectySdkPython/cli/backup.py,sha256=rcla3EWfPsy-vWgmDK844EaM5u3h7JF-u7-b2HUBMgA,4785
5
5
  KonectySdkPython/cli/pull.py,sha256=n4gIkKtuEpPKULb3zcYu5tZlBLbt_4oNQDeu2l6tHxU,9529
6
6
  KonectySdkPython/lib/__init__.py,sha256=Du8jLNfFG1bQuHX5u7pAhbyV9TjC7Ie9K1Xo1KSUqqc,995
7
- KonectySdkPython/lib/client.py,sha256=eazEV9hFz6Tm1Y8UDNCBKhC_J8qaUPMVYxymHsz0NNo,42575
8
- KonectySdkPython/lib/exceptions.py,sha256=WkXRm0ZzKZdOdrHmgGxB_T_yvLH6agsHTsph3bTA0Vs,3251
7
+ KonectySdkPython/lib/client.py,sha256=hjF9lVLXBnuBTRJn_poCMrsx0dz2t5f1dhU8uE4-Yzg,43972
8
+ KonectySdkPython/lib/exceptions.py,sha256=8qKTQMbbzTpAwJdTobv8Q6ewKkxGatadWgNPTyG9bPg,11043
9
9
  KonectySdkPython/lib/file_manager.py,sha256=CLpOJWdA1tb2iq0qOIeRyEEIuACRq9tIo7Rls1YT2ig,9986
10
- KonectySdkPython/lib/filters.py,sha256=PsFaUFCEyKgxgzD566LdTLvuMJevoF_i9U1WnpJZzPQ,4482
10
+ KonectySdkPython/lib/filters.py,sha256=1JPNqxFDJKOr9zgdcaYtUHTWf3M_I7xNDGXif2Pvsyo,9677
11
11
  KonectySdkPython/lib/http.py,sha256=lHb1GFN9OBGgZcC9TkP57S-KqnmeHbOzizq72OvChgk,3571
12
12
  KonectySdkPython/lib/model.py,sha256=8ok1VzPGpXQklz2fJwjP8HT0aKOzhtE93BzcgfVw4zY,2846
13
13
  KonectySdkPython/lib/serialization.py,sha256=HxA2t7uPv-4aqkGHMUF8WgP1RcTg6RXsFmr-nUhtx7E,401
@@ -20,7 +20,7 @@ KonectySdkPython/lib/feature_types/query_json.py,sha256=yZgMPmkMdiBLm-dIrmU1AeZe
20
20
  KonectySdkPython/lib/services/__init__.py,sha256=fluQuxs6LnIZwcjYTuKNeXCFTKuNF3At3X19fIB68p8,121
21
21
  KonectySdkPython/lib/services/admin.py,sha256=DyJWjDNVE9HSd3FaESLbMW6EviTwYbu6iocUhMMIlDc,10747
22
22
  KonectySdkPython/lib/services/aggregation.py,sha256=igK1mleMFU2_rDQCAU1yKyvDoOjhTO1SO0Nn4T_DokA,5610
23
- KonectySdkPython/lib/services/auth.py,sha256=seb2QgdeWT_ALcVLR8bDMgO1ycI9dz75OhuIjahzy7o,5173
23
+ KonectySdkPython/lib/services/auth.py,sha256=3OSNgH2oTFlVsW5lL5i3LfDN5gv8-W0mas0slzGpNrw,6370
24
24
  KonectySdkPython/lib/services/base.py,sha256=tdVyWb7Y77cBhVc8Mz2BQMN6kAyZIohoxBxdwDnRsYs,1820
25
25
  KonectySdkPython/lib/services/change_user.py,sha256=ce6mcDYMP6eWxW0bEtzlVf7wbppGHCkGwdgo2jJFJOQ,2548
26
26
  KonectySdkPython/lib/services/comments.py,sha256=RNevCg4de2FWNk7mD60-pk3r8f2sJ1NLoolD2iNMidg,3033
@@ -31,7 +31,7 @@ KonectySdkPython/lib/services/pat.py,sha256=yelMEubMX4G5aB5-4h-QAYjeQrqJp48AsapM
31
31
  KonectySdkPython/lib/services/query.py,sha256=vCqbf0QdV8Blh8Hfsvq6Ne1-YFuC5siw1gVa7ZBGs_k,6605
32
32
  KonectySdkPython/lib/services/stream.py,sha256=1DMVxcg9Ymk2NgX0Qfvpjrr6vZJo5iFovPFT1NA3_CY,4737
33
33
  KonectySdkPython/lib/services/subscriptions.py,sha256=_Jq8jW7YkL_hwLNte1bWgrr4D2EMZUUSOXUgnRoGGK4,1022
34
- konecty_sdk_python-2.1.6.dist-info/METADATA,sha256=vGd9Fznf_FDSBFBqP8DTd4-wlgV9tQ2bltyZTynXkMg,5442
35
- konecty_sdk_python-2.1.6.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
- konecty_sdk_python-2.1.6.dist-info/entry_points.txt,sha256=awf962DamFgy791G3PsJ354edBMLJg16Dqi-2Y1tb9s,58
37
- konecty_sdk_python-2.1.6.dist-info/RECORD,,
34
+ konecty_sdk_python-2.1.7.dist-info/METADATA,sha256=S7xMz6v-HoTqLOyMqJOHVRxo6pfLXIbTuXPTVLc8SMU,5442
35
+ konecty_sdk_python-2.1.7.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
36
+ konecty_sdk_python-2.1.7.dist-info/entry_points.txt,sha256=awf962DamFgy791G3PsJ354edBMLJg16Dqi-2Y1tb9s,58
37
+ konecty_sdk_python-2.1.7.dist-info/RECORD,,