PyPANRestV2 2.1.1__tar.gz → 2.1.2__tar.gz
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.
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/PKG-INFO +4 -2
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Objects.py +73 -56
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pyproject.toml +1 -1
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/README.md +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/ApplicationHelper.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Base.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Device.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Exceptions.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Network.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Panorama.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/Policies.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/XDR.py +0 -0
- {pypanrestv2-2.1.1 → pypanrestv2-2.1.2}/pypanrestv2/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPANRestV2
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Python tools for interacting with Palo Alto Networks REST API.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Mark Rzepa
|
|
@@ -10,6 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
15
|
Requires-Dist: dnspython (>=2.6.1)
|
|
14
16
|
Requires-Dist: icecream (>=2.1.3)
|
|
15
17
|
Requires-Dist: pycountry (>=23.12.11)
|
|
@@ -378,80 +378,96 @@ class AddressGroups(Object):
|
|
|
378
378
|
"""
|
|
379
379
|
|
|
380
380
|
valid_types = ['static', 'dynamic']
|
|
381
|
-
CompareAttributeList = ['member', 'filter']
|
|
381
|
+
CompareAttributeList = ['member', 'filter'] + valid_types
|
|
382
382
|
|
|
383
383
|
def __init__(self, PANDevice, **kwargs):
|
|
384
384
|
super().__init__(PANDevice, max_name_length=64, max_description_length=1024, has_tags=False, **kwargs)
|
|
385
|
-
# Initialize
|
|
385
|
+
# Initialize container for resolved member objects
|
|
386
386
|
self.MemberObj: list = []
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
|
|
388
|
+
# Start unset; allow instantiation without static/dynamic
|
|
389
|
+
self._static: Optional[Dict[str, List[str]]] = None
|
|
390
|
+
self._dynamic: Optional[Dict[str, str]] = None
|
|
391
|
+
|
|
392
|
+
# Optionally accept one of 'static' or 'dynamic' from kwargs
|
|
393
|
+
provided_static = kwargs.get('static')
|
|
394
|
+
provided_dynamic = kwargs.get('dynamic')
|
|
395
|
+
|
|
396
|
+
if provided_static is not None and provided_dynamic is not None:
|
|
397
|
+
raise ValueError("Only one of 'static' or 'dynamic' can be provided.")
|
|
398
|
+
|
|
399
|
+
if provided_static is not None:
|
|
400
|
+
self.static = provided_static
|
|
401
|
+
elif provided_dynamic is not None:
|
|
402
|
+
self.dynamic = provided_dynamic
|
|
389
403
|
|
|
390
404
|
@property
|
|
391
405
|
def static(self):
|
|
392
406
|
return self._static
|
|
393
407
|
|
|
394
408
|
@static.setter
|
|
395
|
-
def static(self, value: dict):
|
|
409
|
+
def static(self, value: Optional[dict]):
|
|
396
410
|
"""
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
:param value: The value to set for the 'static' attribute.
|
|
402
|
-
:type value: dict
|
|
403
|
-
:raises ValueError: If the value does not meet the specified criteria.
|
|
411
|
+
Set or clear the 'static' attribute.
|
|
412
|
+
When set, it must be a dict containing key 'member' with a non-empty list.
|
|
413
|
+
Setting 'static' clears 'dynamic' (mutually exclusive).
|
|
404
414
|
"""
|
|
415
|
+
if value is None:
|
|
416
|
+
self._static = None
|
|
417
|
+
# Remove from entry if present
|
|
418
|
+
self.entry.pop('static', None)
|
|
419
|
+
return
|
|
420
|
+
|
|
405
421
|
if not isinstance(value, dict):
|
|
406
422
|
raise ValueError("The 'static' attribute must be a dictionary.")
|
|
407
|
-
|
|
408
423
|
if 'member' not in value:
|
|
409
424
|
raise ValueError("The dictionary must contain the key 'member'.")
|
|
410
|
-
|
|
411
425
|
if not isinstance(value['member'], list):
|
|
412
426
|
raise ValueError("The 'member' key must have a list as its value.")
|
|
413
|
-
|
|
414
427
|
if len(value['member']) < 1:
|
|
415
428
|
raise ValueError("The list under 'member' key must contain at least one item.")
|
|
416
429
|
|
|
417
|
-
|
|
418
|
-
self.
|
|
430
|
+
# Set static and clear dynamic to ensure mutual exclusivity
|
|
431
|
+
self._static = {'member': list(value['member'])}
|
|
432
|
+
self.entry.update({'static': self._static})
|
|
433
|
+
# Clear dynamic side if set
|
|
434
|
+
self._dynamic = None
|
|
435
|
+
self.entry.pop('dynamic', None)
|
|
419
436
|
|
|
420
437
|
@property
|
|
421
|
-
def dynamic(self) -> dict:
|
|
438
|
+
def dynamic(self) -> Optional[dict]:
|
|
422
439
|
"""
|
|
423
440
|
The 'dynamic' property getter.
|
|
424
441
|
"""
|
|
425
442
|
return self._dynamic
|
|
426
443
|
|
|
427
444
|
@dynamic.setter
|
|
428
|
-
def dynamic(self, value: dict) -> None:
|
|
445
|
+
def dynamic(self, value: Optional[dict]) -> None:
|
|
429
446
|
"""
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
:param value: The dictionary to set as the 'dynamic' attribute.
|
|
434
|
-
:raises ValueError: If the value does not meet the specified criteria.
|
|
447
|
+
Set or clear the 'dynamic' attribute.
|
|
448
|
+
When set, it must be a dict with a key 'filter' whose value is a string of max length 2047.
|
|
449
|
+
Setting 'dynamic' clears 'static' (mutually exclusive).
|
|
435
450
|
"""
|
|
436
|
-
|
|
451
|
+
if value is None:
|
|
452
|
+
self._dynamic = None
|
|
453
|
+
self.entry.pop('dynamic', None)
|
|
454
|
+
return
|
|
455
|
+
|
|
437
456
|
if not isinstance(value, dict):
|
|
438
457
|
raise ValueError("The 'dynamic' attribute must be a dictionary.")
|
|
439
|
-
|
|
440
|
-
# Check if the dictionary has a key named 'filter'
|
|
441
458
|
if 'filter' not in value:
|
|
442
459
|
raise ValueError("The dictionary must contain the key 'filter'.")
|
|
443
|
-
|
|
444
|
-
# Check if the value of 'filter' is a string
|
|
445
460
|
if not isinstance(value['filter'], str):
|
|
446
461
|
raise ValueError("The 'filter' key must have a string as its value.")
|
|
447
|
-
|
|
448
|
-
# Check if the string length of 'filter' is within the limit
|
|
449
462
|
if len(value['filter']) > 2047:
|
|
450
463
|
raise ValueError("The 'filter' value must not exceed 2047 characters.")
|
|
451
464
|
|
|
452
|
-
#
|
|
453
|
-
self._dynamic = value
|
|
454
|
-
self.entry.update({'dynamic':
|
|
465
|
+
# Set dynamic and clear static to ensure mutual exclusivity
|
|
466
|
+
self._dynamic = {'filter': value['filter']}
|
|
467
|
+
self.entry.update({'dynamic': self._dynamic})
|
|
468
|
+
# Clear static side if set
|
|
469
|
+
self._static = None
|
|
470
|
+
self.entry.pop('static', None)
|
|
455
471
|
|
|
456
472
|
@property
|
|
457
473
|
def MemberObj(self):
|
|
@@ -469,22 +485,27 @@ class AddressGroups(Object):
|
|
|
469
485
|
del self._MemberObj
|
|
470
486
|
|
|
471
487
|
def add_member(self, member):
|
|
472
|
-
|
|
488
|
+
# Ensure we are in static mode
|
|
489
|
+
if self.static is None:
|
|
473
490
|
raise ValueError("Can only add members to a 'static' type AddressGroup")
|
|
491
|
+
members = self._static.setdefault('member', [])
|
|
474
492
|
if isinstance(member, Addresses):
|
|
475
|
-
if member.name not in
|
|
476
|
-
|
|
493
|
+
if member.name not in members:
|
|
494
|
+
members.append(member.name)
|
|
477
495
|
self.MemberObj.append(member)
|
|
478
496
|
elif isinstance(member, str):
|
|
479
|
-
if member not in
|
|
480
|
-
|
|
481
|
-
# Optionally, resolve the Addresses object from the name and add to MemberObj
|
|
497
|
+
if member not in members:
|
|
498
|
+
members.append(member)
|
|
482
499
|
else:
|
|
483
500
|
raise TypeError("Member must be an Addresses object or a string")
|
|
501
|
+
# Reflect changes into entry
|
|
502
|
+
self.entry.update({'static': self._static})
|
|
484
503
|
|
|
485
504
|
def remove_member(self, member):
|
|
486
|
-
|
|
505
|
+
# Ensure we are in static mode
|
|
506
|
+
if self.static is None:
|
|
487
507
|
raise ValueError("Can only remove members from a 'static' type AddressGroup")
|
|
508
|
+
members = self._static.get('member', [])
|
|
488
509
|
if isinstance(member, Addresses):
|
|
489
510
|
member_name = member.name
|
|
490
511
|
elif isinstance(member, str):
|
|
@@ -492,35 +513,31 @@ class AddressGroups(Object):
|
|
|
492
513
|
else:
|
|
493
514
|
raise TypeError("Member must be an Addresses object or a string")
|
|
494
515
|
|
|
495
|
-
if member_name in
|
|
496
|
-
|
|
497
|
-
self.MemberObj = [obj for obj in self.MemberObj if obj
|
|
516
|
+
if member_name in members:
|
|
517
|
+
members.remove(member_name)
|
|
518
|
+
self.MemberObj = [obj for obj in self.MemberObj if getattr(obj, 'name', None) != member_name]
|
|
519
|
+
# Reflect changes into entry
|
|
520
|
+
self.entry.update({'static': self._static})
|
|
498
521
|
|
|
499
522
|
def set_filter(self, filter_str):
|
|
500
|
-
if not
|
|
501
|
-
|
|
523
|
+
# Ensure we are in dynamic mode (and set if not yet set)
|
|
524
|
+
if not isinstance(filter_str, str):
|
|
525
|
+
raise TypeError("Filter must be a string")
|
|
502
526
|
if len(filter_str) > 2047:
|
|
503
527
|
raise ValueError("Filter length exceeds the maximum allowed characters (2047)")
|
|
504
|
-
|
|
528
|
+
# This will also clear static via the setter
|
|
529
|
+
self.dynamic = {'filter': filter_str}
|
|
505
530
|
|
|
506
531
|
def get_object(self, obj_type, name: str, location: str, device_group: str, vsys: str) -> Optional[Any]:
|
|
507
532
|
"""
|
|
508
533
|
Attempt to instantiate an object of type `obj_type` with the provided parameters.
|
|
509
534
|
Returns the instantiated object if it exists, None otherwise.
|
|
510
|
-
|
|
511
|
-
:param obj_type: The class of the object to be instantiated.
|
|
512
|
-
:param name: The name of the object.
|
|
513
|
-
:param location: The location of the object.
|
|
514
|
-
:param device_group: The device group the object belongs to.
|
|
515
|
-
:param vsys: The virtual system the object belongs to.
|
|
516
|
-
:return: An instance of `obj_type` if successful, None otherwise.
|
|
517
535
|
"""
|
|
518
536
|
try:
|
|
519
537
|
obj = obj_type(PANDevice=self.PANDevice, name=name, location=location, device_group=device_group, vsys=vsys)
|
|
520
538
|
if obj.get(ANYLOCATION=True, IsSearch=True):
|
|
521
539
|
return obj
|
|
522
540
|
except Exception as e:
|
|
523
|
-
# Here you should log the exception e
|
|
524
541
|
logger.error(f"Error instantiating object of type {obj_type.__name__}: {e}")
|
|
525
542
|
return None
|
|
526
543
|
|
|
@@ -530,7 +547,7 @@ class AddressGroups(Object):
|
|
|
530
547
|
Tries to instantiate Address objects first; if that fails, tries AddressGroups.
|
|
531
548
|
Raises an exception if the static member list is empty or the objects cannot be found.
|
|
532
549
|
"""
|
|
533
|
-
members = self.static.get('member')
|
|
550
|
+
members = (self.static or {}).get('member')
|
|
534
551
|
if not members:
|
|
535
552
|
raise ValueError('Cannot populate an empty group.')
|
|
536
553
|
|
|
@@ -3,7 +3,7 @@ requires = ["poetry-core>=1.0.0"]
|
|
|
3
3
|
build-backend = "poetry.core.masonry.api"
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "PyPANRestV2"
|
|
6
|
-
version = "2.1.
|
|
6
|
+
version = "2.1.2"
|
|
7
7
|
description = "Python tools for interacting with Palo Alto Networks REST API."
|
|
8
8
|
authors = [
|
|
9
9
|
"Mark Rzepa <mark@rzepa.com>"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|