ACL uses enums, some more tests, other stuff.
authorScott Gasch <[email protected]>
Sat, 10 Jul 2021 01:31:02 +0000 (18:31 -0700)
committerScott Gasch <[email protected]>
Sat, 10 Jul 2021 01:31:02 +0000 (18:31 -0700)
15 files changed:
acl.py
argparse_utils.py
bootstrap.py
config.py
constants.py
dateparse/dateparse_utils.py
datetime_utils.py
decorator_utils.py
deferred_operand.py
exceptions.py
id_generator.py
logging_utils.py
smart_future.py
tests/acl_test.py
thread_utils.py

diff --git a/acl.py b/acl.py
index 5040304a7913fe4e452ceb2dc5ef5f2a76b36491..e6bb9033f89319eb986adab2574a59171dbe899d 100644 (file)
--- a/acl.py
+++ b/acl.py
@@ -1,17 +1,25 @@
 #!/usr/bin/env python3
 
 from abc import ABC, abstractmethod
+import enum
 import fnmatch
 import logging
 import re
 from typing import Any, Callable, List, Optional, Set
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 
 logger = logging.getLogger(__name__)
 
 
-ACL_ORDER_ALLOW_DENY = 1
-ACL_ORDER_DENY_ALLOW = 2
+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
 
 
 class SimpleACL(ABC):
@@ -20,22 +28,22 @@ class SimpleACL(ABC):
     def __init__(
         self,
         *,
-        order_to_check_allow_deny: int,
+        order_to_check_allow_deny: Order,
         default_answer: bool
     ):
         if order_to_check_allow_deny not in (
-                ACL_ORDER_ALLOW_DENY, ACL_ORDER_DENY_ALLOW
+                Order.ALLOW_DENY, Order.DENY_ALLOW
         ):
             raise Exception(
-                'order_to_check_allow_deny must be ACL_ORDER_ALLOW_DENY or ' +
-                'ACL_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}')
-        if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY:
+        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.')
@@ -44,7 +52,7 @@ class SimpleACL(ABC):
             if self.check_denied(x):
                 logger.debug(f'{x} was denied explicitly.')
                 return False
-        elif self.order_to_check_allow_deny == ACL_ORDER_DENY_ALLOW:
+        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.')
@@ -76,7 +84,7 @@ class SetBasedACL(SimpleACL):
                  *,
                  allow_set: Optional[Set[Any]] = None,
                  deny_set: Optional[Set[Any]] = None,
-                 order_to_check_allow_deny: int,
+                 order_to_check_allow_deny: Order,
                  default_answer: bool) -> None:
         super().__init__(
             order_to_check_allow_deny=order_to_check_allow_deny,
@@ -96,13 +104,39 @@ class SetBasedACL(SimpleACL):
         return x in self.deny_set
 
 
+class AllowListACL(SetBasedACL):
+    """Convenience subclass for a list that only allows known items.
+    i.e. a 'whitelist'
+    """
+    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)
+
+
+class DenyListACL(SetBasedACL):
+    """Convenience subclass for a list that only disallows known items.
+    i.e. a 'blacklist'
+    """
+    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)
+
+
 class PredicateListBasedACL(SimpleACL):
     """An ACL that allows or denies by applying predicates."""
     def __init__(self,
                  *,
                  allow_predicate_list: List[Callable[[Any], bool]] = None,
                  deny_predicate_list: List[Callable[[Any], bool]] = None,
-                 order_to_check_allow_deny: int,
+                 order_to_check_allow_deny: Order,
                  default_answer: bool) -> None:
         super().__init__(
             order_to_check_allow_deny=order_to_check_allow_deny,
@@ -128,7 +162,7 @@ class StringWildcardBasedACL(PredicateListBasedACL):
                  *,
                  allowed_patterns: Optional[List[str]] = None,
                  denied_patterns: Optional[List[str]] = None,
-                 order_to_check_allow_deny: int,
+                 order_to_check_allow_deny: Order,
                  default_answer: bool) -> None:
         allow_predicates = []
         if allowed_patterns is not None:
@@ -158,7 +192,7 @@ class StringREBasedACL(PredicateListBasedACL):
                  *,
                  allowed_regexs: Optional[List[re.Pattern]] = None,
                  denied_regexs: Optional[List[re.Pattern]] = None,
-                 order_to_check_allow_deny: int,
+                 order_to_check_allow_deny: Order,
                  default_answer: bool) -> None:
         allow_predicates = None
         if allowed_regexs is not None:
@@ -182,21 +216,49 @@ class StringREBasedACL(PredicateListBasedACL):
         )
 
 
-class AnyCompoundACL(object):
+class AnyCompoundACL(SimpleACL):
     """An ACL that allows if any of its subacls allow."""
-    def __init__(self, subacls: List[SimpleACL]):
-        assert subacls is not 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
+        )
         self.subacls = subacls
 
-    def __call__(self, x: Any):
+    def check_allowed(self, x: Any) -> bool:
+        if self.subacls is None:
+            return False
         return any(acl(x) for acl in self.subacls)
 
+    def check_denied(self, x: Any) -> bool:
+        if self.subacls is None:
+            return False
+        return any(not acl(x) for acl in self.subacls)
+
 
-class AllCompoundACL(object):
+class AllCompoundACL(SimpleACL):
     """An ACL that allows if all of its subacls allow."""
-    def __init__(self, subacls: List[SimpleACL]):
-        assert subacls is not 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
+        )
         self.subacls = subacls
 
-    def __call__(self, x: Any):
+    def check_allowed(self, x: Any) -> bool:
+        if self.subacls is None:
+            return False
         return all(acl(x) for acl in self.subacls)
+
+    def check_denied(self, x: Any) -> bool:
+        if self.subacls is None:
+            return False
+        return any(not acl(x) for acl in self.subacls)
index 02db0f08e762e9a430c9f03f7ce08a4a0acdb7b6..3799a47ccbc1a34a599cdd443eaf5dfa33a64772 100644 (file)
@@ -5,6 +5,9 @@ import datetime
 import logging
 import os
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 logger = logging.getLogger(__name__)
 
 
index 94c8e9c0ecde3347832276d0a2177dd9ee76a410..da421b6f0a35dd2c0c269728f71427750e7e6708 100644 (file)
@@ -7,6 +7,8 @@ import sys
 import time
 import traceback
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 import argparse_utils
 import config
 
index 58a5e83eb75e8d80f942d59a9309de5f5d4c27f2..e7094f3a5ba6d6c4646a579d2e5e1e47cc8e330a 100644 (file)
--- a/config.py
+++ b/config.py
@@ -71,6 +71,9 @@ import re
 import sys
 from typing import Any, Dict, List
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 # Note: at this point in time, logging hasn't been configured and
 # anything we log will come out the root logger.
 
index d321737106d685e5de8c8395e066d4cc9204883f..fdc533bff82d0a51c2e39ee666ae60882ee6597f 100644 (file)
@@ -2,6 +2,9 @@
 
 """Universal constants."""
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 # Date/time based constants
 SECONDS_PER_MINUTE = 60
 SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE
index ad92ccd5d8d3e672bc0adc55ff2197912a833455..05bee8dee7d95d4124192d0b6a2ee553f7d2a9f7 100755 (executable)
@@ -15,10 +15,13 @@ import pytz
 
 import acl
 import bootstrap
-from decorator_utils import decorate_matching_methods_with
+from datetime_utils import (
+    TimeUnit, n_timeunits_from_base, datetime_to_date, date_to_datetime
+)
 from dateparse.dateparse_utilsLexer import dateparse_utilsLexer  # type: ignore
 from dateparse.dateparse_utilsListener import dateparse_utilsListener  # type: ignore
 from dateparse.dateparse_utilsParser import dateparse_utilsParser  # type: ignore
+from decorator_utils import decorate_matching_methods_with
 
 
 logger = logging.getLogger(__name__)
@@ -85,7 +88,7 @@ class RaisingErrorListener(antlr4.DiagnosticErrorListener):
             'exit*',
         ],
         denied_patterns=None,
-        order_to_check_allow_deny=acl.ACL_ORDER_DENY_ALLOW,
+        order_to_check_allow_deny=acl.Order.DENY_ALLOW,
         default_answer=False
     )
 )
@@ -105,7 +108,6 @@ class DateParser(dateparse_utilsListener):
         idea of "now" so that the code can be more easily unittested.
         Leave as None for real use cases.
         """
-        from datetime_utils import TimeUnit
         self.month_name_to_number = {
             'jan': 1,
             'feb': 2,
@@ -231,7 +233,6 @@ class DateParser(dateparse_utilsListener):
 
     def _reset(self):
         """Reset at init and between parses."""
-        from datetime_utils import datetime_to_date
         if self.override_now_for_test_purposes is None:
             self.now_datetime = datetime.datetime.now()
             self.today = datetime.date.today()
@@ -259,9 +260,8 @@ class DateParser(dateparse_utilsListener):
         name = name.replace('washi', 'presi')
         return name
 
-    def _figure_out_date_unit(self, orig: str) -> int:
+    def _figure_out_date_unit(self, orig: str) -> TimeUnit:
         """Figure out what unit a date expression piece is talking about."""
-        from datetime_utils import TimeUnit
         if 'month' in orig:
             return TimeUnit.MONTHS
         txt = orig.lower()[:3]
@@ -469,9 +469,6 @@ class DateParser(dateparse_utilsListener):
 
     def exitDateExpr(self, ctx: dateparse_utilsParser.DateExprContext) -> None:
         """When we leave the date expression, populate self.date."""
-        from datetime_utils import (
-            n_timeunits_from_base, datetime_to_date, date_to_datetime
-        )
         if 'special' in self.context:
             self.date = self._parse_special_date(self.context['special'])
         else:
@@ -514,7 +511,6 @@ class DateParser(dateparse_utilsListener):
 
     def exitTimeExpr(self, ctx: dateparse_utilsParser.TimeExprContext) -> None:
         # Simple time?
-        from datetime_utils import TimeUnit
         self.time = datetime.time(
             self.context['hour'],
             self.context['minute'],
@@ -641,7 +637,6 @@ class DateParser(dateparse_utilsListener):
     def exitDeltaTimeFraction(
             self, ctx: dateparse_utilsParser.DeltaTimeFractionContext
     ) -> None:
-        from datetime_utils import TimeUnit
         try:
             txt = ctx.getText().lower()[:4]
             if txt == 'quar':
@@ -874,7 +869,6 @@ class DateParser(dateparse_utilsListener):
     def exitNFoosFromTodayAgoExpr(
         self, ctx: dateparse_utilsParser.NFoosFromTodayAgoExprContext
     ) -> None:
-        from datetime_utils import n_timeunits_from_base
         d = self.now_datetime
         try:
             count = self._get_int(ctx.unsignedInt().getText())
@@ -900,7 +894,6 @@ class DateParser(dateparse_utilsListener):
     def exitDeltaRelativeToTodayExpr(
         self, ctx: dateparse_utilsParser.DeltaRelativeToTodayExprContext
     ) -> None:
-        from datetime_utils import n_timeunits_from_base
         d = self.now_datetime
         try:
             mod = ctx.thisNextLast()
@@ -1055,6 +1048,7 @@ class DateParser(dateparse_utilsListener):
             pass
 
 
 def main() -> None:
     parser = DateParser()
     for line in sys.stdin:
@@ -1072,5 +1066,4 @@ def main() -> None:
 
 
 if __name__ == "__main__":
-    main = bootstrap.initialize(main)
     main()
index 0b94283b01df595300ecc448f108303c2dba2b2e..795b427c31b4e57a4551aab26badd1f81b68c9a2 100644 (file)
@@ -6,7 +6,7 @@ import datetime
 import enum
 import logging
 import re
-from typing import NewType, Tuple
+from typing import Any, NewType, Tuple
 
 import holidays  # type: ignore
 import pytz
@@ -77,12 +77,28 @@ class TimeUnit(enum.Enum):
     MONTHS = 13
     YEARS = 14
 
+    @classmethod
+    def is_valid(cls, value: Any):
+        if type(value) is int:
+            print("int")
+            return value in cls._value2member_map_
+        elif type(value) is TimeUnit:
+            print("TimeUnit")
+            return value.value in cls._value2member_map_
+        elif type(value) is str:
+            print("str")
+            return value in cls._member_names_
+        else:
+            print(type(value))
+            return False
+
 
 def n_timeunits_from_base(
     count: int,
     unit: TimeUnit,
     base: datetime.datetime
 ) -> datetime.datetime:
+    assert TimeUnit.is_valid(unit)
     if count == 0:
         return base
 
index 375cbad436feca20d57750b8276537c94f66f060..2817239c88c2396b0e5dcc56e7c535b8afdd99d9 100644 (file)
@@ -18,6 +18,8 @@ import traceback
 from typing import Callable, Optional
 import warnings
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 import exceptions
 
 
index f2af66c4cc3ee908767af2b024a37bba096ff5d9..4b12279b5726a2eb3a97eed1c20b1f708870143b 100644 (file)
@@ -3,6 +3,9 @@
 from abc import ABC, abstractmethod
 from typing import Any, Generic, TypeVar
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 T = TypeVar('T')
 
 
index 3e0a2d080b56742d71d1d02ee45ac13fd8d5a146..59aa262d786a11297e1462ff782ed2e47034c16d 100644 (file)
@@ -1,5 +1,8 @@
 #!/usr/bin/env python3
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 class PreconditionException(AssertionError):
     pass
 
index cc287bb5819e0f31db1e504f0a136b1333f79dfe..c5a0d93e6908838c1f382b386a64957f3c2ea3fc 100644 (file)
@@ -3,6 +3,9 @@
 import itertools
 import logging
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 logger = logging.getLogger(__name__)
 generators = {}
 
index ba5270fad87a1e62ccc8be99fe7eaba786583dc8..700bfabcf9b9bbba8b28ca199253de40296a6ccc 100644 (file)
@@ -10,6 +10,8 @@ import os
 import pytz
 import sys
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 import argparse_utils
 import config
 
index e4832d43d5b1674988628e5dae43a67cf8ed0565..1c95973f48ab3bc3a0c9bcd90fa13498f851b8a9 100644 (file)
@@ -6,6 +6,8 @@ import concurrent.futures as fut
 import time
 from typing import Callable, List, TypeVar
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
 from deferred_operand import DeferredOperand
 import id_generator
 
index 810aedf48bd7bd925132a1c5b940cf62c09f1462..4c1cf21457d783f9baeaf886cdc68c7c8647917b 100755 (executable)
@@ -13,7 +13,7 @@ class TestSimpleACL(unittest.TestCase):
         even = acl.SetBasedACL(
             allow_set = set([2, 4, 6, 8, 10]),
             deny_set = set([1, 3, 5, 7, 9]),
-            order_to_check_allow_deny = acl.ACL_ORDER_ALLOW_DENY,
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
             default_answer = False
         )
         self.assertTrue(even(2))
@@ -23,12 +23,12 @@ class TestSimpleACL(unittest.TestCase):
     def test_wildcard_based_acl(self):
         a_or_b = acl.StringWildcardBasedACL(
             allowed_patterns = ['a*', 'b*'],
-            order_to_check_allow_deny = acl.ACL_ORDER_ALLOW_DENY,
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
             default_answer = False
         )
         self.assertTrue(a_or_b('aardvark'))
-        self.assertTrue(a_or_b('bubblegum'))
-        self.assertFalse(a_or_b('charlie'))
+        self.assertTrue(a_or_b('baboon'))
+        self.assertFalse(a_or_b('cheetah'))
 
     def test_re_based_acl(self):
         weird = acl.StringREBasedACL(
@@ -36,12 +36,56 @@ class TestSimpleACL(unittest.TestCase):
                 re.compile('^a.*a$'),
                 re.compile('^b.*b$')
             ],
-            order_to_check_allow_deny = acl.ACL_ORDER_ALLOW_DENY,
+            order_to_check_allow_deny = acl.Order.DENY_ALLOW,
             default_answer = True
         )
         self.assertTrue(weird('aardvark'))
         self.assertFalse(weird('anaconda'))
-        self.assertFalse(weird('beelzebub'))
+        self.assertFalse(weird('blackneb'))
+        self.assertTrue(weird('crow'))
+
+    def test_compound_acls_disjunction(self):
+        a_b_c = acl.StringWildcardBasedACL(
+            allowed_patterns = ['a*', 'b*', 'c*'],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False
+        )
+        c_d_e = acl.StringWildcardBasedACL(
+            allowed_patterns = ['c*', 'd*', 'e*'],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False
+        )
+        disjunction = acl.AnyCompoundACL(
+            subacls = [a_b_c, c_d_e],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False,
+        )
+        self.assertTrue(disjunction('aardvark'))
+        self.assertTrue(disjunction('caribou'))
+        self.assertTrue(disjunction('eagle'))
+        self.assertFalse(disjunction('newt'))
+
+    def test_compound_acls_conjunction(self):
+        a_b_c = acl.StringWildcardBasedACL(
+            allowed_patterns = ['a*', 'b*', 'c*'],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False
+        )
+        c_d_e = acl.StringWildcardBasedACL(
+            allowed_patterns = ['c*', 'd*', 'e*'],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False
+        )
+        conjunction = acl.AllCompoundACL(
+            subacls = [a_b_c, c_d_e],
+            order_to_check_allow_deny = acl.Order.ALLOW_DENY,
+            default_answer = False,
+        )
+        self.assertFalse(conjunction('aardvark'))
+        self.assertTrue(conjunction('caribou'))
+        self.assertTrue(conjunction('condor'))
+        self.assertFalse(conjunction('eagle'))
+        self.assertFalse(conjunction('newt'))
 
 
 if __name__ == '__main__':
index af6e0e1abe840946a8754e426ca93bd653545ca5..bb15c034b9e4e02f273b98dbc77410072878e089 100644 (file)
@@ -6,6 +6,9 @@ import os
 import threading
 from typing import Callable, Optional, Tuple
 
+# This module is commonly used by others in here and should avoid
+# taking any unnecessary dependencies back on them.
+
 logger = logging.getLogger(__name__)