pangea-sdk 3.8.0__py3-none-any.whl → 5.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. pangea/__init__.py +2 -1
  2. pangea/asyncio/__init__.py +1 -0
  3. pangea/asyncio/file_uploader.py +39 -0
  4. pangea/asyncio/request.py +46 -23
  5. pangea/asyncio/services/__init__.py +2 -0
  6. pangea/asyncio/services/audit.py +46 -20
  7. pangea/asyncio/services/authn.py +123 -61
  8. pangea/asyncio/services/authz.py +57 -31
  9. pangea/asyncio/services/base.py +21 -2
  10. pangea/asyncio/services/embargo.py +2 -2
  11. pangea/asyncio/services/file_scan.py +24 -9
  12. pangea/asyncio/services/intel.py +104 -30
  13. pangea/asyncio/services/redact.py +52 -3
  14. pangea/asyncio/services/sanitize.py +217 -0
  15. pangea/asyncio/services/share.py +733 -0
  16. pangea/asyncio/services/vault.py +1709 -766
  17. pangea/crypto/rsa.py +135 -0
  18. pangea/deep_verify.py +7 -1
  19. pangea/dump_audit.py +9 -8
  20. pangea/file_uploader.py +35 -0
  21. pangea/request.py +70 -49
  22. pangea/response.py +36 -17
  23. pangea/services/__init__.py +2 -0
  24. pangea/services/audit/audit.py +57 -29
  25. pangea/services/audit/models.py +12 -3
  26. pangea/services/audit/signing.py +6 -5
  27. pangea/services/audit/util.py +3 -3
  28. pangea/services/authn/authn.py +120 -66
  29. pangea/services/authn/models.py +167 -11
  30. pangea/services/authz.py +53 -30
  31. pangea/services/base.py +16 -2
  32. pangea/services/embargo.py +2 -2
  33. pangea/services/file_scan.py +32 -15
  34. pangea/services/intel.py +155 -30
  35. pangea/services/redact.py +132 -3
  36. pangea/services/sanitize.py +388 -0
  37. pangea/services/share/file_format.py +170 -0
  38. pangea/services/share/share.py +1440 -0
  39. pangea/services/vault/models/asymmetric.py +120 -18
  40. pangea/services/vault/models/common.py +439 -141
  41. pangea/services/vault/models/keys.py +94 -0
  42. pangea/services/vault/models/secret.py +27 -3
  43. pangea/services/vault/models/symmetric.py +68 -22
  44. pangea/services/vault/vault.py +1690 -766
  45. pangea/tools.py +6 -7
  46. pangea/utils.py +94 -33
  47. pangea/verify_audit.py +270 -83
  48. {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/METADATA +21 -29
  49. pangea_sdk-5.3.0.dist-info/RECORD +56 -0
  50. {pangea_sdk-3.8.0.dist-info → pangea_sdk-5.3.0.dist-info}/WHEEL +1 -1
  51. pangea_sdk-3.8.0.dist-info/RECORD +0 -46
@@ -1,10 +1,16 @@
1
1
  # Copyright 2022 Pangea Cyber Corporation
2
2
  # Author: Pangea Cyber Corporation
3
3
 
4
+ from __future__ import annotations
5
+
4
6
  import enum
5
7
  from typing import Dict, List, NewType, Optional, Union
8
+ from warnings import warn
9
+
10
+ from typing_extensions import deprecated
6
11
 
7
12
  import pangea.services.intel as im
13
+ from pangea.deprecated import pangea_deprecated
8
14
  from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
9
15
  from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
10
16
 
@@ -12,9 +18,49 @@ Scopes = NewType("Scopes", List[str])
12
18
 
13
19
 
14
20
  class Profile(Dict[str, str]):
15
- first_name: str
16
- last_name: str
17
- phone: Optional[str] = None
21
+ @property
22
+ def first_name(self) -> str:
23
+ warn(
24
+ '`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
25
+ )
26
+ return self["first_name"]
27
+
28
+ @first_name.setter
29
+ def first_name(self, value: str) -> None:
30
+ warn(
31
+ '`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
32
+ )
33
+ self["first_name"] = value
34
+
35
+ @property
36
+ def last_name(self) -> str:
37
+ warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
38
+ return self["last_name"]
39
+
40
+ @last_name.setter
41
+ def last_name(self, value: str) -> None:
42
+ warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
43
+ self["last_name"] = value
44
+
45
+ @property
46
+ def phone(self) -> str:
47
+ warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
48
+ return self["phone"]
49
+
50
+ @phone.setter
51
+ def phone(self, value: str) -> None:
52
+ warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
53
+ self["phone"] = value
54
+
55
+ @deprecated("`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.")
56
+ @pangea_deprecated(reason="`Profile` is already a `dict[str, str]`.")
57
+ def model_dump(self, *, exclude_none: bool = False) -> dict[str, str]:
58
+ warn(
59
+ "`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.",
60
+ DeprecationWarning,
61
+ stacklevel=2,
62
+ )
63
+ return self
18
64
 
19
65
 
20
66
  class ClientPasswordChangeRequest(APIRequestModel):
@@ -59,7 +105,7 @@ class SessionToken(PangeaResponseResult):
59
105
  identity: str
60
106
  email: str
61
107
  scopes: Optional[Scopes] = None
62
- profile: Profile
108
+ profile: Union[Profile, Dict[str, str]]
63
109
  created_at: str
64
110
  intelligence: Optional[Intelligence] = None
65
111
 
@@ -69,7 +115,7 @@ class LoginToken(SessionToken):
69
115
 
70
116
 
71
117
  class ClientTokenCheckResult(SessionToken):
72
- token: Optional[str]
118
+ token: Optional[str] = None
73
119
 
74
120
 
75
121
  class IDProvider(str, enum.Enum):
@@ -150,34 +196,93 @@ class UserListOrderBy(enum.Enum):
150
196
 
151
197
 
152
198
  class Authenticator(APIResponseModel):
199
+ """Authenticator."""
200
+
153
201
  id: str
202
+ """An ID for an authenticator."""
203
+
154
204
  type: str
205
+ """An authentication mechanism."""
206
+
155
207
  enabled: bool
208
+ """Enabled."""
209
+
156
210
  provider: Optional[str] = None
211
+ """Provider."""
212
+
213
+ provider_name: Optional[str] = None
214
+ """Provider name."""
215
+
157
216
  rpid: Optional[str] = None
217
+ """RPID."""
218
+
158
219
  phase: Optional[str] = None
220
+ """Phase."""
221
+
222
+ enrolling_browser: Optional[str] = None
223
+ """Enrolling browser."""
224
+
225
+ enrolling_ip: Optional[str] = None
226
+ """Enrolling IP."""
227
+
228
+ created_at: str
229
+ """A time in ISO-8601 format."""
230
+
231
+ updated_at: str
232
+ """A time in ISO-8601 format."""
233
+
234
+ state: Optional[str] = None
235
+ """State."""
159
236
 
160
237
 
161
238
  class User(PangeaResponseResult):
162
239
  id: str
240
+ """The identity of a user or a service."""
241
+
163
242
  email: str
164
- profile: Profile
243
+ """An email address."""
244
+
245
+ username: str
246
+ """A username."""
247
+
248
+ profile: Union[Profile, Dict[str, str]]
249
+ """A user profile as a collection of string properties."""
250
+
165
251
  verified: bool
252
+ """True if the user's email has been verified."""
253
+
166
254
  disabled: bool
255
+ """True if the service administrator has disabled user account."""
256
+
167
257
  accepted_eula_id: Optional[str] = None
258
+ """An ID for an agreement."""
259
+
168
260
  accepted_privacy_policy_id: Optional[str] = None
261
+ """An ID for an agreement."""
262
+
169
263
  last_login_at: Optional[str] = None
264
+ """A time in ISO-8601 format."""
265
+
170
266
  created_at: str
267
+ """A time in ISO-8601 format."""
268
+
171
269
  login_count: int = 0
172
270
  last_login_ip: Optional[str] = None
173
271
  last_login_city: Optional[str] = None
174
272
  last_login_country: Optional[str] = None
175
273
  authenticators: List[Authenticator] = []
274
+ """A list of authenticators."""
176
275
 
177
276
 
178
277
  class UserCreateRequest(APIRequestModel):
179
278
  email: str
180
- profile: Profile
279
+ """An email address."""
280
+
281
+ profile: Union[Profile, Dict[str, str]]
282
+ """A user profile as a collection of string properties."""
283
+
284
+ username: Optional[str] = None
285
+ """A username."""
181
286
 
182
287
 
183
288
  class UserCreateResult(User):
@@ -186,7 +291,13 @@ class UserCreateResult(User):
186
291
 
187
292
  class UserDeleteRequest(APIRequestModel):
188
293
  email: Optional[str] = None
294
+ """An email address."""
295
+
189
296
  id: Optional[str] = None
297
+ """The identity of a user or a service."""
298
+
299
+ username: Optional[str] = None
300
+ """A username."""
190
301
 
191
302
 
192
303
  class UserDeleteResult(PangeaResponseResult):
@@ -289,7 +400,7 @@ class UserInviterOrderBy(enum.Enum):
289
400
 
290
401
 
291
402
  class UserInviteListFilter(APIRequestModel):
292
- callback: Optional[str]
403
+ callback: Optional[str] = None
293
404
  callback__contains: Optional[List[str]] = None
294
405
  callback__in: Optional[List[str]] = None
295
406
  created_at: Optional[str] = None
@@ -343,7 +454,13 @@ class UserInviteDeleteResult(PangeaResponseResult):
343
454
 
344
455
  class UserProfileGetRequest(APIRequestModel):
345
456
  id: Optional[str] = None
457
+ """The identity of a user or a service."""
458
+
346
459
  email: Optional[str] = None
460
+ """An email address."""
461
+
462
+ username: Optional[str] = None
463
+ """A username."""
347
464
 
348
465
 
349
466
  class UserProfileGetResult(User):
@@ -351,9 +468,17 @@ class UserProfileGetResult(User):
351
468
 
352
469
 
353
470
  class UserProfileUpdateRequest(APIRequestModel):
354
- profile: Profile
471
+ profile: Union[Profile, Dict[str, str]]
472
+ """Updates to a user profile."""
473
+
355
474
  id: Optional[str] = None
475
+ """The identity of a user or a service."""
476
+
356
477
  email: Optional[str] = None
478
+ """An email address."""
479
+
480
+ username: Optional[str] = None
481
+ """A username."""
357
482
 
358
483
 
359
484
  class UserProfileUpdateResult(User):
@@ -362,9 +487,25 @@ class UserProfileUpdateResult(User):
362
487
 
363
488
  class UserUpdateRequest(APIRequestModel):
364
489
  id: Optional[str] = None
490
+ """The identity of a user or a service."""
491
+
365
492
  email: Optional[str] = None
493
+ """An email address."""
494
+
366
495
  disabled: Optional[bool] = None
496
+ """
497
+ New disabled value. Disabling a user account will prevent them from logging
498
+ in.
499
+ """
500
+
367
501
  unlock: Optional[bool] = None
502
+ """
503
+ Unlock a user account if it has been locked out due to failed authentication
504
+ attempts.
505
+ """
506
+
507
+ username: Optional[str] = None
508
+ """A username."""
368
509
 
369
510
 
370
511
  class UserUpdateResult(User):
@@ -386,8 +527,16 @@ class ClientJWKSResult(PangeaResponseResult):
386
527
 
387
528
  class UserAuthenticatorsDeleteRequest(APIRequestModel):
388
529
  id: Optional[str] = None
530
+ """The identity of a user or a service."""
531
+
389
532
  email: Optional[str] = None
533
+ """An email address."""
534
+
390
535
  authenticator_id: str
536
+ """An ID for an authenticator."""
537
+
538
+ username: Optional[str] = None
539
+ """A username."""
391
540
 
392
541
 
393
542
  class UserAuthenticatorsDeleteResult(PangeaResponseResult):
@@ -396,11 +545,18 @@ class UserAuthenticatorsDeleteResult(PangeaResponseResult):
396
545
 
397
546
  class UserAuthenticatorsListRequest(APIRequestModel):
398
547
  email: Optional[str] = None
548
+ """An email address."""
549
+
399
550
  id: Optional[str] = None
551
+ """The identity of a user or a service."""
552
+
553
+ username: Optional[str] = None
554
+ """A username."""
400
555
 
401
556
 
402
557
  class UserAuthenticatorsListResult(PangeaResponseResult):
403
558
  authenticators: List[Authenticator] = []
559
+ """A list of authenticators."""
404
560
 
405
561
 
406
562
  class FlowCompleteRequest(APIRequestModel):
@@ -499,7 +655,7 @@ class FlowUpdateDataPassword(APIRequestModel):
499
655
 
500
656
 
501
657
  class FlowUpdateDataProfile(APIRequestModel):
502
- profile: Profile
658
+ profile: Union[Profile, Dict[str, str]]
503
659
 
504
660
 
505
661
  class FlowUpdateDataProvisionalEnrollment(APIRequestModel):
@@ -621,7 +777,7 @@ class SessionItem(APIResponseModel):
621
777
  expire: str
622
778
  email: str
623
779
  scopes: Optional[Scopes] = None
624
- profile: Profile
780
+ profile: Union[Profile, Dict[str, str]]
625
781
  created_at: str
626
782
  active_token: Optional[SessionToken] = None
627
783
 
pangea/services/authz.py CHANGED
@@ -1,9 +1,11 @@
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
  from typing import Any, Dict, List, Optional, Union
6
7
 
8
+ from pangea.config import PangeaConfig
7
9
  from pangea.response import APIRequestModel, APIResponseModel, PangeaResponse, PangeaResponseResult
8
10
  from pangea.services.base import ServiceBase
9
11
 
@@ -113,7 +115,7 @@ class CheckRequest(APIRequestModel):
113
115
  class DebugPath(APIResponseModel):
114
116
  type: str
115
117
  id: str
116
- action: Optional[str]
118
+ action: Optional[str] = None
117
119
 
118
120
 
119
121
  class Debug(APIResponseModel):
@@ -132,6 +134,7 @@ class ListResourcesRequest(APIRequestModel):
132
134
  type: str
133
135
  action: str
134
136
  subject: Subject
137
+ attributes: Optional[Dict[str, Any]] = None
135
138
 
136
139
 
137
140
  class ListResourcesResult(PangeaResponseResult):
@@ -141,6 +144,7 @@ class ListResourcesResult(PangeaResponseResult):
141
144
  class ListSubjectsRequest(APIRequestModel):
142
145
  resource: Resource
143
146
  action: str
147
+ attributes: Optional[Dict[str, Any]] = None
144
148
 
145
149
 
146
150
  class ListSubjectsResult(PangeaResponseResult):
@@ -148,12 +152,11 @@ class ListSubjectsResult(PangeaResponseResult):
148
152
 
149
153
 
150
154
  class AuthZ(ServiceBase):
151
- """AuthZ service client. (Beta)
155
+ """AuthZ service client.
152
156
 
153
157
  Provides methods to interact with the Pangea AuthZ Service.
154
158
  Documentation for the AuthZ Service API can be found at
155
- <https://pangea.cloud/docs/api/authz>. Note that this service is in Beta and
156
- is subject to change.
159
+ <https://pangea.cloud/docs/api/authz>.
157
160
 
158
161
  Examples:
159
162
  import os
@@ -170,15 +173,32 @@ class AuthZ(ServiceBase):
170
173
 
171
174
  service_name = "authz"
172
175
 
173
- def __init__(self, token: str, config=None, logger_name="pangea", config_id: Optional[str] = None):
176
+ def __init__(
177
+ self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
178
+ ) -> None:
179
+ """
180
+ AuthZ client
181
+
182
+ Initializes a new AuthZ client.
183
+
184
+ Args:
185
+ token: Pangea API token.
186
+ config: Configuration.
187
+ logger_name: Logger name.
188
+ config_id: Configuration ID.
189
+
190
+ Examples:
191
+ config = PangeaConfig(domain="aws.us.pangea.cloud")
192
+ authz = AuthZ(token="pangea_token", config=config)
193
+ """
194
+
174
195
  super().__init__(token, config, logger_name, config_id=config_id)
175
196
 
176
197
  def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
177
- """Create tuples. (Beta)
198
+ """Create tuples.
178
199
 
179
200
  Create tuples in the AuthZ Service. The request will fail if there is no schema
180
201
  or the tuples do not validate against the schema.
181
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
182
202
 
183
203
  Args:
184
204
  tuples (List[Tuple]): List of tuples to be created.
@@ -204,7 +224,7 @@ class AuthZ(ServiceBase):
204
224
  """
205
225
 
206
226
  input_data = TupleCreateRequest(tuples=tuples)
207
- return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.dict(exclude_none=True))
227
+ return self.request.post("v1/tuple/create", TupleCreateResult, data=input_data.model_dump(exclude_none=True))
208
228
 
209
229
  def tuple_list(
210
230
  self,
@@ -214,12 +234,11 @@ class AuthZ(ServiceBase):
214
234
  order: Optional[ItemOrder] = None,
215
235
  order_by: Optional[TupleOrderBy] = None,
216
236
  ) -> PangeaResponse[TupleListResult]:
217
- """List tuples. (Beta)
237
+ """List tuples.
218
238
 
219
239
  Return a paginated list of filtered tuples. The filter is given in terms
220
240
  of a tuple. Fill out the fields that you want to filter. If the filter
221
241
  is empty it will return all the tuples.
222
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
223
242
 
224
243
  Args:
225
244
  filter (TupleListFilter): The filter for listing tuples.
@@ -240,15 +259,14 @@ class AuthZ(ServiceBase):
240
259
  authz.tuple_list(TupleListFilter(subject_type="user", subject_id="user_1"))
241
260
  """
242
261
  input_data = TupleListRequest(
243
- filter=filter.dict(exclude_none=True), size=size, last=last, order=order, order_by=order_by
262
+ filter=filter.model_dump(exclude_none=True), size=size, last=last, order=order, order_by=order_by
244
263
  )
245
- return self.request.post("v1/tuple/list", TupleListResult, data=input_data.dict(exclude_none=True))
264
+ return self.request.post("v1/tuple/list", TupleListResult, data=input_data.model_dump(exclude_none=True))
246
265
 
247
266
  def tuple_delete(self, tuples: List[Tuple]) -> PangeaResponse[TupleDeleteResult]:
248
- """Delete tuples. (Beta)
267
+ """Delete tuples.
249
268
 
250
269
  Delete tuples in the AuthZ Service.
251
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
252
270
 
253
271
  Args:
254
272
  tuples (List[Tuple]): List of tuples to be deleted.
@@ -274,7 +292,7 @@ class AuthZ(ServiceBase):
274
292
  """
275
293
 
276
294
  input_data = TupleDeleteRequest(tuples=tuples)
277
- return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.dict(exclude_none=True))
295
+ return self.request.post("v1/tuple/delete", TupleDeleteResult, data=input_data.model_dump(exclude_none=True))
278
296
 
279
297
  def check(
280
298
  self,
@@ -282,19 +300,18 @@ class AuthZ(ServiceBase):
282
300
  action: str,
283
301
  subject: Subject,
284
302
  debug: Optional[bool] = None,
285
- attributes: Optional[Dict[str, Union[int, str]]] = None,
303
+ attributes: Optional[Dict[str, Any]] = None,
286
304
  ) -> PangeaResponse[CheckResult]:
287
- """Perform a check request. (Beta)
305
+ """Perform a check request.
288
306
 
289
307
  Check if a subject has permission to perform an action on the resource.
290
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
291
308
 
292
309
  Args:
293
310
  resource (Resource): The resource to check.
294
311
  action (str): The action to check.
295
312
  subject (Subject): The subject to check.
296
313
  debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
297
- attributes (Optional[Dict[str, Union[int, str]]]): Additional attributes for the check.
314
+ attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
298
315
 
299
316
  Raises:
300
317
  PangeaAPIException: If an API Error happens.
@@ -314,19 +331,21 @@ class AuthZ(ServiceBase):
314
331
  """
315
332
 
316
333
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
317
- return self.request.post("v1/check", CheckResult, data=input_data.dict(exclude_none=True))
334
+ return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
318
335
 
319
- def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
320
- """List resources. (Beta)
336
+ def list_resources(
337
+ self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
338
+ ) -> PangeaResponse[ListResourcesResult]:
339
+ """List resources.
321
340
 
322
341
  Given a type, action, and subject, list all the resources in the
323
342
  type that the subject has access to the action with.
324
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
325
343
 
326
344
  Args:
327
345
  type (str): The type to filter resources.
328
346
  action (str): The action to filter resources.
329
347
  subject (Subject): The subject to filter resources.
348
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
330
349
 
331
350
  Raises:
332
351
  PangeaAPIException: If an API Error happens.
@@ -344,19 +363,23 @@ class AuthZ(ServiceBase):
344
363
  )
345
364
  """
346
365
 
347
- input_data = ListResourcesRequest(type=type, action=action, subject=subject)
348
- return self.request.post("v1/list-resources", ListResourcesResult, data=input_data.dict(exclude_none=True))
366
+ input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
367
+ return self.request.post(
368
+ "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
369
+ )
349
370
 
350
- def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
351
- """List subjects. (Beta)
371
+ def list_subjects(
372
+ self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
373
+ ) -> PangeaResponse[ListSubjectsResult]:
374
+ """List subjects.
352
375
 
353
376
  Given a resource and an action, return the list of subjects who have
354
377
  access to the action for the given resource.
355
- How to install a [Beta release](https://pangea.cloud/docs/sdk/python/#beta-releases).
356
378
 
357
379
  Args:
358
380
  resource (Resource): The resource to filter subjects.
359
381
  action (str): The action to filter subjects.
382
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
360
383
 
361
384
  Raises:
362
385
  PangeaAPIException: If an API Error happens.
@@ -373,5 +396,5 @@ class AuthZ(ServiceBase):
373
396
  )
374
397
  """
375
398
 
376
- input_data = ListSubjectsRequest(resource=resource, action=action)
377
- return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.dict(exclude_none=True))
399
+ input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
400
+ return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True))
pangea/services/base.py CHANGED
@@ -80,7 +80,8 @@ class ServiceBase(object):
80
80
  Returns request's result that has been accepted by the server
81
81
 
82
82
  Args:
83
- exception (AcceptedRequestException): Exception raise by SDK on the call that is been processed.
83
+ exception: Exception that was previously raised by the SDK on a call
84
+ that is being processed.
84
85
 
85
86
  Returns:
86
87
  PangeaResponse
@@ -100,5 +101,18 @@ class ServiceBase(object):
100
101
  else:
101
102
  raise AttributeError("Need to set exception, response or request_id")
102
103
 
103
- def download_file(self, url: str, filename: Optional[str] = None) -> AttachedFile:
104
+ def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
105
+ """
106
+ Download file
107
+
108
+ Download a file from the specified URL and save it with the given
109
+ filename.
110
+
111
+ Args:
112
+ url: URL of the file to download
113
+ filename: Name to save the downloaded file as. If not provided, the
114
+ filename will be determined from the Content-Disposition header or
115
+ the URL.
116
+ """
117
+
104
118
  return self.request.download_file(url=url, filename=filename)
@@ -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())
@@ -11,22 +11,25 @@ from pangea.utils import FileUploadParams, get_file_upload_params
11
11
 
12
12
 
13
13
  class FileScanRequest(APIRequestModel):
14
- """
15
- File Scan request data
16
-
17
- provider (str, optional): Provider of the information. Default provider defined by the configuration.
18
- verbose (bool, optional): Echo back the parameters of the API in the response
19
- raw (bool, optional): Return additional details from the provider.
20
- """
14
+ """File Scan request data."""
21
15
 
22
16
  verbose: Optional[bool] = None
17
+ """Echo back the parameters of the API in the response."""
18
+
23
19
  raw: Optional[bool] = None
20
+ """Return additional details from the provider."""
21
+
24
22
  provider: Optional[str] = None
23
+ """Provider of the information. Default provider defined by the configuration."""
24
+
25
25
  size: Optional[int] = None
26
26
  crc32c: Optional[str] = None
27
27
  sha256: Optional[str] = None
28
28
  source_url: Optional[str] = None
29
+ """A URL where the file to be scanned can be downloaded."""
30
+
29
31
  transfer_method: TransferMethod = TransferMethod.POST_URL
32
+ """The transfer method used to upload the file data."""
30
33
 
31
34
 
32
35
  class FileScanData(PangeaResponseResult):
@@ -71,7 +74,6 @@ class FileScan(ServiceBase):
71
74
  """
72
75
 
73
76
  service_name = "file-scan"
74
- version = "v1"
75
77
 
76
78
  def file_scan(
77
79
  self,
@@ -92,12 +94,14 @@ class FileScan(ServiceBase):
92
94
  OperationId: file_scan_post_v1_scan
93
95
 
94
96
  Args:
95
- file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
96
97
  file_path (str, optional): filepath to be opened and scanned
98
+ file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
97
99
  verbose (bool, optional): Echo the API parameters in the response
98
100
  raw (bool, optional): Include raw data from this provider
99
101
  provider (str, optional): Scan file using this provider
100
102
  sync_call (bool, optional): True to wait until server returns a result, False to return immediately and retrieve result asynchronously
103
+ transfer_method (TransferMethod, optional): Transfer method used to upload the file data.
104
+ source_url (str, optional): A URL where the Pangea APIs can fetch the contents of the input file.
101
105
 
102
106
  Raises:
103
107
  PangeaAPIException: If an API Error happens
@@ -118,6 +122,15 @@ class FileScan(ServiceBase):
118
122
  print(f"\\t{err.detail} \\n")
119
123
  """
120
124
 
125
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
126
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
127
+
128
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
129
+ raise ValueError(
130
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
131
+ )
132
+
133
+ files: Optional[List[Tuple]] = None
121
134
  if file or file_path:
122
135
  if file_path:
123
136
  file = open(file_path, "rb")
@@ -128,9 +141,9 @@ class FileScan(ServiceBase):
128
141
  size = params.size
129
142
  else:
130
143
  crc, sha, size = None, None, None
131
- files: List[Tuple] = [("upload", ("filename", file, "application/octet-stream"))]
132
- else:
133
- raise ValueError("Need to set file_path or file arguments")
144
+ files = [("upload", ("filename", file, "application/octet-stream"))]
145
+ elif source_url is None:
146
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
134
147
 
135
148
  input = FileScanRequest(
136
149
  verbose=verbose,
@@ -142,8 +155,12 @@ class FileScan(ServiceBase):
142
155
  transfer_method=transfer_method,
143
156
  source_url=source_url,
144
157
  )
145
- data = input.dict(exclude_none=True)
146
- return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
158
+ data = input.model_dump(exclude_none=True)
159
+ try:
160
+ return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
161
+ finally:
162
+ if file_path and file:
163
+ file.close()
147
164
 
148
165
  def request_upload_url(
149
166
  self,
@@ -164,7 +181,7 @@ class FileScan(ServiceBase):
164
181
  input.sha256 = params.sha256_hex
165
182
  input.size = params.size
166
183
 
167
- data = input.dict(exclude_none=True)
184
+ data = input.model_dump(exclude_none=True)
168
185
  return self.request.request_presigned_url("v1/scan", FileScanResult, data=data)
169
186
 
170
187