Easier and more self documenting patterns for loading/saving Persistent
[python_utils.git] / acl.py
diff --git a/acl.py b/acl.py
index 2b347673af1b14dbb144ca9179d115a5eba642b0..726dafc72f0240a7371dd0da39b69c253c3c69bc 100644 (file)
--- a/acl.py
+++ b/acl.py
@@ -1,11 +1,15 @@
 #!/usr/bin/env python3
 
-from abc import ABC, abstractmethod
+# © Copyright 2021-2022, Scott Gasch
+
+"""This module defines various flavors of Access Control Lists."""
+
 import enum
 import fnmatch
 import logging
 import re
-from typing import Any, Callable, List, Optional, Set, Sequence
+from abc import ABC, abstractmethod
+from typing import Any, Callable, List, Optional, Sequence, Set
 
 from overrides import overrides
 
@@ -19,6 +23,7 @@ class Order(enum.Enum):
     """A helper to express the order of evaluation for allows/denies
     in an Access Control List.
     """
+
     UNDEFINED = 0
     ALLOW_DENY = 1
     DENY_ALLOW = 2
@@ -27,45 +32,41 @@ class Order(enum.Enum):
 class SimpleACL(ABC):
     """A simple Access Control List interface."""
 
-    def __init__(
-        self,
-        *,
-        order_to_check_allow_deny: Order,
-        default_answer: bool
-    ):
+    def __init__(self, *, order_to_check_allow_deny: Order, default_answer: bool):
         if order_to_check_allow_deny not in (
-                Order.ALLOW_DENY, Order.DENY_ALLOW
+            Order.ALLOW_DENY,
+            Order.DENY_ALLOW,
         ):
             raise Exception(
-                'order_to_check_allow_deny must be Order.ALLOW_DENY or ' +
-                'Order.DENY_ALLOW')
+                'order_to_check_allow_deny must be Order.ALLOW_DENY or ' + 'Order.DENY_ALLOW'
+            )
         self.order_to_check_allow_deny = order_to_check_allow_deny
         self.default_answer = default_answer
 
     def __call__(self, x: Any) -> bool:
         """Returns True if x is allowed, False otherwise."""
-        logger.debug(f'SimpleACL checking {x}')
+        logger.debug('SimpleACL checking %s', x)
         if self.order_to_check_allow_deny == Order.ALLOW_DENY:
             logger.debug('Checking allowed first...')
             if self.check_allowed(x):
-                logger.debug(f'{x} was allowed explicitly.')
+                logger.debug('%s was allowed explicitly.', x)
                 return True
             logger.debug('Checking denied next...')
             if self.check_denied(x):
-                logger.debug(f'{x} was denied explicitly.')
+                logger.debug('%s was denied explicitly.', x)
                 return False
         elif self.order_to_check_allow_deny == Order.DENY_ALLOW:
             logger.debug('Checking denied first...')
             if self.check_denied(x):
-                logger.debug(f'{x} was denied explicitly.')
+                logger.debug('%s was denied explicitly.', x)
                 return False
             if self.check_allowed(x):
-                logger.debug(f'{x} was allowed explicitly.')
+                logger.debug('%s was allowed explicitly.', x)
                 return True
 
         logger.debug(
-            f'{x} was not explicitly allowed or denied; ' +
-            f'using default answer ({self.default_answer})'
+            f'{x} was not explicitly allowed or denied; '
+            f'using default answer ({self.default_answer})'
         )
         return self.default_answer
 
@@ -82,15 +83,18 @@ class SimpleACL(ABC):
 
 class SetBasedACL(SimpleACL):
     """An ACL that allows or denies based on membership in a set."""
-    def __init__(self,
-                 *,
-                 allow_set: Optional[Set[Any]] = None,
-                 deny_set: Optional[Set[Any]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+
+    def __init__(
+        self,
+        *,
+        allow_set: Optional[Set[Any]] = None,
+        deny_set: Optional[Set[Any]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         super().__init__(
             order_to_check_allow_deny=order_to_check_allow_deny,
-            default_answer=default_answer
+            default_answer=default_answer,
         )
         self.allow_set = allow_set
         self.deny_set = deny_set
@@ -112,52 +116,55 @@ class AllowListACL(SetBasedACL):
     """Convenience subclass for a list that only allows known items.
     i.e. a 'allowlist'
     """
-    def __init__(self,
-                 *,
-                 allow_set: Optional[Set[Any]]) -> None:
+
+    def __init__(self, *, allow_set: Optional[Set[Any]]) -> None:
         super().__init__(
-            allow_set = allow_set,
-            order_to_check_allow_deny = Order.ALLOW_DENY,
-            default_answer = False)
+            allow_set=allow_set,
+            order_to_check_allow_deny=Order.ALLOW_DENY,
+            default_answer=False,
+        )
 
 
 class DenyListACL(SetBasedACL):
     """Convenience subclass for a list that only disallows known items.
     i.e. a 'blocklist'
     """
-    def __init__(self,
-                 *,
-                 deny_set: Optional[Set[Any]]) -> None:
+
+    def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
         super().__init__(
-            deny_set = deny_set,
-            order_to_check_allow_deny = Order.ALLOW_DENY,
-            default_answer = True)
+            deny_set=deny_set,
+            order_to_check_allow_deny=Order.ALLOW_DENY,
+            default_answer=True,
+        )
 
 
 class BlockListACL(SetBasedACL):
     """Convenience subclass for a list that only disallows known items.
     i.e. a 'blocklist'
     """
-    def __init__(self,
-                 *,
-                 deny_set: Optional[Set[Any]]) -> None:
+
+    def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
         super().__init__(
-            deny_set = deny_set,
-            order_to_check_allow_deny = Order.ALLOW_DENY,
-            default_answer = True)
+            deny_set=deny_set,
+            order_to_check_allow_deny=Order.ALLOW_DENY,
+            default_answer=True,
+        )
 
 
 class PredicateListBasedACL(SimpleACL):
     """An ACL that allows or denies by applying predicates."""
-    def __init__(self,
-                 *,
-                 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
-                 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+
+    def __init__(
+        self,
+        *,
+        allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
+        deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         super().__init__(
             order_to_check_allow_deny=order_to_check_allow_deny,
-            default_answer=default_answer
+            default_answer=default_answer,
         )
         self.allow_predicate_list = allow_predicate_list
         self.deny_predicate_list = deny_predicate_list
@@ -176,26 +183,27 @@ class PredicateListBasedACL(SimpleACL):
 
 
 class StringWildcardBasedACL(PredicateListBasedACL):
-    """An ACL that allows or denies based on string glob (*, ?) patterns."""
-    def __init__(self,
-                 *,
-                 allowed_patterns: Optional[List[str]] = None,
-                 denied_patterns: Optional[List[str]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+    """An ACL that allows or denies based on string glob :code:`(*, ?)`
+    patterns.
+    """
+
+    def __init__(
+        self,
+        *,
+        allowed_patterns: Optional[List[str]] = None,
+        denied_patterns: Optional[List[str]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         allow_predicates = []
         if allowed_patterns is not None:
             for pattern in allowed_patterns:
-                allow_predicates.append(
-                    lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
-                )
+                allow_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
         deny_predicates = None
         if denied_patterns is not None:
             deny_predicates = []
             for pattern in denied_patterns:
-                deny_predicates.append(
-                    lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
-                )
+                deny_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
 
         super().__init__(
             allow_predicate_list=allow_predicates,
@@ -207,26 +215,25 @@ class StringWildcardBasedACL(PredicateListBasedACL):
 
 class StringREBasedACL(PredicateListBasedACL):
     """An ACL that allows or denies by applying regexps."""
-    def __init__(self,
-                 *,
-                 allowed_regexs: Optional[List[re.Pattern]] = None,
-                 denied_regexs: Optional[List[re.Pattern]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+
+    def __init__(
+        self,
+        *,
+        allowed_regexs: Optional[List[re.Pattern]] = None,
+        denied_regexs: Optional[List[re.Pattern]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         allow_predicates = None
         if allowed_regexs is not None:
             allow_predicates = []
             for pattern in allowed_regexs:
-                allow_predicates.append(
-                    lambda x, pattern=pattern: pattern.match(x) is not None
-                )
+                allow_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
         deny_predicates = None
         if denied_regexs is not None:
             deny_predicates = []
             for pattern in denied_regexs:
-                deny_predicates.append(
-                    lambda x, pattern=pattern: pattern.match(x) is not None
-                )
+                deny_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
         super().__init__(
             allow_predicate_list=allow_predicates,
             deny_predicate_list=deny_predicates,
@@ -237,14 +244,17 @@ class StringREBasedACL(PredicateListBasedACL):
 
 class AnyCompoundACL(SimpleACL):
     """An ACL that allows if any of its subacls allow."""
-    def __init__(self,
-                 *,
-                 subacls: Optional[List[SimpleACL]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+
+    def __init__(
+        self,
+        *,
+        subacls: Optional[List[SimpleACL]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         super().__init__(
-            order_to_check_allow_deny = order_to_check_allow_deny,
-            default_answer = default_answer
+            order_to_check_allow_deny=order_to_check_allow_deny,
+            default_answer=default_answer,
         )
         self.subacls = subacls
 
@@ -263,14 +273,17 @@ class AnyCompoundACL(SimpleACL):
 
 class AllCompoundACL(SimpleACL):
     """An ACL that allows if all of its subacls allow."""
-    def __init__(self,
-                 *,
-                 subacls: Optional[List[SimpleACL]] = None,
-                 order_to_check_allow_deny: Order,
-                 default_answer: bool) -> None:
+
+    def __init__(
+        self,
+        *,
+        subacls: Optional[List[SimpleACL]] = None,
+        order_to_check_allow_deny: Order,
+        default_answer: bool,
+    ) -> None:
         super().__init__(
-            order_to_check_allow_deny = order_to_check_allow_deny,
-            default_answer = default_answer
+            order_to_check_allow_deny=order_to_check_allow_deny,
+            default_answer=default_answer,
         )
         self.subacls = subacls