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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: PyPANRestV2
3
- Version: 2.1.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'].extend(valid_types)
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 MemberObj list with Address objects if 'member' list is provided
385
+ # Initialize container for resolved member objects
386
386
  self.MemberObj: list = []
387
- self.static: Dict[str, List[str]] = {}
388
- self.dynamic: Dict[str, List[str]] = {}
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
- The setter method for the 'static' attribute.
398
- Validates that the value is a dictionary containing the key 'member' with a value of type list,
399
- and this list contains at least one item.
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
- self._static = value
418
- self.entry.update({'static': value})
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
- The setter for the 'dynamic' attribute.
431
- Validates that the value is a dictionary with a key 'filter' whose value is a string of max length 2047.
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
- # Check if the value is a dictionary
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
- # If all checks pass, set the value
453
- self._dynamic = value
454
- self.entry.update({'dynamic': value})
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
- if not hasattr(self, 'static'):
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 self.member:
476
- self.member.append(member.name)
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 self.member:
480
- self.member.append(member)
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
- if not hasattr(self, 'static'):
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 self.member:
496
- self.member.remove(member_name)
497
- self.MemberObj = [obj for obj in self.MemberObj if obj.name != member_name]
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 hasattr(self, 'dynamic'):
501
- raise ValueError("Filter can only be set for a 'dynamic' type AddressGroup")
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
- self.dynamic = filter_str
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.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