pangea-sdk 3.9.0__py3-none-any.whl → 4.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.
pangea/request.py CHANGED
@@ -5,7 +5,7 @@ import copy
5
5
  import json
6
6
  import logging
7
7
  import time
8
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
8
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Type, Union
9
9
 
10
10
  import requests
11
11
  from requests.adapters import HTTPAdapter, Retry
@@ -190,7 +190,7 @@ class PangeaRequestBase(object):
190
190
  raise pe.PangeaAPIException(f"{summary} ", response)
191
191
 
192
192
 
193
- TResult = TypeVar("TResult", bound=PangeaResponseResult, default=PangeaResponseResult)
193
+ TResult = TypeVar("TResult", bound=PangeaResponseResult)
194
194
 
195
195
 
196
196
  class PangeaRequest(PangeaRequestBase):
@@ -324,14 +324,17 @@ class PangeaRequest(PangeaRequestBase):
324
324
  return self.session.post(url, headers=headers, data=data_send, files=files)
325
325
 
326
326
  def _http_post_process(
327
- self, data: Union[str, Dict] = {}, files: Optional[List[Tuple]] = None, multipart_post: bool = True
327
+ self,
328
+ data: Union[str, Dict] = {},
329
+ files: Optional[Sequence[Tuple[str, Tuple[Any, str, str]]]] = None,
330
+ multipart_post: bool = True,
328
331
  ):
329
332
  if files:
330
333
  if multipart_post is True:
331
334
  data_send: str = json.dumps(data, default=default_encoder) if isinstance(data, dict) else data
332
335
  multi = [("request", (None, data_send, "application/json"))]
333
- multi.extend(files) # type: ignore[arg-type]
334
- files = multi # type: ignore[assignment]
336
+ multi.extend(files)
337
+ files = multi
335
338
  return None, files
336
339
  else:
337
340
  # Post to presigned url as form
pangea/response.py CHANGED
@@ -7,8 +7,8 @@ from typing import Any, Dict, Generic, List, Optional, Type, Union
7
7
 
8
8
  import aiohttp
9
9
  import requests
10
- from pydantic import BaseModel
11
- from typing_extensions import TypeVar
10
+ from pydantic import BaseModel, ConfigDict, PlainSerializer
11
+ from typing_extensions import Annotated, TypeVar
12
12
 
13
13
  from pangea.utils import format_datetime
14
14
 
@@ -50,24 +50,17 @@ class TransferMethod(str, enum.Enum):
50
50
  return str(self.value)
51
51
 
52
52
 
53
+ PangeaDateTime = Annotated[datetime.datetime, PlainSerializer(format_datetime)]
54
+
55
+
53
56
  # API response should accept arbitrary fields to make them accept possible new parameters
54
57
  class APIResponseModel(BaseModel):
55
- class Config:
56
- arbitrary_types_allowed = True
57
- # allow parameters despite they are not declared in model. Make SDK accept server new parameters
58
- extra = "allow"
58
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
59
59
 
60
60
 
61
61
  # API request models doesn't not allow arbitrary fields
62
62
  class APIRequestModel(BaseModel):
63
- class Config:
64
- arbitrary_types_allowed = True
65
- extra = (
66
- "allow" # allow parameters despite they are not declared in model. Make SDK accept server new parameters
67
- )
68
- json_encoders = {
69
- datetime.datetime: format_datetime,
70
- }
63
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
71
64
 
72
65
 
73
66
  class PangeaResponseResult(APIResponseModel):
@@ -169,10 +162,10 @@ class ResponseHeader(APIResponseModel):
169
162
  """
170
163
 
171
164
 
172
- T = TypeVar("T", bound=PangeaResponseResult, default=PangeaResponseResult)
165
+ T = TypeVar("T", bound=PangeaResponseResult)
173
166
 
174
167
 
175
- class PangeaResponse(Generic[T], ResponseHeader):
168
+ class PangeaResponse(ResponseHeader, Generic[T]):
176
169
  raw_result: Optional[Dict[str, Any]] = None
177
170
  raw_response: Optional[Union[requests.Response, aiohttp.ClientResponse]] = None
178
171
  result: Optional[T] = None
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import datetime
6
6
  import json
7
- from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Union
7
+ from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
8
8
 
9
9
  import pangea.exceptions as pexc
10
10
  from pangea.config import PangeaConfig
@@ -199,7 +199,7 @@ class AuditBase:
199
199
 
200
200
  # verify consistency proofs
201
201
  if self.can_verify_consistency_proof(search_event):
202
- if self.verify_consistency_proof(self.pub_roots, search_event):
202
+ if search_event.leaf_index is not None and self.verify_consistency_proof(search_event.leaf_index):
203
203
  search_event.consistency_verification = EventVerification.PASS
204
204
  else:
205
205
  search_event.consistency_verification = EventVerification.FAIL
@@ -222,7 +222,7 @@ class AuditBase:
222
222
  tree_sizes.difference_update(self.pub_roots.keys())
223
223
 
224
224
  if tree_sizes:
225
- arweave_roots = get_arweave_published_roots(result.root.tree_name, list(tree_sizes))
225
+ arweave_roots = get_arweave_published_roots(result.root.tree_name, tree_sizes)
226
226
  else:
227
227
  arweave_roots = {}
228
228
 
@@ -284,7 +284,7 @@ class AuditBase:
284
284
  """
285
285
  return event.published and event.leaf_index is not None and event.leaf_index >= 0 # type: ignore[return-value]
286
286
 
287
- def verify_consistency_proof(self, pub_roots: Dict[int, PublishedRoot], event: SearchEvent) -> bool:
287
+ def verify_consistency_proof(self, tree_size: int) -> bool:
288
288
  """
289
289
  Verify consistency proof
290
290
 
@@ -293,18 +293,17 @@ class AuditBase:
293
293
  Read more at: [What is a consistency proof?](https://pangea.cloud/docs/audit/merkle-trees#what-is-a-consistency-proof)
294
294
 
295
295
  Args:
296
- pub_roots (dict[int, Root]): list of published root hashes across time
297
- event (SearchEvent): Audit event to be verified.
296
+ leaf_index (int): The tree size of the root to be verified.
298
297
 
299
298
  Returns:
300
299
  bool: True if consistency proof is verified, False otherwise.
301
300
  """
302
301
 
303
- if event.leaf_index == 0:
302
+ if tree_size == 0:
304
303
  return True
305
304
 
306
- curr_root = pub_roots.get(event.leaf_index + 1) # type: ignore[operator]
307
- prev_root = pub_roots.get(event.leaf_index) # type: ignore[arg-type]
305
+ curr_root = self.pub_roots.get(tree_size + 1)
306
+ prev_root = self.pub_roots.get(tree_size)
308
307
 
309
308
  if not curr_root or not prev_root:
310
309
  return False
@@ -314,9 +313,12 @@ class AuditBase:
314
313
  ):
315
314
  return False
316
315
 
316
+ if curr_root.consistency_proof is None:
317
+ return False
318
+
317
319
  curr_root_hash = decode_hash(curr_root.root_hash)
318
320
  prev_root_hash = decode_hash(prev_root.root_hash)
319
- proof = decode_consistency_proof(curr_root.consistency_proof) # type: ignore[arg-type]
321
+ proof = decode_consistency_proof(curr_root.consistency_proof)
320
322
 
321
323
  return verify_consistency_proof(curr_root_hash, prev_root_hash, proof)
322
324
 
@@ -519,7 +521,9 @@ class Audit(ServiceBase, AuditBase):
519
521
  """
520
522
 
521
523
  input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
522
- response: PangeaResponse[LogResult] = self.request.post("v1/log", LogResult, data=input.dict(exclude_none=True))
524
+ response: PangeaResponse[LogResult] = self.request.post(
525
+ "v1/log", LogResult, data=input.model_dump(exclude_none=True)
526
+ )
523
527
  if response.success and response.result is not None:
524
528
  self._process_log_result(response.result, verify=verify)
525
529
  return response
@@ -559,7 +563,7 @@ class Audit(ServiceBase, AuditBase):
559
563
 
560
564
  input = self._get_log_request(events, sign_local=sign_local, verify=False, verbose=verbose)
561
565
  response: PangeaResponse[LogBulkResult] = self.request.post(
562
- "v2/log", LogBulkResult, data=input.dict(exclude_none=True)
566
+ "v2/log", LogBulkResult, data=input.model_dump(exclude_none=True)
563
567
  )
564
568
 
565
569
  if response.success and response.result is not None:
@@ -603,7 +607,7 @@ class Audit(ServiceBase, AuditBase):
603
607
  try:
604
608
  # Calling to v2 methods will return always a 202.
605
609
  response: PangeaResponse[LogBulkResult] = self.request.post(
606
- "v2/log_async", LogBulkResult, data=input.dict(exclude_none=True), poll_result=False
610
+ "v2/log_async", LogBulkResult, data=input.model_dump(exclude_none=True), poll_result=False
607
611
  )
608
612
  except pexc.AcceptedRequestException as e:
609
613
  return e.response
@@ -694,7 +698,7 @@ class Audit(ServiceBase, AuditBase):
694
698
  )
695
699
 
696
700
  response: PangeaResponse[SearchOutput] = self.request.post(
697
- "v1/search", SearchOutput, data=input.dict(exclude_none=True)
701
+ "v1/search", SearchOutput, data=input.model_dump(exclude_none=True)
698
702
  )
699
703
  if verify_consistency and response.result is not None:
700
704
  self.update_published_roots(response.result)
@@ -751,7 +755,7 @@ class Audit(ServiceBase, AuditBase):
751
755
  return_context=return_context,
752
756
  )
753
757
  response: PangeaResponse[SearchResultOutput] = self.request.post(
754
- "v1/results", SearchResultOutput, data=input.dict(exclude_none=True)
758
+ "v1/results", SearchResultOutput, data=input.model_dump(exclude_none=True)
755
759
  )
756
760
  if verify_consistency and response.result is not None:
757
761
  self.update_published_roots(response.result)
@@ -813,7 +817,7 @@ class Audit(ServiceBase, AuditBase):
813
817
  )
814
818
  try:
815
819
  return self.request.post(
816
- "v1/export", PangeaResponseResult, data=input.dict(exclude_none=True), poll_result=False
820
+ "v1/export", PangeaResponseResult, data=input.model_dump(exclude_none=True), poll_result=False
817
821
  )
818
822
  except pexc.AcceptedRequestException as e:
819
823
  return e.response
@@ -880,7 +884,7 @@ class Audit(ServiceBase, AuditBase):
880
884
  response = audit.root(tree_size=7)
881
885
  """
882
886
  input = RootRequest(tree_size=tree_size)
883
- return self.request.post("v1/root", RootResult, data=input.dict(exclude_none=True))
887
+ return self.request.post("v1/root", RootResult, data=input.model_dump(exclude_none=True))
884
888
 
885
889
  def download_results(
886
890
  self,
@@ -922,7 +926,7 @@ class Audit(ServiceBase, AuditBase):
922
926
  input = DownloadRequest(
923
927
  request_id=request_id, result_id=result_id, format=format, return_context=return_context
924
928
  )
925
- return self.request.post("v1/download_results", DownloadResult, data=input.dict(exclude_none=True))
929
+ return self.request.post("v1/download_results", DownloadResult, data=input.model_dump(exclude_none=True))
926
930
 
927
931
  def update_published_roots(self, result: SearchResultOutput):
928
932
  """Fetches series of published root hashes from Arweave
@@ -947,12 +951,31 @@ class Audit(ServiceBase, AuditBase):
947
951
  for tree_size in tree_sizes:
948
952
  pub_root = None
949
953
  if tree_size in arweave_roots:
950
- pub_root = PublishedRoot(**arweave_roots[tree_size].dict(exclude_none=True))
954
+ pub_root = PublishedRoot(**arweave_roots[tree_size].model_dump(exclude_none=True))
951
955
  pub_root.source = RootSource.ARWEAVE
952
956
  elif self.allow_server_roots:
953
957
  resp = self.root(tree_size=tree_size)
954
958
  if resp.success and resp.result is not None:
955
- pub_root = PublishedRoot(**resp.result.data.dict(exclude_none=True))
959
+ pub_root = PublishedRoot(**resp.result.data.model_dump(exclude_none=True))
956
960
  pub_root.source = RootSource.PANGEA
957
961
  if pub_root is not None:
958
962
  self.pub_roots[tree_size] = pub_root
963
+
964
+ self.fix_consistency_proofs(tree_sizes)
965
+
966
+ def fix_consistency_proofs(self, tree_sizes: Iterable[int]):
967
+ # on very rare occasions, the consistency proof in Arweave may be wrong
968
+ # override it with the proof from pangea (not the root hash, just the proof)
969
+ for tree_size in tree_sizes:
970
+ if tree_size not in self.pub_roots or tree_size - 1 not in self.pub_roots:
971
+ continue
972
+
973
+ if self.pub_roots[tree_size].source == RootSource.PANGEA:
974
+ continue
975
+
976
+ if self.verify_consistency_proof(tree_size):
977
+ continue
978
+
979
+ resp = self.root(tree_size=tree_size)
980
+ if resp.success and resp.result is not None and resp.result.data is not None:
981
+ self.pub_roots[tree_size].consistency_proof = resp.result.data.consistency_proof
@@ -6,7 +6,7 @@ import datetime
6
6
  import enum
7
7
  from typing import Any, Dict, List, Optional, Sequence, Union
8
8
 
9
- from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
9
+ from pangea.response import APIRequestModel, APIResponseModel, PangeaDateTime, PangeaResponseResult
10
10
 
11
11
 
12
12
  class EventVerification(str, enum.Enum):
@@ -126,7 +126,7 @@ class EventEnvelope(APIResponseModel):
126
126
  event: Dict[str, Any]
127
127
  signature: Optional[str] = None
128
128
  public_key: Optional[str] = None
129
- received_at: datetime.datetime
129
+ received_at: PangeaDateTime
130
130
 
131
131
 
132
132
  class LogRequest(APIRequestModel):
@@ -410,7 +410,7 @@ class SearchOutput(SearchResultOutput):
410
410
  """
411
411
 
412
412
  id: str
413
- expires_at: datetime.datetime
413
+ expires_at: PangeaDateTime
414
414
 
415
415
 
416
416
  class SearchResultRequest(APIRequestModel):
@@ -7,7 +7,7 @@ from binascii import hexlify, unhexlify
7
7
  from dataclasses import dataclass
8
8
  from datetime import datetime
9
9
  from hashlib import sha256
10
- from typing import Dict, List, Optional
10
+ from typing import Collection, Dict, List, Optional
11
11
 
12
12
  import requests
13
13
 
@@ -61,7 +61,7 @@ def verify_hash(hash1: str, hash2: str) -> bool:
61
61
 
62
62
 
63
63
  def verify_envelope_hash(envelope: EventEnvelope, hash: str):
64
- return verify_hash(hash_dict(normalize_log(envelope.dict(exclude_none=True))), hash)
64
+ return verify_hash(hash_dict(normalize_log(envelope.model_dump(exclude_none=True))), hash)
65
65
 
66
66
 
67
67
  def canonicalize_event(event: Event) -> bytes:
@@ -192,7 +192,7 @@ def arweave_graphql_url():
192
192
  return f"{ARWEAVE_BASE_URL}/graphql"
193
193
 
194
194
 
195
- def get_arweave_published_roots(tree_name: str, tree_sizes: List[int]) -> Dict[int, PublishedRoot]:
195
+ def get_arweave_published_roots(tree_name: str, tree_sizes: Collection[int]) -> Dict[int, PublishedRoot]:
196
196
  if len(tree_sizes) == 0:
197
197
  return {}
198
198
 
@@ -96,7 +96,7 @@ class AuthN(ServiceBase):
96
96
  """
97
97
  input = m.SessionInvalidateRequest(session_id=session_id)
98
98
  return self.request.post(
99
- "v2/session/invalidate", m.SessionInvalidateResult, data=input.dict(exclude_none=True)
99
+ "v2/session/invalidate", m.SessionInvalidateResult, data=input.model_dump(exclude_none=True)
100
100
  )
101
101
 
102
102
  def list(
@@ -134,7 +134,7 @@ class AuthN(ServiceBase):
134
134
  filter = m.SessionListFilter(**filter)
135
135
 
136
136
  input = m.SessionListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
137
- return self.request.post("v2/session/list", m.SessionListResults, data=input.dict(exclude_none=True))
137
+ return self.request.post("v2/session/list", m.SessionListResults, data=input.model_dump(exclude_none=True))
138
138
 
139
139
  def logout(self, user_id: str) -> PangeaResponse[m.SessionLogoutResult]:
140
140
  """
@@ -156,7 +156,9 @@ class AuthN(ServiceBase):
156
156
  )
157
157
  """
158
158
  input = m.SessionLogoutRequest(user_id=user_id)
159
- return self.request.post("v2/session/logout", m.SessionLogoutResult, data=input.dict(exclude_none=True))
159
+ return self.request.post(
160
+ "v2/session/logout", m.SessionLogoutResult, data=input.model_dump(exclude_none=True)
161
+ )
160
162
 
161
163
  class Client(ServiceBase):
162
164
  service_name = SERVICE_NAME
@@ -194,7 +196,9 @@ class AuthN(ServiceBase):
194
196
  )
195
197
  """
196
198
  input = m.ClientUserinfoRequest(code=code)
197
- return self.request.post("v2/client/userinfo", m.ClientUserinfoResult, data=input.dict(exclude_none=True))
199
+ return self.request.post(
200
+ "v2/client/userinfo", m.ClientUserinfoResult, data=input.model_dump(exclude_none=True)
201
+ )
198
202
 
199
203
  def jwks(
200
204
  self,
@@ -250,7 +254,9 @@ class AuthN(ServiceBase):
250
254
  """
251
255
  input = m.ClientSessionInvalidateRequest(token=token, session_id=session_id)
252
256
  return self.request.post(
253
- "v2/client/session/invalidate", m.ClientSessionInvalidateResult, data=input.dict(exclude_none=True)
257
+ "v2/client/session/invalidate",
258
+ m.ClientSessionInvalidateResult,
259
+ data=input.model_dump(exclude_none=True),
254
260
  )
255
261
 
256
262
  def list(
@@ -295,7 +301,7 @@ class AuthN(ServiceBase):
295
301
  token=token, filter=filter, last=last, order=order, order_by=order_by, size=size
296
302
  )
297
303
  return self.request.post(
298
- "v2/client/session/list", m.ClientSessionListResults, data=input.dict(exclude_none=True)
304
+ "v2/client/session/list", m.ClientSessionListResults, data=input.model_dump(exclude_none=True)
299
305
  )
300
306
 
301
307
  def logout(self, token: str) -> PangeaResponse[m.ClientSessionLogoutResult]:
@@ -319,7 +325,7 @@ class AuthN(ServiceBase):
319
325
  """
320
326
  input = m.ClientSessionLogoutRequest(token=token)
321
327
  return self.request.post(
322
- "v2/client/session/logout", m.ClientSessionLogoutResult, data=input.dict(exclude_none=True)
328
+ "v2/client/session/logout", m.ClientSessionLogoutResult, data=input.model_dump(exclude_none=True)
323
329
  )
324
330
 
325
331
  def refresh(
@@ -349,7 +355,7 @@ class AuthN(ServiceBase):
349
355
  """
350
356
  input = m.ClientSessionRefreshRequest(refresh_token=refresh_token, user_token=user_token)
351
357
  return self.request.post(
352
- "v2/client/session/refresh", m.ClientSessionRefreshResult, data=input.dict(exclude_none=True)
358
+ "v2/client/session/refresh", m.ClientSessionRefreshResult, data=input.model_dump(exclude_none=True)
353
359
  )
354
360
 
355
361
  class Password(ServiceBase):
@@ -390,7 +396,7 @@ class AuthN(ServiceBase):
390
396
  """
391
397
  input = m.ClientPasswordChangeRequest(token=token, old_password=old_password, new_password=new_password)
392
398
  return self.request.post(
393
- "v2/client/password/change", m.ClientPasswordChangeResult, data=input.dict(exclude_none=True)
399
+ "v2/client/password/change", m.ClientPasswordChangeResult, data=input.model_dump(exclude_none=True)
394
400
  )
395
401
 
396
402
  class Token(ServiceBase):
@@ -427,7 +433,7 @@ class AuthN(ServiceBase):
427
433
  """
428
434
  input = m.ClientTokenCheckRequest(token=token)
429
435
  return self.request.post(
430
- "v2/client/token/check", m.ClientTokenCheckResult, data=input.dict(exclude_none=True)
436
+ "v2/client/token/check", m.ClientTokenCheckResult, data=input.model_dump(exclude_none=True)
431
437
  )
432
438
 
433
439
  class User(ServiceBase):
@@ -482,7 +488,7 @@ class AuthN(ServiceBase):
482
488
  profile=profile,
483
489
  username=username,
484
490
  )
485
- return self.request.post("v2/user/create", m.UserCreateResult, data=input.dict(exclude_none=True))
491
+ return self.request.post("v2/user/create", m.UserCreateResult, data=input.model_dump(exclude_none=True))
486
492
 
487
493
  def delete(
488
494
  self, email: str | None = None, id: str | None = None, *, username: str | None = None
@@ -506,7 +512,7 @@ class AuthN(ServiceBase):
506
512
  authn.user.delete(email="example@example.com")
507
513
  """
508
514
  input = m.UserDeleteRequest(email=email, id=id, username=username)
509
- return self.request.post("v2/user/delete", m.UserDeleteResult, data=input.dict(exclude_none=True))
515
+ return self.request.post("v2/user/delete", m.UserDeleteResult, data=input.model_dump(exclude_none=True))
510
516
 
511
517
  def invite(
512
518
  self,
@@ -547,7 +553,7 @@ class AuthN(ServiceBase):
547
553
  callback=callback,
548
554
  state=state,
549
555
  )
550
- return self.request.post("v2/user/invite", m.UserInviteResult, data=input.dict(exclude_none=True))
556
+ return self.request.post("v2/user/invite", m.UserInviteResult, data=input.model_dump(exclude_none=True))
551
557
 
552
558
  def update(
553
559
  self,
@@ -592,7 +598,7 @@ class AuthN(ServiceBase):
592
598
  username=username,
593
599
  )
594
600
 
595
- return self.request.post("v2/user/update", m.UserUpdateResult, data=input.dict(exclude_none=True))
601
+ return self.request.post("v2/user/update", m.UserUpdateResult, data=input.model_dump(exclude_none=True))
596
602
 
597
603
  def list(
598
604
  self,
@@ -635,7 +641,7 @@ class AuthN(ServiceBase):
635
641
  order_by=order_by,
636
642
  size=size,
637
643
  )
638
- return self.request.post("v2/user/list", m.UserListResult, data=input.dict(exclude_none=True))
644
+ return self.request.post("v2/user/list", m.UserListResult, data=input.model_dump(exclude_none=True))
639
645
 
640
646
  class Invites(ServiceBase):
641
647
  service_name = SERVICE_NAME
@@ -682,7 +688,7 @@ class AuthN(ServiceBase):
682
688
 
683
689
  input = m.UserInviteListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
684
690
  return self.request.post(
685
- "v2/user/invite/list", m.UserInviteListResult, data=input.dict(exclude_none=True)
691
+ "v2/user/invite/list", m.UserInviteListResult, data=input.model_dump(exclude_none=True)
686
692
  )
687
693
 
688
694
  def delete(self, id: str) -> PangeaResponse[m.UserInviteDeleteResult]:
@@ -706,7 +712,7 @@ class AuthN(ServiceBase):
706
712
  """
707
713
  input = m.UserInviteDeleteRequest(id=id)
708
714
  return self.request.post(
709
- "v2/user/invite/delete", m.UserInviteDeleteResult, data=input.dict(exclude_none=True)
715
+ "v2/user/invite/delete", m.UserInviteDeleteResult, data=input.model_dump(exclude_none=True)
710
716
  )
711
717
 
712
718
  class Authenticators(ServiceBase):
@@ -756,7 +762,7 @@ class AuthN(ServiceBase):
756
762
  return self.request.post(
757
763
  "v2/user/authenticators/delete",
758
764
  m.UserAuthenticatorsDeleteResult,
759
- data=input.dict(exclude_none=True),
765
+ data=input.model_dump(exclude_none=True),
760
766
  )
761
767
 
762
768
  def list(
@@ -786,7 +792,9 @@ class AuthN(ServiceBase):
786
792
  """
787
793
  input = m.UserAuthenticatorsListRequest(email=email, id=id, username=username)
788
794
  return self.request.post(
789
- "v2/user/authenticators/list", m.UserAuthenticatorsListResult, data=input.dict(exclude_none=True)
795
+ "v2/user/authenticators/list",
796
+ m.UserAuthenticatorsListResult,
797
+ data=input.model_dump(exclude_none=True),
790
798
  )
791
799
 
792
800
  class Profile(ServiceBase):
@@ -827,7 +835,7 @@ class AuthN(ServiceBase):
827
835
  """
828
836
  input = m.UserProfileGetRequest(id=id, email=email, username=username)
829
837
  return self.request.post(
830
- "v2/user/profile/get", m.UserProfileGetResult, data=input.dict(exclude_none=True)
838
+ "v2/user/profile/get", m.UserProfileGetResult, data=input.model_dump(exclude_none=True)
831
839
  )
832
840
 
833
841
  def update(
@@ -871,7 +879,7 @@ class AuthN(ServiceBase):
871
879
  username=username,
872
880
  )
873
881
  return self.request.post(
874
- "v2/user/profile/update", m.UserProfileUpdateResult, data=input.dict(exclude_none=True)
882
+ "v2/user/profile/update", m.UserProfileUpdateResult, data=input.model_dump(exclude_none=True)
875
883
  )
876
884
 
877
885
  class Flow(ServiceBase):
@@ -907,7 +915,7 @@ class AuthN(ServiceBase):
907
915
  )
908
916
  """
909
917
  input = m.FlowCompleteRequest(flow_id=flow_id)
910
- return self.request.post("v2/flow/complete", m.FlowCompleteResult, data=input.dict(exclude_none=True))
918
+ return self.request.post("v2/flow/complete", m.FlowCompleteResult, data=input.model_dump(exclude_none=True))
911
919
 
912
920
  def restart(
913
921
  self, flow_id: str, choice: m.FlowChoice, data: m.FlowRestartData = {}
@@ -939,7 +947,7 @@ class AuthN(ServiceBase):
939
947
  """
940
948
 
941
949
  input = m.FlowRestartRequest(flow_id=flow_id, choice=choice, data=data)
942
- return self.request.post("v2/flow/restart", m.FlowRestartResult, data=input.dict(exclude_none=True))
950
+ return self.request.post("v2/flow/restart", m.FlowRestartResult, data=input.model_dump(exclude_none=True))
943
951
 
944
952
  def start(
945
953
  self,
@@ -978,7 +986,7 @@ class AuthN(ServiceBase):
978
986
  )
979
987
  """
980
988
  input = m.FlowStartRequest(cb_uri=cb_uri, email=email, flow_types=flow_types, invitation=invitation)
981
- return self.request.post("v2/flow/start", m.FlowStartResult, data=input.dict(exclude_none=True))
989
+ return self.request.post("v2/flow/start", m.FlowStartResult, data=input.model_dump(exclude_none=True))
982
990
 
983
991
  def update(
984
992
  self, flow_id: str, choice: m.FlowChoice, data: m.FlowUpdateData = {}
@@ -1012,7 +1020,7 @@ class AuthN(ServiceBase):
1012
1020
  """
1013
1021
 
1014
1022
  input = m.FlowUpdateRequest(flow_id=flow_id, choice=choice, data=data)
1015
- return self.request.post("v2/flow/update", m.FlowUpdateResult, data=input.dict(exclude_none=True))
1023
+ return self.request.post("v2/flow/update", m.FlowUpdateResult, data=input.model_dump(exclude_none=True))
1016
1024
 
1017
1025
  class Agreements(ServiceBase):
1018
1026
  service_name = SERVICE_NAME
@@ -1056,7 +1064,7 @@ class AuthN(ServiceBase):
1056
1064
 
1057
1065
  input = m.AgreementCreateRequest(type=type, name=name, text=text, active=active)
1058
1066
  return self.request.post(
1059
- "v2/agreements/create", m.AgreementCreateResult, data=input.dict(exclude_none=True)
1067
+ "v2/agreements/create", m.AgreementCreateResult, data=input.model_dump(exclude_none=True)
1060
1068
  )
1061
1069
 
1062
1070
  def delete(self, type: m.AgreementType, id: str) -> PangeaResponse[m.AgreementDeleteResult]:
@@ -1083,7 +1091,7 @@ class AuthN(ServiceBase):
1083
1091
 
1084
1092
  input = m.AgreementDeleteRequest(type=type, id=id)
1085
1093
  return self.request.post(
1086
- "v2/agreements/delete", m.AgreementDeleteResult, data=input.dict(exclude_none=True)
1094
+ "v2/agreements/delete", m.AgreementDeleteResult, data=input.model_dump(exclude_none=True)
1087
1095
  )
1088
1096
 
1089
1097
  def list(
@@ -1121,7 +1129,9 @@ class AuthN(ServiceBase):
1121
1129
  filter = m.AgreementListFilter(**filter)
1122
1130
 
1123
1131
  input = m.AgreementListRequest(filter=filter, last=last, order=order, order_by=order_by, size=size)
1124
- return self.request.post("v2/agreements/list", m.AgreementListResult, data=input.dict(exclude_none=True))
1132
+ return self.request.post(
1133
+ "v2/agreements/list", m.AgreementListResult, data=input.model_dump(exclude_none=True)
1134
+ )
1125
1135
 
1126
1136
  def update(
1127
1137
  self,
@@ -1161,5 +1171,5 @@ class AuthN(ServiceBase):
1161
1171
 
1162
1172
  input = m.AgreementUpdateRequest(type=type, id=id, name=name, text=text, active=active)
1163
1173
  return self.request.post(
1164
- "v2/agreements/update", m.AgreementUpdateResult, data=input.dict(exclude_none=True)
1174
+ "v2/agreements/update", m.AgreementUpdateResult, data=input.model_dump(exclude_none=True)
1165
1175
  )
@@ -1,10 +1,13 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
3
4
  from __future__ import annotations
4
5
 
5
6
  import enum
6
7
  from typing import Dict, List, NewType, Optional, Union
7
8
 
9
+ from pydantic import BaseModel, ConfigDict
10
+
8
11
  import pangea.services.intel as im
9
12
  from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
10
13
  from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
@@ -12,11 +15,13 @@ from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
12
15
  Scopes = NewType("Scopes", List[str])
13
16
 
14
17
 
15
- class Profile(Dict[str, str]):
18
+ class Profile(BaseModel):
16
19
  first_name: str
17
- last_name: str
20
+ last_name: Optional[str] = None
18
21
  phone: Optional[str] = None
19
22
 
23
+ model_config = ConfigDict(extra="allow")
24
+
20
25
 
21
26
  class ClientPasswordChangeRequest(APIRequestModel):
22
27
  token: str
@@ -70,7 +75,7 @@ class LoginToken(SessionToken):
70
75
 
71
76
 
72
77
  class ClientTokenCheckResult(SessionToken):
73
- token: Optional[str]
78
+ token: Optional[str] = None
74
79
 
75
80
 
76
81
  class IDProvider(str, enum.Enum):
@@ -324,7 +329,7 @@ class UserInviterOrderBy(enum.Enum):
324
329
 
325
330
 
326
331
  class UserInviteListFilter(APIRequestModel):
327
- callback: Optional[str]
332
+ callback: Optional[str] = None
328
333
  callback__contains: Optional[List[str]] = None
329
334
  callback__in: Optional[List[str]] = None
330
335
  created_at: Optional[str] = None