pangea-sdk 6.1.1__py3-none-any.whl → 6.2.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.
Files changed (50) hide show
  1. pangea/__init__.py +9 -1
  2. pangea/asyncio/__init__.py +1 -0
  3. pangea/asyncio/file_uploader.py +4 -2
  4. pangea/asyncio/request.py +52 -17
  5. pangea/asyncio/services/__init__.py +2 -0
  6. pangea/asyncio/services/ai_guard.py +9 -12
  7. pangea/asyncio/services/audit.py +6 -1
  8. pangea/asyncio/services/authn.py +15 -4
  9. pangea/asyncio/services/base.py +4 -0
  10. pangea/asyncio/services/file_scan.py +7 -1
  11. pangea/asyncio/services/intel.py +26 -28
  12. pangea/asyncio/services/redact.py +4 -0
  13. pangea/asyncio/services/sanitize.py +5 -1
  14. pangea/asyncio/services/share.py +5 -1
  15. pangea/asyncio/services/vault.py +4 -0
  16. pangea/audit_logger.py +3 -1
  17. pangea/deep_verify.py +13 -13
  18. pangea/deprecated.py +1 -1
  19. pangea/dump_audit.py +2 -3
  20. pangea/exceptions.py +8 -5
  21. pangea/file_uploader.py +4 -0
  22. pangea/request.py +63 -47
  23. pangea/response.py +21 -18
  24. pangea/services/__init__.py +2 -0
  25. pangea/services/ai_guard.py +35 -24
  26. pangea/services/audit/audit.py +10 -7
  27. pangea/services/audit/models.py +71 -34
  28. pangea/services/audit/signing.py +1 -1
  29. pangea/services/audit/util.py +10 -10
  30. pangea/services/authn/authn.py +15 -4
  31. pangea/services/authn/models.py +10 -56
  32. pangea/services/authz.py +4 -0
  33. pangea/services/base.py +7 -4
  34. pangea/services/embargo.py +6 -0
  35. pangea/services/file_scan.py +7 -1
  36. pangea/services/intel.py +36 -19
  37. pangea/services/redact.py +4 -0
  38. pangea/services/sanitize.py +5 -1
  39. pangea/services/share/share.py +13 -7
  40. pangea/services/vault/models/asymmetric.py +4 -0
  41. pangea/services/vault/models/common.py +4 -0
  42. pangea/services/vault/models/symmetric.py +4 -0
  43. pangea/services/vault/vault.py +2 -4
  44. pangea/tools.py +13 -9
  45. pangea/utils.py +3 -5
  46. pangea/verify_audit.py +23 -27
  47. {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0.dist-info}/METADATA +36 -17
  48. pangea_sdk-6.2.0.dist-info/RECORD +60 -0
  49. pangea_sdk-6.1.1.dist-info/RECORD +0 -60
  50. {pangea_sdk-6.1.1.dist-info → pangea_sdk-6.2.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,11 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
7
+ from __future__ import annotations
8
+
3
9
  import io
4
10
  import logging
5
11
  from typing import Dict, List, Optional, Tuple
@@ -133,7 +139,7 @@ class FileScan(ServiceBase):
133
139
  files: Optional[List[Tuple]] = None
134
140
  if file or file_path:
135
141
  if file_path:
136
- file = open(file_path, "rb")
142
+ file = open(file_path, "rb") # noqa: SIM115
137
143
  if transfer_method == TransferMethod.POST_URL:
138
144
  params = get_file_upload_params(file) # type: ignore[arg-type]
139
145
  crc = params.crc_hex
pangea/services/intel.py CHANGED
@@ -1,10 +1,14 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import enum
6
10
  import hashlib
7
- from typing import Dict, List, Optional
11
+ from typing import Dict, List, Literal, Optional
8
12
 
9
13
  from pangea.exceptions import PangeaException
10
14
  from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult
@@ -61,8 +65,20 @@ class FileReputationBulkRequest(APIRequestModel):
61
65
  hash_type (str): Type of hash, can be "sha256", "sha" or "md5"
62
66
  """
63
67
 
64
- hashes: List[str]
65
- hash_type: str
68
+ hashes: list[str]
69
+ """The hash of the file to be looked up"""
70
+
71
+ hash_type: Literal["sha256", "sha", "md5"]
72
+ """One of "sha256", "sha", "md5"."""
73
+
74
+ verbose: Optional[bool] = None
75
+ """Echo the API parameters in the response"""
76
+
77
+ raw: Optional[bool] = None
78
+ """Include raw data from this provider"""
79
+
80
+ provider: Optional[Literal["reversinglabs", "crowdstrike"]] = None
81
+ """Use reputation data from this provider"""
66
82
 
67
83
 
68
84
  class FileReputationData(IntelReputationData):
@@ -356,7 +372,8 @@ class DomainWhoIsRequest(DomainCommonRequest):
356
372
  Domain whois request data
357
373
  """
358
374
 
359
- pass
375
+ domain: str
376
+ """The domain to query."""
360
377
 
361
378
 
362
379
  class DomainWhoIsData(PangeaResponseResult):
@@ -530,11 +547,11 @@ class FileIntel(ServiceBase):
530
547
 
531
548
  def hash_reputation_bulk(
532
549
  self,
533
- hashes: List[str],
534
- hash_type: str,
535
- provider: Optional[str] = None,
536
- verbose: Optional[bool] = None,
537
- raw: Optional[bool] = None,
550
+ hashes: list[str],
551
+ hash_type: Literal["sha256", "sha", "md5"],
552
+ provider: Literal["reversinglabs", "crowdstrike"] | None = None,
553
+ verbose: bool | None = None,
554
+ raw: bool | None = None,
538
555
  ) -> PangeaResponse[FileReputationBulkResult]:
539
556
  """
540
557
  Reputation check V2
@@ -562,7 +579,7 @@ class FileIntel(ServiceBase):
562
579
  provider="reversinglabs",
563
580
  )
564
581
  """
565
- input = FileReputationBulkRequest( # type: ignore[call-arg]
582
+ input = FileReputationBulkRequest(
566
583
  hashes=hashes, hash_type=hash_type, verbose=verbose, raw=raw, provider=provider
567
584
  )
568
585
  return self.request.post("v2/reputation", FileReputationBulkResult, data=input.model_dump(exclude_none=True))
@@ -610,10 +627,10 @@ class FileIntel(ServiceBase):
610
627
 
611
628
  def filepath_reputation_bulk(
612
629
  self,
613
- filepaths: List[str],
614
- provider: Optional[str] = None,
615
- verbose: Optional[bool] = None,
616
- raw: Optional[bool] = None,
630
+ filepaths: list[str],
631
+ provider: Literal["reversinglabs", "crowdstrike"] | None = None,
632
+ verbose: bool | None = None,
633
+ raw: bool | None = None,
617
634
  ) -> PangeaResponse[FileReputationBulkResult]:
618
635
  """
619
636
  Reputation, from filepath V2
@@ -624,10 +641,10 @@ class FileIntel(ServiceBase):
624
641
  OperationId: file_intel_post_v2_reputation
625
642
 
626
643
  Args:
627
- filepaths (List[str]): The path list to the files to be looked up
628
- provider (str, optional): Use reputation data from these providers: "reversinglabs" or "crowdstrike"
629
- verbose (bool, optional): Echo the API parameters in the response
630
- raw (bool, optional): Include raw data from this provider
644
+ filepaths: The path list to the files to be looked up
645
+ provider: Use reputation data from these providers: "reversinglabs" or "crowdstrike"
646
+ verbose: Echo the API parameters in the response
647
+ raw: Include raw data from this provider
631
648
 
632
649
  Raises:
633
650
  PangeaAPIException: If an API Error happens
@@ -777,7 +794,7 @@ class DomainIntel(ServiceBase):
777
794
  provider="whoisxml",
778
795
  )
779
796
  """
780
- input = DomainWhoIsRequest(domain=domain, verbose=verbose, provider=provider, raw=raw) # type: ignore[call-arg]
797
+ input = DomainWhoIsRequest(domain=domain, verbose=verbose, provider=provider, raw=raw)
781
798
  return self.request.post("v1/whois", DomainWhoIsResult, data=input.model_dump(exclude_none=True))
782
799
 
783
800
 
pangea/services/redact.py CHANGED
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import enum
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import io
@@ -267,7 +271,7 @@ class Sanitize(ServiceBase):
267
271
  files: Optional[List[Tuple]] = None
268
272
  if file or file_path:
269
273
  if file_path:
270
- file = open(file_path, "rb")
274
+ file = open(file_path, "rb") # noqa: SIM115
271
275
  if (
272
276
  transfer_method == TransferMethod.POST_URL
273
277
  and file
@@ -1,10 +1,16 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import enum
6
10
  import io
7
- from typing import Dict, List, NewType, Optional, Tuple, Union
11
+ from typing import Dict, List, Optional, Tuple, Union
12
+
13
+ from pydantic import RootModel
8
14
 
9
15
  from pangea.config import PangeaConfig
10
16
  from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
@@ -12,8 +18,8 @@ from pangea.services.base import ServiceBase
12
18
  from pangea.services.share.file_format import FileFormat
13
19
  from pangea.utils import get_file_size, get_file_upload_params
14
20
 
15
- Metadata = NewType("Metadata", Dict[str, str])
16
- Tags = NewType("Tags", List[str])
21
+ Metadata = RootModel[dict[str, str]]
22
+ Tags = RootModel[list[str]]
17
23
 
18
24
 
19
25
  class ItemOrder(str, enum.Enum):
@@ -916,10 +922,10 @@ class Share(ServiceBase):
916
922
 
917
923
  def get_archive(
918
924
  self,
919
- ids: List[str] = [],
920
- format: Optional[ArchiveFormat] = None,
921
- transfer_method: Optional[TransferMethod] = None,
922
- bucket_id: Optional[str] = None,
925
+ ids: list[str],
926
+ format: ArchiveFormat | None = None,
927
+ transfer_method: TransferMethod | None = None,
928
+ bucket_id: str | None = None,
923
929
  ) -> PangeaResponse[GetArchiveResult]:
924
930
  """
925
931
  Get archive
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  from enum import Enum
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  import enum
@@ -1,5 +1,9 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+
4
+ # TODO: Use `list` instead of `List`.
5
+ # ruff: noqa: UP006, UP035
6
+
3
7
  from __future__ import annotations
4
8
 
5
9
  from enum import Enum
@@ -2,10 +2,9 @@
2
2
  # Author: Pangea Cyber Corporation
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, List, Literal, Optional, Union, cast, overload
5
+ from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional, Union, cast, overload
6
6
 
7
7
  from pydantic import Field, TypeAdapter
8
- from typing_extensions import Annotated
9
8
 
10
9
  from pangea.response import PangeaResponse, PangeaResponseResult
11
10
  from pangea.services.base import ServiceBase
@@ -34,7 +33,6 @@ from pangea.services.vault.models.common import (
34
33
  EncryptTransformRequest,
35
34
  EncryptTransformResult,
36
35
  ExportEncryptionAlgorithm,
37
- ExportEncryptionType,
38
36
  ExportRequest,
39
37
  ExportResult,
40
38
  Folder,
@@ -98,7 +96,7 @@ vault_item_adapter: TypeAdapter[VaultItem] = TypeAdapter(VaultItem)
98
96
 
99
97
 
100
98
  class GetBulkResponse(PangeaResponseResult):
101
- items: List[VaultItem]
99
+ items: list[VaultItem]
102
100
 
103
101
 
104
102
  class Vault(ServiceBase):
pangea/tools.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
3
4
 
4
5
  import enum
5
6
  import io
@@ -7,9 +8,10 @@ import json
7
8
  import logging
8
9
  import os
9
10
  import sys
11
+ from collections.abc import Iterator
10
12
  from datetime import datetime, timezone
11
13
  from logging.handlers import TimedRotatingFileHandler
12
- from typing import Dict, Iterator, List, Optional
14
+ from typing import Optional
13
15
 
14
16
  from pangea.config import PangeaConfig
15
17
  from pangea.exceptions import PangeaException
@@ -17,6 +19,8 @@ from pangea.services import Audit
17
19
 
18
20
 
19
21
  class TestEnvironment(str, enum.Enum):
22
+ __test__ = False
23
+
20
24
  DEVELOP = "DEV"
21
25
  LIVE = "LVE"
22
26
  STAGING = "STG"
@@ -28,15 +32,15 @@ class TestEnvironment(str, enum.Enum):
28
32
  return str(self.value)
29
33
 
30
34
 
31
- class Root(Dict):
35
+ class Root(dict):
32
36
  size: int
33
37
  tree_name: str
34
38
 
35
39
 
36
- class Event(Dict):
40
+ class Event(dict):
37
41
  membership_proof: str
38
42
  leaf_index: Optional[int]
39
- event: Dict
43
+ event: dict
40
44
  hash: str
41
45
  tree_size: Optional[int]
42
46
 
@@ -68,7 +72,7 @@ def exit_with_error(message: str):
68
72
  sys.exit(1)
69
73
 
70
74
 
71
- def file_events(root_hashes: Dict[int, str], f: io.TextIOWrapper) -> Iterator[Event]:
75
+ def file_events(root_hashes: dict[int, str], f: io.TextIOWrapper) -> Iterator[Event]:
72
76
  """
73
77
  Reads a file containing Events in JSON format with the following fields:
74
78
  - membership_proof: str
@@ -111,8 +115,8 @@ def make_aware_datetime(d: datetime) -> datetime:
111
115
  return d
112
116
 
113
117
 
114
- def filter_deep_none(data: Dict) -> Dict:
115
- return {k: v if not isinstance(v, Dict) else filter_deep_none(v) for k, v in data.items() if v is not None}
118
+ def filter_deep_none(data: dict) -> dict:
119
+ return {k: v if not isinstance(v, dict) else filter_deep_none(v) for k, v in data.items() if v is not None}
116
120
 
117
121
 
118
122
  def _load_env_var(env_var_name: str) -> str:
@@ -183,7 +187,7 @@ class SequenceFollower:
183
187
  self.numbers.remove(min_val)
184
188
  min_val += 1
185
189
 
186
- def holes(self) -> List[int]:
190
+ def holes(self) -> list[int]:
187
191
  if not self.numbers:
188
192
  return []
189
193
 
@@ -192,7 +196,7 @@ class SequenceFollower:
192
196
  return [val for val in range(min_val, max_val) if val not in self.numbers]
193
197
 
194
198
 
195
- loggers: Dict[str, bool] = {}
199
+ loggers: dict[str, bool] = {}
196
200
 
197
201
 
198
202
  def logger_set_pangea_config(logger_name: str, level=logging.DEBUG):
pangea/utils.py CHANGED
@@ -60,7 +60,7 @@ def canonicalize(data: dict) -> str:
60
60
  return json.dumps(
61
61
  data, ensure_ascii=False, allow_nan=False, separators=(",", ":"), sort_keys=True, default=default_encoder
62
62
  )
63
- elif isinstance(data, datetime.datetime) or isinstance(data, datetime.date):
63
+ elif isinstance(data, (datetime.datetime, datetime.date)):
64
64
  return format_datetime(data)
65
65
  else:
66
66
  return str(data)
@@ -151,10 +151,8 @@ def get_crc32c(data: str) -> str:
151
151
 
152
152
 
153
153
  def hash_256_filepath(filepath: str) -> str:
154
- data = open(filepath, "rb")
155
- hash = sha256(data.read()).hexdigest()
156
- data.close()
157
- return hash
154
+ with open(filepath, "rb") as data:
155
+ return sha256(data.read()).hexdigest()
158
156
 
159
157
 
160
158
  def get_prefix(hash: str, len: int = 5):
pangea/verify_audit.py CHANGED
@@ -9,14 +9,16 @@ You can provide a single event (obtained from the PUC) or the result from a sear
9
9
  In the latter case, all the events are verified.
10
10
  """
11
11
 
12
+ from __future__ import annotations
13
+
12
14
  import argparse
13
15
  import json
14
16
  import logging
15
17
  import os
16
18
  import sys
17
- from collections.abc import Set
19
+ from collections.abc import Iterable, Set
18
20
  from enum import Enum
19
- from typing import Dict, Iterable, List, Optional, Union
21
+ from typing import Optional, Union
20
22
 
21
23
  from pangea.config import PangeaConfig
22
24
  from pangea.exceptions import TreeNotFoundException
@@ -36,8 +38,8 @@ from pangea.services.audit.util import (
36
38
  )
37
39
 
38
40
  logger = logging.getLogger("audit")
39
- arweave_roots: Dict[int, PublishedRoot] = {} # roots fetched from Arweave
40
- pangea_roots: Dict[int, Root] = {} # roots fetched from Pangea
41
+ arweave_roots: dict[int, PublishedRoot] = {} # roots fetched from Arweave
42
+ pangea_roots: dict[int, Root] = {} # roots fetched from Pangea
41
43
  audit: Optional[Audit] = None
42
44
 
43
45
 
@@ -72,10 +74,7 @@ class VerifierLogFormatter(logging.Formatter):
72
74
  self.in_section = True
73
75
  return f"{' ' * self.indent}⎾ {record.msg}"
74
76
  else:
75
- if self.in_section:
76
- pre = f"{' ' * (self.indent+4)}⌲ "
77
- else:
78
- pre = ""
77
+ pre = f"{' ' * (self.indent + 4)}⌲ " if self.in_section else ""
79
78
  return f"{pre}{record.msg}"
80
79
 
81
80
 
@@ -100,8 +99,8 @@ formatter = VerifierLogFormatter()
100
99
  InvalidTokenError = ValueError("Invalid Pangea Token provided")
101
100
 
102
101
 
103
- def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> Dict[int, Root]:
104
- ans: Dict[int, Root] = {}
102
+ def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> dict[int, Root]:
103
+ ans: dict[int, Root] = {}
105
104
  if audit is None:
106
105
  return ans
107
106
 
@@ -125,7 +124,7 @@ def get_pangea_roots(tree_name: str, tree_sizes: Iterable[int]) -> Dict[int, Roo
125
124
  return ans
126
125
 
127
126
 
128
- def _verify_hash(data: Dict, data_hash: str) -> Status:
127
+ def _verify_hash(data: dict, data_hash: str) -> Status:
129
128
  log_section("Checking data hash")
130
129
  status = Status.SKIPPED
131
130
  try:
@@ -213,7 +212,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
213
212
  logger.debug(f"Fetching root(s) {comma_sep(pending_roots)} from Arweave")
214
213
  arweave_roots |= {int(k): v for k, v in get_arweave_published_roots(tree_name, pending_roots).items()}
215
214
  update_pending_roots()
216
- except:
215
+ except: # noqa: E722
217
216
  pass
218
217
 
219
218
  if pending_roots:
@@ -227,7 +226,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
227
226
  pangea_roots |= {int(k): v for k, v in get_pangea_roots(tree_name, pending_roots).items()}
228
227
  update_pending_roots()
229
228
  status = Status.SUCCEEDED_PANGEA
230
- except:
229
+ except: # noqa: E722
231
230
  pass
232
231
 
233
232
  if pending_roots:
@@ -244,7 +243,7 @@ def _fetch_roots(tree_name: str, tree_size: int, leaf_index: Optional[int]) -> S
244
243
 
245
244
 
246
245
  def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str]) -> Status:
247
- pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
246
+ pub_roots: dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
248
247
 
249
248
  log_section("Checking membership proof")
250
249
 
@@ -277,7 +276,7 @@ def _verify_membership_proof(tree_size: int, node_hash: str, proof: Optional[str
277
276
  return status
278
277
 
279
278
 
280
- def _consistency_proof_ok(pub_roots: Dict[int, Union[Root, PublishedRoot]], leaf_index: int) -> bool:
279
+ def _consistency_proof_ok(pub_roots: dict[int, Union[Root, PublishedRoot]], leaf_index: int) -> bool:
281
280
  """returns true if a consistency proof is correct"""
282
281
 
283
282
  curr_root = pub_roots[leaf_index + 1]
@@ -296,7 +295,7 @@ def _consistency_proof_ok(pub_roots: Dict[int, Union[Root, PublishedRoot]], leaf
296
295
  # Due to an (already fixed) bug, some proofs from Arweave may be wrong.
297
296
  # Try the proof from Pangea instead. If the root hash in both Arweave and Pangea is the same,
298
297
  # it doesn't matter where the proof came from.
299
- def _fix_consistency_proof(pub_roots: Dict[int, Union[Root, PublishedRoot]], tree_name: str, leaf_index: int):
298
+ def _fix_consistency_proof(pub_roots: dict[int, Union[Root, PublishedRoot]], tree_name: str, leaf_index: int):
300
299
  logger.debug("Consistency proof from Arweave failed to verify")
301
300
  size = leaf_index + 1
302
301
  logger.debug(f"Fetching root from Pangea for size {size}")
@@ -305,13 +304,13 @@ def _fix_consistency_proof(pub_roots: Dict[int, Union[Root, PublishedRoot]], tre
305
304
  raise ValueError("Error fetching root from Pangea")
306
305
  pangea_roots[size] = new_roots[size]
307
306
  pub_roots[size] = pangea_roots[size]
308
- logger.debug(f"Comparing Arweave root hash with Pangea root hash")
307
+ logger.debug("Comparing Arweave root hash with Pangea root hash")
309
308
  if pangea_roots[size].root_hash != arweave_roots[size].root_hash:
310
309
  raise ValueError("Hash does not match")
311
310
 
312
311
 
313
312
  def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Status:
314
- pub_roots: Dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
313
+ pub_roots: dict[int, Union[Root, PublishedRoot]] = arweave_roots | pangea_roots
315
314
 
316
315
  log_section("Checking consistency proof")
317
316
 
@@ -336,10 +335,7 @@ def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Stat
336
335
  _fix_consistency_proof(pub_roots, tree_name, leaf_index)
337
336
 
338
337
  # check again
339
- if _consistency_proof_ok(pub_roots, leaf_index):
340
- status = Status.SUCCEEDED
341
- else:
342
- status = Status.FAILED
338
+ status = Status.SUCCEEDED if _consistency_proof_ok(pub_roots, leaf_index) else Status.FAILED
343
339
 
344
340
  else:
345
341
  logger.debug(
@@ -356,11 +352,11 @@ def _verify_consistency_proof(tree_name: str, leaf_index: Optional[int]) -> Stat
356
352
  return status
357
353
 
358
354
 
359
- def create_signed_event(event: Dict) -> Dict:
355
+ def create_signed_event(event: dict) -> dict:
360
356
  return {k: v for k, v in event.items() if v is not None}
361
357
 
362
358
 
363
- def _verify_signature(data: Dict) -> Status:
359
+ def _verify_signature(data: dict) -> Status:
364
360
  log_section("Checking signature")
365
361
  if "signature" not in data:
366
362
  logger.debug("Signature is not present")
@@ -383,13 +379,13 @@ def _verify_signature(data: Dict) -> Status:
383
379
  return status
384
380
 
385
381
 
386
- def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> Status:
382
+ def verify_multiple(root: dict, unpublished_root: dict, events: list[dict]) -> Status:
387
383
  """
388
384
  Verify a list of events.
389
385
  Returns a status.
390
386
  """
391
387
 
392
- statuses: List[Status] = []
388
+ statuses: list[Status] = []
393
389
  for counter, event in enumerate(events):
394
390
  event.update({"root": root, "unpublished_root": unpublished_root})
395
391
  event_status = verify_single(event, counter + 1)
@@ -403,7 +399,7 @@ def verify_multiple(root: Dict, unpublished_root: Dict, events: List[Dict]) -> S
403
399
  return Status.SUCCEEDED
404
400
 
405
401
 
406
- def verify_single(data: Dict, counter: Optional[int] = None) -> Status:
402
+ def verify_single(data: dict, counter: Optional[int] = None) -> Status:
407
403
  """
408
404
  Verify a single event.
409
405
  Returns a status.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pangea-sdk
3
- Version: 6.1.1
3
+ Version: 6.2.0
4
4
  Summary: Pangea API SDK
5
5
  License: MIT
6
6
  Keywords: Pangea,SDK,Audit
@@ -9,16 +9,16 @@ Author-email: glenn.gallien@pangea.cloud
9
9
  Requires-Python: >=3.9.2,<4.0.0
10
10
  Classifier: Topic :: Software Development
11
11
  Classifier: Topic :: Software Development :: Libraries
12
- Requires-Dist: aiohttp (>=3.11.18,<4.0.0)
13
- Requires-Dist: cryptography (>=44.0.3,<44.0.4)
12
+ Requires-Dist: aiohttp (>=3.12.13,<4.0.0)
13
+ Requires-Dist: cryptography (>=45.0.4,<46.0.0)
14
14
  Requires-Dist: deprecated (>=1.2.18,<2.0.0)
15
15
  Requires-Dist: google-crc32c (>=1.7.1,<2.0.0)
16
- Requires-Dist: pydantic (>=2.11.4,<3.0.0)
16
+ Requires-Dist: pydantic (>=2.11.7,<3.0.0)
17
17
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
18
- Requires-Dist: requests (>=2.32.3,<3.0.0)
18
+ Requires-Dist: requests (>=2.32.4,<3.0.0)
19
19
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
20
- Requires-Dist: typing-extensions (>=4.13.2,<5.0.0)
21
- Requires-Dist: yarl (>=1.20.0,<2.0.0)
20
+ Requires-Dist: typing-extensions (>=4.14.0,<5.0.0)
21
+ Requires-Dist: yarl (>=1.20.1,<2.0.0)
22
22
  Description-Content-Type: text/markdown
23
23
 
24
24
  <a href="https://pangea.cloud?utm_source=github&utm_medium=python-sdk" target="_blank" rel="noopener noreferrer">
@@ -63,13 +63,13 @@ the same compatibility guarantees as stable releases.
63
63
  Via pip:
64
64
 
65
65
  ```bash
66
- $ pip3 install pangea-sdk==5.5.0b2
66
+ $ pip3 install pangea-sdk==6.2.0b2
67
67
  ```
68
68
 
69
69
  Via poetry:
70
70
 
71
71
  ```bash
72
- $ poetry add pangea-sdk==5.5.0b2
72
+ $ poetry add pangea-sdk==6.2.0b2
73
73
  ```
74
74
 
75
75
  ## Usage
@@ -102,6 +102,26 @@ audit = Audit(token, config)
102
102
  response = audit.log(message="Hello, World!")
103
103
  ```
104
104
 
105
+ ## Configuration
106
+
107
+ The SDK supports the following configuration options via `PangeaConfig`:
108
+
109
+ - `base_url_template` — Template for constructing the base URL for API requests.
110
+ The placeholder `{SERVICE_NAME}` will be replaced with the service name slug.
111
+ This is a more powerful version of `domain` that allows for setting more than
112
+ just the host of the API server. Defaults to
113
+ `https://{SERVICE_NAME}.aws.us.pangea.cloud`.
114
+ - `domain` — Base domain for API requests. This is a weaker version of
115
+ `base_url_template` that only allows for setting the host of the API server.
116
+ Use `base_url_template` for more control over the URL, such as setting
117
+ service-specific paths. Defaults to `aws.us.pangea.cloud`.
118
+ - `request_retries` — Number of retries on the initial request.
119
+ - `request_backoff` — Backoff strategy passed to 'requests'.
120
+ - `request_timeout` — Timeout used on initial request attempts.
121
+ - `poll_result_timeout` — Timeout used to poll results after 202 (in secs).
122
+ - `queued_retry_enabled` — Enable queued request retry support.
123
+ - `custom_user_agent` — Custom user agent to be used in the request headers.
124
+
105
125
  ## asyncio support
106
126
 
107
127
  asyncio support is available through the `pangea.asyncio.services` module. The
@@ -156,6 +176,7 @@ options:
156
176
  ```
157
177
 
158
178
  It accepts multiple file formats:
179
+
159
180
  - a Verification Artifact from the Pangea User Console
160
181
  - a search response from the REST API:
161
182
 
@@ -163,7 +184,6 @@ It accepts multiple file formats:
163
184
  $ curl -H "Authorization: Bearer ${PANGEA_TOKEN}" -X POST -H 'Content-Type: application/json' --data '{"verbose": true}' https://audit.aws.us.pangea.cloud/v1/search
164
185
  ```
165
186
 
166
-
167
187
  ### Bulk Download Audit Data
168
188
 
169
189
  Download all audit logs for a given time range. Start and end date should be provided,
@@ -213,15 +233,14 @@ options:
213
233
  ```
214
234
 
215
235
  It accepts multiple file formats:
236
+
216
237
  - a Verification Artifact from the Pangea User Console
217
238
  - a file generated by the `dump_audit` command
218
239
  - a search response from the REST API (see `verify_audit`)
219
240
 
220
-
221
-
222
- [Documentation]: https://pangea.cloud/docs/sdk/python/
223
- [GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
224
- [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
225
- [Pangea Console]: https://console.pangea.cloud/
226
- [Secure Audit Log]: https://pangea.cloud/docs/audit
241
+ [Documentation]: https://pangea.cloud/docs/sdk/python/
242
+ [GA Examples]: https://github.com/pangeacyber/pangea-python/tree/main/examples
243
+ [Beta Examples]: https://github.com/pangeacyber/pangea-python/tree/beta/examples
244
+ [Pangea Console]: https://console.pangea.cloud/
245
+ [Secure Audit Log]: https://pangea.cloud/docs/audit
227
246