pangea-sdk 3.9.0__py3-none-any.whl → 4.0.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.
@@ -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
pangea/services/authz.py CHANGED
@@ -113,7 +113,7 @@ class CheckRequest(APIRequestModel):
113
113
  class DebugPath(APIResponseModel):
114
114
  type: str
115
115
  id: str
116
- action: Optional[str]
116
+ action: Optional[str] = None
117
117
 
118
118
 
119
119
  class Debug(APIResponseModel):
@@ -202,7 +202,7 @@ class AuthZ(ServiceBase):
202
202
  """
203
203
 
204
204
  input_data = TupleCreateRequest(tuples=tuples)
205
- return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.dict(exclude_none=True))
205
+ return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True))
206
206
 
207
207
  def tuple_list(
208
208
  self,
@@ -237,9 +237,9 @@ class AuthZ(ServiceBase):
237
237
  authz.tuple_list(TupleListFilter(subject_type="user", subject_id="user_1"))
238
238
  """
239
239
  input_data = TupleListRequest(
240
- filter=filter.dict(exclude_none=True), size=size, last=last, order=order, order_by=order_by
240
+ filter=filter.model_dump(exclude_none=True), size=size, last=last, order=order, order_by=order_by
241
241
  )
242
- return self.request.post("v1/tuple/list", TupleListResult, data=input_data.dict(exclude_none=True))
242
+ return self.request.post("v1/tuple/list", TupleListResult, data=input_data.model_dump(exclude_none=True))
243
243
 
244
244
  def tuple_delete(self, tuples: List[Tuple]) -> PangeaResponse[TupleDeleteResult]:
245
245
  """Delete tuples.
@@ -270,7 +270,7 @@ class AuthZ(ServiceBase):
270
270
  """
271
271
 
272
272
  input_data = TupleDeleteRequest(tuples=tuples)
273
- return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.dict(exclude_none=True))
273
+ return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.model_dump(exclude_none=True))
274
274
 
275
275
  def check(
276
276
  self,
@@ -309,7 +309,7 @@ class AuthZ(ServiceBase):
309
309
  """
310
310
 
311
311
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
312
- return self.request.post("v1/check", CheckResult, data=input_data.dict(exclude_none=True))
312
+ return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
313
313
 
314
314
  def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
315
315
  """List resources.
@@ -339,7 +339,9 @@ class AuthZ(ServiceBase):
339
339
  """
340
340
 
341
341
  input_data = ListResourcesRequest(type=type, action=action, subject=subject)
342
- return self.request.post("v1/list-resources", ListResourcesResult, data=input_data.dict(exclude_none=True))
342
+ return self.request.post(
343
+ "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
344
+ )
343
345
 
344
346
  def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
345
347
  """List subjects.
@@ -367,4 +369,4 @@ class AuthZ(ServiceBase):
367
369
  """
368
370
 
369
371
  input_data = ListSubjectsRequest(resource=resource, action=action)
370
- return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.dict(exclude_none=True))
372
+ return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True))
@@ -103,7 +103,7 @@ class Embargo(ServiceBase):
103
103
  response = embargo.ip_check("190.6.64.94")
104
104
  """
105
105
  input = IPCheckRequest(ip=ip)
106
- return self.request.post("v1/ip/check", EmbargoResult, data=input.dict())
106
+ return self.request.post("v1/ip/check", EmbargoResult, data=input.model_dump())
107
107
 
108
108
  def iso_check(self, iso_code: str) -> PangeaResponse[EmbargoResult]:
109
109
  """
@@ -130,4 +130,4 @@ class Embargo(ServiceBase):
130
130
  response = embargo.iso_check("CU")
131
131
  """
132
132
  input = ISOCheckRequest(iso_code=iso_code)
133
- return self.request.post("v1/iso/check", result_class=EmbargoResult, data=input.dict())
133
+ return self.request.post("v1/iso/check", result_class=EmbargoResult, data=input.model_dump())
@@ -142,7 +142,7 @@ class FileScan(ServiceBase):
142
142
  transfer_method=transfer_method,
143
143
  source_url=source_url,
144
144
  )
145
- data = input.dict(exclude_none=True)
145
+ data = input.model_dump(exclude_none=True)
146
146
  return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
147
147
 
148
148
  def request_upload_url(
@@ -164,7 +164,7 @@ class FileScan(ServiceBase):
164
164
  input.sha256 = params.sha256_hex
165
165
  input.size = params.size
166
166
 
167
- data = input.dict(exclude_none=True)
167
+ data = input.model_dump(exclude_none=True)
168
168
  return self.request.request_presigned_url("v1/scan", FileScanResult, data=data)
169
169
 
170
170