Rename some directories (e.g. typez -> types) since they don't collide with
authorScott Gasch <[email protected]>
Sun, 30 Oct 2022 19:40:12 +0000 (12:40 -0700)
committerScott Gasch <[email protected]>
Sun, 30 Oct 2022 19:40:12 +0000 (12:40 -0700)
any stdlib names.

28 files changed:
docs/pyutils.datetimes.rst [moved from docs/pyutils.datetimez.rst with 100% similarity]
docs/pyutils.rst
docs/pyutils.types.rst [moved from docs/pyutils.typez.rst with 100% similarity]
examples/cron/cron.py
examples/reminder/reminder.py
examples/wordle/wordle.py
src/pyutils/argparse_utils.py
src/pyutils/datetimes/.gitignore [moved from src/pyutils/datetimez/.gitignore with 100% similarity]
src/pyutils/datetimes/__init__.py [moved from src/pyutils/datetimez/__init__.py with 100% similarity]
src/pyutils/datetimes/constants.py [moved from src/pyutils/datetimez/constants.py with 100% similarity]
src/pyutils/datetimes/dateparse_utils.g4 [moved from src/pyutils/datetimez/dateparse_utils.g4 with 100% similarity]
src/pyutils/datetimes/dateparse_utils.py [moved from src/pyutils/datetimez/dateparse_utils.py with 99% similarity]
src/pyutils/datetimes/datetime_utils.py [moved from src/pyutils/datetimez/datetime_utils.py with 97% similarity]
src/pyutils/files/file_utils.py
src/pyutils/files/lockfile.py
src/pyutils/parallelize/executors.py
src/pyutils/string_utils.py
src/pyutils/types/__init__.py [moved from src/pyutils/typez/__init__.py with 100% similarity]
src/pyutils/types/centcount.py [moved from src/pyutils/typez/centcount.py with 88% similarity]
src/pyutils/types/histogram.py [moved from src/pyutils/typez/histogram.py with 100% similarity]
src/pyutils/types/money.py [moved from src/pyutils/typez/money.py with 84% similarity]
src/pyutils/types/rate.py [moved from src/pyutils/typez/rate.py with 100% similarity]
src/pyutils/types/type_utils.py [moved from src/pyutils/typez/type_utils.py with 100% similarity]
tests/datetimes/dateparse_utils_test.py [new file with mode: 0755]
tests/datetimez/dateparse_utils_test.py [deleted file]
tests/types/centcount_test.py [moved from tests/typez/centcount_test.py with 92% similarity]
tests/types/money_test.py [moved from tests/typez/money_test.py with 91% similarity]
tests/types/rate_test.py [moved from tests/typez/rate_test.py with 94% similarity]

index 2e96cbb530d40ffcccf50d138588b34a936dca3a..7d19ab70d7e5cd236d154cbdad4a8e0d4f53fbc7 100644 (file)
@@ -50,7 +50,7 @@ Most code includes inline documentation and doctests.  I've tried to
 organize it into logical packages based on the code's functionality.
 Note that when words would collide with a Python standard library or
 reserved keyword I've used a 'z' at the end, e.g. 'collectionz'
-instead of 'collections', 'typez' instead of 'type', etc...
+instead of 'collections'.
 
 There's some example code that uses various features of this project checked
 in under `examples/ <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=tree;f=examples;h=d9744bf2b171ba7a9ff21ae1d3862b673647fff4;hb=HEAD>`_ that you can check out.  See the `README <http://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=examples/README;hb=HEAD>`__ in that directory for more information
@@ -113,12 +113,12 @@ Subpackages
 
    pyutils.collectionz
    pyutils.compress
-   pyutils.datetimez
+   pyutils.datetimes
    pyutils.files
    pyutils.parallelize
    pyutils.search
    pyutils.security
-   pyutils.typez
+   pyutils.types
 
 Submodules
 ----------
index 7acc419cac84c81aef3a939f271e0c6f37cfeafb..4b7d5eb1922d33f7ce54a912d5c4cd963c399fb0 100755 (executable)
@@ -11,55 +11,55 @@ import sys
 from typing import Optional
 
 from pyutils import bootstrap, config, exec_utils, stopwatch
-from pyutils.datetimez import datetime_utils
+from pyutils.datetimes import datetime_utils
 from pyutils.files import file_utils, lockfile
 
 logger = logging.getLogger(__name__)
 
 cfg = config.add_commandline_args(
-    f'Python Cron Runner ({__file__})',
-    'Wrapper for cron commands with locking, timeouts, and accounting.',
+    f"Python Cron Runner ({__file__})",
+    "Wrapper for cron commands with locking, timeouts, and accounting.",
 )
 cfg.add_argument(
-    '--lockfile',
+    "--lockfile",
     default=None,
-    metavar='LOCKFILE_PATH',
-    help='Path to the lockfile to use to ensure that two instances of a command do not execute contemporaneously.',
+    metavar="LOCKFILE_PATH",
+    help="Path to the lockfile to use to ensure that two instances of a command do not execute contemporaneously.",
 )
 cfg.add_argument(
-    '--lockfile_audit_record',
+    "--lockfile_audit_record",
     default=None,
-    metavar='LOCKFILE_AUDIT_RECORD_FILENAME',
-    help='Path to a record of when the logfile was held/released and for what reason',
+    metavar="LOCKFILE_AUDIT_RECORD_FILENAME",
+    help="Path to a record of when the logfile was held/released and for what reason",
 )
 cfg.add_argument(
-    '--timeout',
+    "--timeout",
     type=str,
-    metavar='TIMEOUT',
+    metavar="TIMEOUT",
     default=None,
     help='Maximum time for lock acquisition + command execution.  Undecorated for seconds but "3m" or "1h 15m" work too.',
 )
 cfg.add_argument(
-    '--timestamp',
+    "--timestamp",
     type=str,
-    metavar='TIMESTAMP_FILE',
+    metavar="TIMESTAMP_FILE",
     default=None,
-    help='The /timestamp/TIMESTAMP_FILE file tracking the work being done; files\' mtimes will be set to the last successful run of a command for accounting purposes.',
+    help="The /timestamp/TIMESTAMP_FILE file tracking the work being done; files' mtimes will be set to the last successful run of a command for accounting purposes.",
 )
 cfg.add_argument(
-    '--max_frequency',
+    "--max_frequency",
     type=str,
-    metavar='FREQUENCY',
+    metavar="FREQUENCY",
     default=None,
     help='The maximum frequency with which to do this work; even if the wrapper is invoked more often than this it will not run the command.  Requires --timestamp.  Undecorated for seconds but "3h" or "1h 15m" work too.',
 )
 cfg.add_argument(
-    '--command',
-    nargs='*',
+    "--command",
+    nargs="*",
     required=True,
     type=str,
-    metavar='COMMANDLINE',
-    help='The commandline to run under a lock.',
+    metavar="COMMANDLINE",
+    help="The commandline to run under a lock.",
 )
 config.overwrite_argparse_epilog(
     """
@@ -77,51 +77,51 @@ cron.py's exit value:
 
 def run_command(timeout: Optional[int], timestamp_file: Optional[str]) -> int:
     """Run cron command"""
-    cmd = ' '.join(config.config['command'])
+    cmd = " ".join(config.config["command"])
     logger.info('cron cmd = "%s"', cmd)
-    logger.debug('shell environment:')
+    logger.debug("shell environment:")
     for var in os.environ:
         val = os.environ[var]
-        logger.debug('%s = %s', var, val)
-    logger.debug('____ (↓↓↓ output from the subprocess appears below here ↓↓↓) ____')
+        logger.debug("%s = %s", var, val)
+    logger.debug("____ (↓↓↓ output from the subprocess appears below here ↓↓↓) ____")
     try:
         with stopwatch.Timer() as t:
             ret = exec_utils.cmd_exitcode(cmd, timeout)
         logger.debug(
-            f'____ (↑↑↑ subprocess finished in {t():.2f}s, exit value was {ret} ↑↑↑) ____'
+            f"____ (↑↑↑ subprocess finished in {t():.2f}s, exit value was {ret} ↑↑↑) ____"
         )
         if timestamp_file is not None and os.path.exists(timestamp_file):
-            logger.debug('Touching %s', timestamp_file)
+            logger.debug("Touching %s", timestamp_file)
             file_utils.touch_file(timestamp_file)
         return ret
     except Exception as e:
         logger.exception(e)
-        print('Cron subprocess failed, giving up.', file=sys.stderr)
-        logger.warning('Cron subprocess failed, giving up')
+        print("Cron subprocess failed, giving up.", file=sys.stderr)
+        logger.warning("Cron subprocess failed, giving up")
         return -1000
 
 
 @bootstrap.initialize
 def main() -> int:
     """Entry point"""
-    if config.config['timestamp']:
+    if config.config["timestamp"]:
         timestamp_file = f"/timestamps/{config.config['timestamp']}"
         if not file_utils.does_file_exist(timestamp_file):
             logger.error(
-                '--timestamp argument\'s target file (%s) must already exist.',
+                "--timestamp argument's target file (%s) must already exist.",
                 timestamp_file,
             )
             sys.exit(-1)
     else:
         timestamp_file = None
-        if config.config['max_frequency']:
+        if config.config["max_frequency"]:
             config.error(
-                'The --max_frequency argument requires the --timestamp argument.'
+                "The --max_frequency argument requires the --timestamp argument."
             )
 
     now = datetime.datetime.now()
     if timestamp_file is not None and os.path.exists(timestamp_file):
-        max_frequency = config.config['max_frequency']
+        max_frequency = config.config["max_frequency"]
         if max_frequency is not None:
             max_delta = datetime_utils.parse_duration(max_frequency)
             if max_delta > 0:
@@ -134,56 +134,56 @@ def main() -> int:
                     )
                     sys.exit(0)
 
-    timeout = config.config['timeout']
+    timeout = config.config["timeout"]
     if timeout is not None:
         timeout = datetime_utils.parse_duration(timeout)
         assert timeout > 0
-        logger.debug('Timeout is %ss', timeout)
+        logger.debug("Timeout is %ss", timeout)
         lockfile_expiration = datetime.datetime.now().timestamp() + timeout
     else:
-        logger.debug('Timeout not specified; no lockfile expiration.')
+        logger.debug("Timeout not specified; no lockfile expiration.")
         lockfile_expiration = None
 
-    lockfile_path = config.config['lockfile']
+    lockfile_path = config.config["lockfile"]
     if lockfile_path is not None:
-        logger.debug('Attempting to acquire lockfile %s...', lockfile_path)
+        logger.debug("Attempting to acquire lockfile %s...", lockfile_path)
         try:
             with lockfile.LockFile(
                 lockfile_path,
                 do_signal_cleanup=True,
-                override_command=' '.join(config.config['command']),
+                override_command=" ".join(config.config["command"]),
                 expiration_timestamp=lockfile_expiration,
             ) as lf:
-                record = config.config['lockfile_audit_record']
-                cmd = ' '.join(config.config['command'])
+                record = config.config["lockfile_audit_record"]
+                cmd = " ".join(config.config["command"])
                 if record:
                     start = lf.locktime
-                    with open(record, 'a') as wf:
-                        print(f'{lockfile_path}, ACQUIRE, {start}, {cmd}', file=wf)
+                    with open(record, "a") as wf:
+                        print(f"{lockfile_path}, ACQUIRE, {start}, {cmd}", file=wf)
                 retval = run_command(timeout, timestamp_file)
                 if record:
                     end = datetime.datetime.now().timestamp()
                     duration = datetime_utils.describe_duration_briefly(end - start)
-                    with open(record, 'a') as wf:
+                    with open(record, "a") as wf:
                         print(
-                            f'{lockfile_path}, RELEASE({duration}), {end}, {cmd}',
+                            f"{lockfile_path}, RELEASE({duration}), {end}, {cmd}",
                             file=wf,
                         )
                 return retval
         except lockfile.LockFileException as e:
             logger.exception(e)
-            msg = f'Failed to acquire {lockfile_path}, giving up.'
+            msg = f"Failed to acquire {lockfile_path}, giving up."
             logger.error(msg)
             print(msg, file=sys.stderr)
             return 1000
     else:
-        logger.debug('No lockfile indicated; not locking anything.')
+        logger.debug("No lockfile indicated; not locking anything.")
         return run_command(timeout, timestamp_file)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     # Insist that our logger.whatever('messages') make their way into
     # syslog with a facility=LOG_CRON, please.  Yes, this is hacky.
-    sys.argv.append('--logging_syslog')
-    sys.argv.append('--logging_syslog_facility=CRON')
+    sys.argv.append("--logging_syslog")
+    sys.argv.append("--logging_syslog_facility=CRON")
     main()
index 550cb54b5cde2a89a0aee1cf11a4c9df03c52d8a..a7ed8e775c607b583354e711631b6d6adadd84e0 100755 (executable)
@@ -14,7 +14,7 @@ from typing import Dict, List, Optional
 
 from pyutils import argparse_utils, bootstrap, config, persistent, string_utils
 from pyutils.ansi import fg, reset
-from pyutils.datetimez import dateparse_utils as dateparse
+from pyutils.datetimes import dateparse_utils as dateparse
 from pyutils.files import file_utils
 
 logger = logging.getLogger(__name__)
@@ -24,24 +24,24 @@ cfg = config.add_commandline_args(
 cfg.add_argument(
     "--reminder_filename",
     type=argparse_utils.valid_filename,
-    default='.reminder',
-    metavar='FILENAME',
+    default=".reminder",
+    metavar="FILENAME",
     help="Override the .reminder filepath",
 )
 cfg.add_argument(
-    '--reminder_cache_file',
+    "--reminder_cache_file",
     type=str,
     default=f'{os.environ["HOME"]}/.reminder_cache',
-    metavar='FILENAME',
-    help='Override the .reminder cache location',
+    metavar="FILENAME",
+    help="Override the .reminder cache location",
 )
 cfg.add_argument(
-    "-n", "--count", type=int, metavar='COUNT', help="How many events to remind about"
+    "-n", "--count", type=int, metavar="COUNT", help="How many events to remind about"
 )
 cfg.add_argument(
     "--days_ahead",
     type=int,
-    metavar='#DAYS',
+    metavar="#DAYS",
     help="How many days ahead to remind about",
 )
 cfg.add_argument(
@@ -73,10 +73,10 @@ class Reminder(object):
     def __init__(
         self, cached_state: Optional[Dict[datetime.date, List[str]]] = None
     ) -> None:
-        if not config.config['override_timestamp']:
+        if not config.config["override_timestamp"]:
             self.today = datetime.date.today()
         else:
-            self.today = config.config['override_timestamp'][0].date()
+            self.today = config.config["override_timestamp"][0].date()
             logger.debug(
                 'Overriding "now" with %s because of commandline argument.',
                 self.today,
@@ -85,7 +85,7 @@ class Reminder(object):
             self.label_by_date = cached_state
             return
         self.label_by_date: Dict[datetime.date, List[str]] = defaultdict(list)
-        self.read_file(config.config['reminder_filename'])
+        self.read_file(config.config["reminder_filename"])
 
     def handle_event_by_adjusting_year_to_now(
         self,
@@ -109,21 +109,21 @@ class Reminder(object):
                 month=orig_date.month,
                 day=orig_date.day,
             )
-            logger.debug('Date in %d: %s', year, dt)
+            logger.debug("Date in %d: %s", year, dt)
             self.label_by_date[dt].append(label)
-            logger.debug('%s => %s', dt, label)
+            logger.debug("%s => %s", dt, label)
 
     def handle_event_with_fixed_year(
         self,
         orig_date: datetime.date,
         orig_label: str,
     ) -> None:
-        logger.debug('Fixed date event...')
+        logger.debug("Fixed date event...")
         self.label_by_date[orig_date].append(orig_label)
-        logger.debug('%s => %s', orig_date, orig_label)
+        logger.debug("%s => %s", orig_date, orig_label)
 
     def read_file(self, filename: str) -> None:
-        logger.debug('Reading %s:', filename)
+        logger.debug("Reading %s:", filename)
         date_parser = dateparse.DateParser()
         parsing_mode = Reminder.MODE_EVENT
         with open(filename) as f:
@@ -133,33 +133,33 @@ class Reminder(object):
             line = re.sub(r"#.*$", "", line)
             if re.match(r"^ *$", line) is not None:
                 continue
-            logger.debug('> %s', line)
+            logger.debug("> %s", line)
             try:
                 if "=" in line:
                     label, date = line.split("=")
                 else:
                     print(f"Skipping unparsable line: {line}", file=sys.stderr)
-                    logger.error('Skipping malformed line: %s', line)
+                    logger.error("Skipping malformed line: %s", line)
                     continue
 
                 if label == "type":
                     if "event" in date:
                         parsing_mode = Reminder.MODE_EVENT
-                        logger.debug('--- EVENT MODE ---')
+                        logger.debug("--- EVENT MODE ---")
                     elif "birthday" in date:
                         parsing_mode = Reminder.MODE_BIRTHDAY
-                        logger.debug('--- BIRTHDAY MODE ---')
+                        logger.debug("--- BIRTHDAY MODE ---")
                     elif "anniversary" in date:
                         parsing_mode = Reminder.MODE_ANNIVERSARY
-                        logger.debug('--- ANNIVERSARY MODE ---')
+                        logger.debug("--- ANNIVERSARY MODE ---")
                 else:
                     date_parser.parse(date)
                     orig_date = date_parser.get_date()
                     if orig_date is None:
                         print(f"Skipping unparsable date: {line}", file=sys.stderr)
-                        logger.error('Skipping line with unparsable date')
+                        logger.error("Skipping line with unparsable date")
                         continue
-                    logger.debug('Original date: %s', orig_date)
+                    logger.debug("Original date: %s", orig_date)
 
                     overt_year = date_parser.saw_overt_year
                     if parsing_mode in (
@@ -174,7 +174,7 @@ class Reminder(object):
 
             except Exception as e:
                 print(f"Skipping unparsable line: {line}", file=sys.stderr)
-                logger.error('Skipping malformed line: %s', line)
+                logger.error("Skipping malformed line: %s", line)
                 logger.exception(e)
 
     def remind(
@@ -224,16 +224,16 @@ class Reminder(object):
 
     @classmethod
     def load(cls):
-        if not config.config['override_timestamp']:
+        if not config.config["override_timestamp"]:
             now = datetime.datetime.now()
         else:
-            now = config.config['override_timestamp'][0]
+            now = config.config["override_timestamp"][0]
             logger.debug(
                 'Overriding "now" with %s because of commandline argument.', now
             )
 
         cache_ts = file_utils.get_file_mtime_as_datetime(
-            config.config['reminder_cache_file']
+            config.config["reminder_cache_file"]
         )
         if cache_ts is None:
             return None
@@ -245,14 +245,14 @@ class Reminder(object):
             and now.year == cache_ts.year
         ):
             reminder_ts = file_utils.get_file_mtime_as_datetime(
-                config.config['reminder_filename']
+                config.config["reminder_filename"]
             )
 
             # ...and the .reminder file wasn't updated since the cache write...
             if reminder_ts <= cache_ts:
                 import pickle
 
-                with open(config.config['reminder_cache_file'], 'rb') as rf:
+                with open(config.config["reminder_cache_file"], "rb") as rf:
                     reminder_data = pickle.load(rf)
                     return cls(reminder_data)
         return None
@@ -260,7 +260,7 @@ class Reminder(object):
     def save(self):
         import pickle
 
-        with open(config.config['reminder_cache_file'], 'wb') as wf:
+        with open(config.config["reminder_cache_file"], "wb") as wf:
             pickle.dump(
                 self.label_by_date,
                 wf,
@@ -271,9 +271,9 @@ class Reminder(object):
 @bootstrap.initialize
 def main() -> None:
     reminder = Reminder()
-    count = config.config['count']
-    days_ahead = config.config['days_ahead']
-    reminder.remind(count, days_ahead, config.config['date'])
+    count = config.config["count"]
+    days_ahead = config.config["days_ahead"]
+    reminder.remind(count, days_ahead, config.config["date"])
     return None
 
 
index df9874ee0b309e7a70a5a7c8900629869def3928..52ef9b3f4c32769a193b6a4e3794e26aaa91dc2a 100755 (executable)
@@ -24,7 +24,7 @@ from pyutils.files import file_utils
 from pyutils.parallelize import executors
 from pyutils.parallelize import parallelize as par
 from pyutils.parallelize import smart_future
-from pyutils.typez import histogram
+from pyutils.types import histogram
 
 logger = logging.getLogger(__name__)
 args = config.add_commandline_args(
index a0b4cc24d587690d9a59564e8000a656c2c7c645..ccc49fcc52ac491503203b929d188a48b00ca263 100644 (file)
@@ -49,21 +49,21 @@ class ActionNoYes(argparse.Action):
 
     def __init__(self, option_strings, dest, default=None, required=False, help=None):
         if default is None:
-            msg = 'You must provide a default with Yes/No action'
+            msg = "You must provide a default with Yes/No action"
             logger.critical(msg)
             raise ValueError(msg)
         if len(option_strings) != 1:
-            msg = 'Only single argument is allowed with NoYes action'
+            msg = "Only single argument is allowed with NoYes action"
             logger.critical(msg)
             raise ValueError(msg)
         opt = option_strings[0]
-        if not opt.startswith('--'):
-            msg = 'Yes/No arguments must be prefixed with --'
+        if not opt.startswith("--"):
+            msg = "Yes/No arguments must be prefixed with --"
             logger.critical(msg)
             raise ValueError(msg)
 
         opt = opt[2:]
-        opts = ['--' + opt, '--no_' + opt]
+        opts = ["--" + opt, "--no_" + opt]
         super().__init__(
             opts,
             dest,
@@ -76,7 +76,7 @@ class ActionNoYes(argparse.Action):
 
     @overrides
     def __call__(self, parser, namespace, values, option_strings=None):
-        if option_strings.startswith('--no-') or option_strings.startswith('--no_'):
+        if option_strings.startswith("--no-") or option_strings.startswith("--no_"):
             setattr(namespace, self.dest, False)
         else:
             setattr(namespace, self.dest, True)
@@ -252,7 +252,7 @@ def valid_percentage(num: str) -> float:
     argparse.ArgumentTypeError: 115 is an invalid percentage; expected 0 <= n <= 100.0
 
     """
-    num = num.strip('%')
+    num = num.strip("%")
     n = float(num)
     if 0.0 <= n <= 100.0:
         return n
@@ -340,7 +340,7 @@ def valid_date(txt: str) -> datetime.date:
     date = to_date(txt)
     if date is not None:
         return date
-    msg = f'Cannot parse argument as a date: {txt}'
+    msg = f"Cannot parse argument as a date: {txt}"
     logger.error(msg)
     raise argparse.ArgumentTypeError(msg)
 
@@ -372,7 +372,7 @@ def valid_datetime(txt: str) -> datetime.datetime:
     .. note::
         Because this code uses an English date-expression parsing grammar
         internally, much more complex datetimes can be expressed in free form.
-        See :mod:`pyutils.datetimez.dateparse_utils` for details.  These
+        See :mod:`pyutils.datetimes.dateparse_utils` for details.  These
         are not included in here because they are hard to write valid doctests
         for!
 
@@ -384,7 +384,7 @@ def valid_datetime(txt: str) -> datetime.datetime:
     dt = to_datetime(txt)
     if dt is not None:
         return dt
-    msg = f'Cannot parse argument as datetime: {txt}'
+    msg = f"Cannot parse argument as datetime: {txt}"
     logger.error(msg)
     raise argparse.ArgumentTypeError(msg)
 
@@ -438,7 +438,7 @@ def valid_duration(txt: str) -> datetime.timedelta:
     ...
     argparse.ArgumentTypeError: a little while is not a valid duration.
     """
-    from pyutils.datetimez.datetime_utils import parse_duration
+    from pyutils.datetimes.datetime_utils import parse_duration
 
     try:
         secs = parse_duration(txt, raise_on_error=True)
@@ -448,8 +448,8 @@ def valid_duration(txt: str) -> datetime.timedelta:
         raise argparse.ArgumentTypeError(e) from e
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
 
-    doctest.ELLIPSIS_MARKER = '-ANYTHING-'
+    doctest.ELLIPSIS_MARKER = "-ANYTHING-"
     doctest.testmod()
similarity index 99%
rename from src/pyutils/datetimez/dateparse_utils.py
rename to src/pyutils/datetimes/dateparse_utils.py
index 48250f5f6e7ae32e7418d4fc7011e776a6e9e146..fcdaffe131b4c958d9ae2b6573c0d26dd1e4c605 100755 (executable)
@@ -89,7 +89,7 @@ and
 :meth:`pyutils.string_utils.to_date`.  This means any of these are
 also able to accept and recognize this larger set of date expressions.
 
-See the `unittest <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=tests/datetimez/dateparse_utils_test.py;h=93c7b96e4c19af217fbafcf1ed5dbde13ec599c5;hb=HEAD>`_ for more examples and the `grammar <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=src/pyutils/datetimez/dateparse_utils.g4;hb=HEAD>`_ for more details.
+See the `unittest <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=tests/datetimes/dateparse_utils_test.py;h=93c7b96e4c19af217fbafcf1ed5dbde13ec599c5;hb=HEAD>`_ for more examples and the `grammar <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=src/pyutils/datetimes/dateparse_utils.g4;hb=HEAD>`_ for more details.
 """
 
 import datetime
@@ -104,14 +104,14 @@ import holidays  # type: ignore
 import pytz
 
 from pyutils import bootstrap, decorator_utils
-from pyutils.datetimez.dateparse_utilsLexer import dateparse_utilsLexer  # type: ignore
-from pyutils.datetimez.dateparse_utilsListener import (
+from pyutils.datetimes.dateparse_utilsLexer import dateparse_utilsLexer  # type: ignore
+from pyutils.datetimes.dateparse_utilsListener import (
     dateparse_utilsListener,
 )  # type: ignore
-from pyutils.datetimez.dateparse_utilsParser import (
+from pyutils.datetimes.dateparse_utilsParser import (
     dateparse_utilsParser,
 )  # type: ignore
-from pyutils.datetimez.datetime_utils import (
+from pyutils.datetimes.datetime_utils import (
     TimeUnit,
     date_to_datetime,
     datetime_to_date,
similarity index 97%
rename from src/pyutils/datetimez/datetime_utils.py
rename to src/pyutils/datetimes/datetime_utils.py
index 2f428cce1fc58e40b3ec169c706919087b1686f7..55ceb9c60258b83c569d333430b7f753a7663faa 100644 (file)
@@ -13,7 +13,7 @@ from typing import Any, NewType, Optional, Tuple
 import holidays  # type: ignore
 import pytz
 
-from pyutils.datetimez import constants
+from pyutils.datetimes import constants
 
 logger = logging.getLogger(__name__)
 
@@ -153,8 +153,8 @@ def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetim
         if dt.tzinfo == tz:
             return dt
         raise Exception(
-            f'{dt} is already timezone aware; use replace_timezone or translate_timezone '
-            + 'depending on the semantics you want.  See the pydocs / code.'
+            f"{dt} is already timezone aware; use replace_timezone or translate_timezone "
+            + "depending on the semantics you want.  See the pydocs / code."
         )
     return dt.replace(tzinfo=tz)
 
@@ -206,7 +206,7 @@ def replace_timezone(
     """
     if is_timezone_aware(dt):
         logger.warning(
-            '%s already has a timezone; klobbering it anyway.\n  Be aware that this operation changed the instant to which the object refers.',
+            "%s already has a timezone; klobbering it anyway.\n  Be aware that this operation changed the instant to which the object refers.",
             dt,
         )
         return datetime.datetime(
@@ -829,7 +829,7 @@ def string_to_datetime(
 ) -> Tuple[datetime.datetime, str]:
     """A nice way to convert a string into a datetime.  Returns both the
     datetime and the format string used to parse it.  Also consider
-    :mod:`pyutils.datetimez.dateparse_utils` for a full parser alternative.
+    :mod:`pyutils.datetimes.dateparse_utils` for a full parser alternative.
 
     Args:
         txt: the string to be converted into a datetime
@@ -966,9 +966,9 @@ def minute_number(hour: int, minute: int) -> MinuteOfDay:
     1439
     """
     if hour < 0 or hour > 23:
-        raise ValueError(f'Bad hour: {hour}.  Expected 0 <= hour <= 23')
+        raise ValueError(f"Bad hour: {hour}.  Expected 0 <= hour <= 23")
     if minute < 0 or minute > 59:
-        raise ValueError(f'Bad minute: {minute}.  Expected 0 <= minute <= 59')
+        raise ValueError(f"Bad minute: {minute}.  Expected 0 <= minute <= 59")
     return MinuteOfDay(hour * 60 + minute)
 
 
@@ -1079,23 +1079,23 @@ def parse_duration(duration: str, raise_on_error=False) -> int:
         return int(duration)
 
     m = re.match(
-        r'(\d+ *d[ays]*)* *(\d+ *h[ours]*)* *(\d+ *m[inutes]*)* *(\d+ *[seconds]*)',
+        r"(\d+ *d[ays]*)* *(\d+ *h[ours]*)* *(\d+ *m[inutes]*)* *(\d+ *[seconds]*)",
         duration,
     )
     if not m and raise_on_error:
-        raise ValueError(f'{duration} is not a valid duration.')
+        raise ValueError(f"{duration} is not a valid duration.")
 
     seconds = 0
-    m = re.search(r'(\d+) *d[ays]*', duration)
+    m = re.search(r"(\d+) *d[ays]*", duration)
     if m is not None:
         seconds += int(m.group(1)) * 60 * 60 * 24
-    m = re.search(r'(\d+) *h[ours]*', duration)
+    m = re.search(r"(\d+) *h[ours]*", duration)
     if m is not None:
         seconds += int(m.group(1)) * 60 * 60
-    m = re.search(r'(\d+) *m[inutes]*', duration)
+    m = re.search(r"(\d+) *m[inutes]*", duration)
     if m is not None:
         seconds += int(m.group(1)) * 60
-    m = re.search(r'(\d+) *s[econds]*', duration)
+    m = re.search(r"(\d+) *s[econds]*", duration)
     if m is not None:
         seconds += int(m.group(1))
     return seconds
@@ -1151,14 +1151,14 @@ def describe_duration(seconds: int, *, include_seconds=False) -> str:
         descr = descr + f"{int(minutes[0])} minutes"
 
     if include_seconds:
-        descr = descr + ', '
+        descr = descr + ", "
         if len(descr) > 0:
-            descr = descr + 'and '
+            descr = descr + "and "
         s = minutes[1]
         if s == 1:
-            descr = descr + '1 second'
+            descr = descr + "1 second"
         else:
-            descr = descr + f'{s} seconds'
+            descr = descr + f"{s} seconds"
     return descr
 
 
@@ -1219,15 +1219,15 @@ def describe_duration_briefly(seconds: int, *, include_seconds=False) -> str:
     hours = divmod(days[1], constants.SECONDS_PER_HOUR)
     minutes = divmod(hours[1], constants.SECONDS_PER_MINUTE)
 
-    descr = ''
+    descr = ""
     if days[0] > 0:
-        descr = f'{int(days[0])}d '
+        descr = f"{int(days[0])}d "
     if hours[0] > 0:
-        descr = descr + f'{int(hours[0])}h '
+        descr = descr + f"{int(hours[0])}h "
     if minutes[0] > 0 or (len(descr) == 0 and not include_seconds):
-        descr = descr + f'{int(minutes[0])}m '
+        descr = descr + f"{int(minutes[0])}m "
     if minutes[1] > 0 and include_seconds:
-        descr = descr + f'{int(minutes[1])}s'
+        descr = descr + f"{int(minutes[1])}s"
     return descr.strip()
 
 
@@ -1400,7 +1400,7 @@ def easter(year, method=EASTER_WESTERN):
     return datetime.date(int(y), int(m), int(d))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
 
     doctest.testmod()
index bddc63101b648de8e4d45431f4757276dee71d63..43959bfba6c5d1351460ab225f77c27795b70d38 100644 (file)
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
 def remove_newlines(x: str) -> str:
     """Trivial function to be used as a line_transformer in
     :meth:`slurp_file` for no newlines in file contents"""
-    return x.replace('\n', '')
+    return x.replace("\n", "")
 
 
 def strip_whitespace(x: str) -> str:
@@ -43,7 +43,7 @@ def strip_whitespace(x: str) -> str:
 def remove_hash_comments(x: str) -> str:
     """Trivial function to be used as a line_transformer in
     :meth:`slurp_file` for no # comments in file contents"""
-    return re.sub(r'#.*$', '', x)
+    return re.sub(r"#.*$", "", x)
 
 
 def slurp_file(
@@ -70,12 +70,12 @@ def slurp_file(
         for x in line_transformers:
             xforms.append(x)
     if not file_is_readable(filename):
-        raise Exception(f'{filename} can\'t be read.')
+        raise Exception(f"{filename} can't be read.")
     with open(filename) as rf:
         for line in rf:
             for transformation in xforms:
                 line = transformation(line)
-            if skip_blank_lines and line == '':
+            if skip_blank_lines and line == "":
                 continue
             ret.append(line)
     return ret
@@ -115,7 +115,7 @@ def fix_multiple_slashes(path: str) -> str:
     >>> fix_multiple_slashes(p) == p
     True
     """
-    return re.sub(r'/+', '/', path)
+    return re.sub(r"/+", "/", path)
 
 
 def delete(path: str) -> None:
@@ -174,7 +174,7 @@ def without_all_extensions(path: str) -> str:
     '/home/scott/foobar'
 
     """
-    while '.' in path:
+    while "." in path:
         path = without_extension(path)
     return path
 
@@ -948,7 +948,7 @@ def get_file_mtime_timedelta(filename: str) -> Optional[datetime.timedelta]:
 
 def describe_file_timestamp(filename: str, extractor, *, brief=False) -> Optional[str]:
     """~Internal helper"""
-    from pyutils.datetimez.datetime_utils import (
+    from pyutils.datetimes.datetime_utils import (
         describe_duration,
         describe_duration_briefly,
     )
@@ -1203,7 +1203,7 @@ class FileWriter(contextlib.AbstractContextManager):
         """
         self.filename = filename
         uuid = uuid4()
-        self.tempfile = f'{filename}-{uuid}.tmp'
+        self.tempfile = f"{filename}-{uuid}.tmp"
         self.handle: Optional[TextIO] = None
 
     def __enter__(self) -> TextIO:
@@ -1214,14 +1214,14 @@ class FileWriter(contextlib.AbstractContextManager):
     def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]:
         if self.handle is not None:
             self.handle.close()
-            cmd = f'/bin/mv -f {self.tempfile} {self.filename}'
+            cmd = f"/bin/mv -f {self.tempfile} {self.filename}"
             ret = os.system(cmd)
             if (ret >> 8) != 0:
-                raise Exception(f'{cmd} failed, exit value {ret>>8}!')
+                raise Exception(f"{cmd} failed, exit value {ret>>8}!")
         return False
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
 
     doctest.testmod()
index c7b4841e5373779d6c0bc783f7ce6f32270c9273..68f957cbc080deed793cf2e24c14baddeecd6e48 100644 (file)
@@ -25,15 +25,15 @@ from dataclasses import dataclass
 from typing import Literal, Optional
 
 from pyutils import argparse_utils, config, decorator_utils
-from pyutils.datetimez import datetime_utils
+from pyutils.datetimes import datetime_utils
 
-cfg = config.add_commandline_args(f'Lockfile ({__file__})', 'Args related to lockfiles')
+cfg = config.add_commandline_args(f"Lockfile ({__file__})", "Args related to lockfiles")
 cfg.add_argument(
-    '--lockfile_held_duration_warning_threshold',
+    "--lockfile_held_duration_warning_threshold",
     type=argparse_utils.valid_duration,
     default=datetime.timedelta(60.0),
-    metavar='DURATION',
-    help='If a lock is held for longer than this threshold we log a warning',
+    metavar="DURATION",
+    help="If a lock is held for longer than this threshold we log a warning",
 )
 logger = logging.getLogger(__name__)
 
@@ -123,12 +123,12 @@ class LockFile(contextlib.AbstractContextManager):
                 logger.debug(contents)
                 f.write(contents)
             self.locktime = datetime.datetime.now().timestamp()
-            logger.debug('Success; I own %s.', self.lockfile)
+            logger.debug("Success; I own %s.", self.lockfile)
             self.is_locked = True
             return True
         except OSError:
             pass
-        logger.warning('Couldn\'t acquire %s.', self.lockfile)
+        logger.warning("Couldn't acquire %s.", self.lockfile)
         return False
 
     def acquire_with_retries(
@@ -187,12 +187,12 @@ class LockFile(contextlib.AbstractContextManager):
             if (
                 duration
                 >= config.config[
-                    'lockfile_held_duration_warning_threshold'
+                    "lockfile_held_duration_warning_threshold"
                 ].total_seconds()
             ):
                 # Note: describe duration briefly only does 1s granularity...
                 str_duration = datetime_utils.describe_duration_briefly(int(duration))
-                msg = f'Held {self.lockfile} for {str_duration}'
+                msg = f"Held {self.lockfile} for {str_duration}"
                 logger.warning(msg)
                 warnings.warn(msg, stacklevel=2)
         self.release()
@@ -210,7 +210,7 @@ class LockFile(contextlib.AbstractContextManager):
         if self.override_command:
             cmd = self.override_command
         else:
-            cmd = ' '.join(sys.argv)
+            cmd = " ".join(sys.argv)
         contents = LockFileContents(
             pid=os.getpid(),
             commandline=cmd,
@@ -220,7 +220,7 @@ class LockFile(contextlib.AbstractContextManager):
 
     def _detect_stale_lockfile(self) -> None:
         try:
-            with open(self.lockfile, 'r') as rf:
+            with open(self.lockfile, "r") as rf:
                 lines = rf.readlines()
                 if len(lines) == 1:
                     line = lines[0]
@@ -233,7 +233,7 @@ class LockFile(contextlib.AbstractContextManager):
                         os.kill(contents.pid, 0)
                     except OSError:
                         logger.warning(
-                            'Lockfile %s\'s pid (%d) is stale; force acquiring...',
+                            "Lockfile %s's pid (%d) is stale; force acquiring...",
                             self.lockfile,
                             contents.pid,
                         )
@@ -244,7 +244,7 @@ class LockFile(contextlib.AbstractContextManager):
                         now = datetime.datetime.now().timestamp()
                         if now > contents.expiration_timestamp:
                             logger.warning(
-                                'Lockfile %s\'s expiration time has passed; force acquiring',
+                                "Lockfile %s's expiration time has passed; force acquiring",
                                 self.lockfile,
                             )
                             self.release()
index a2877fa9eb19d958cf7263828a1fc0598e542d2e..99f3459a4c2879fb098fe8a02a77dc7f188a5977 100644 (file)
@@ -55,7 +55,7 @@ from typing import Any, Callable, Dict, List, Optional, Set
 import cloudpickle  # type: ignore
 from overrides import overrides
 
-import pyutils.typez.histogram as hist
+import pyutils.types.histogram as hist
 from pyutils import (
     argparse_utils,
     config,
@@ -68,7 +68,7 @@ from pyutils.ansi import bg, fg, reset, underline
 from pyutils.decorator_utils import singleton
 from pyutils.exec_utils import cmd_exitcode, cmd_in_background, run_silently
 from pyutils.parallelize.thread_utils import background_thread
-from pyutils.typez import type_utils
+from pyutils.types import type_utils
 
 logger = logging.getLogger(__name__)
 
index d397ad8e0b306c9494168cf859b2855ab0604045..dff4a798a27381912947b0446a0bb4292a7056bf 100644 (file)
@@ -1657,16 +1657,16 @@ def to_date(in_str: str) -> Optional[datetime.date]:
     Returns:
         The datetime.date the string contained or None to indicate
         an error.  This parser is relatively clever; see
-        :class:`datetimez.dateparse_utils` docs for details.
+        :class:`datetimes.dateparse_utils` docs for details.
 
-    See also: :mod:`pyutils.datetimez.dateparse_utils`, :meth:`extract_date`,
+    See also: :mod:`pyutils.datetimes.dateparse_utils`, :meth:`extract_date`,
     :meth:`is_valid_date`, :meth:`to_datetime`, :meth:`valid_datetime`.
 
     >>> to_date('9/11/2001')
     datetime.date(2001, 9, 11)
     >>> to_date('xyzzy')
     """
-    import pyutils.datetimez.dateparse_utils as du
+    import pyutils.datetimes.dateparse_utils as du
 
     try:
         d = du.DateParser()  # type: ignore
@@ -1687,7 +1687,7 @@ def extract_date(in_str: Any) -> Optional[datetime.datetime]:
     Returns:
         a datetime if date was found, otherwise None
 
-    See also: :mod:`pyutils.datetimez.dateparse_utils`, :meth:`to_date`,
+    See also: :mod:`pyutils.datetimes.dateparse_utils`, :meth:`to_date`,
     :meth:`is_valid_date`, :meth:`to_datetime`, :meth:`valid_datetime`.
 
     >>> extract_date("filename.txt    dec 13, 2022")
@@ -1698,7 +1698,7 @@ def extract_date(in_str: Any) -> Optional[datetime.datetime]:
     """
     import itertools
 
-    import pyutils.datetimez.dateparse_utils as du
+    import pyutils.datetimes.dateparse_utils as du
 
     d = du.DateParser()  # type: ignore
     chunks = in_str.split()
@@ -1726,9 +1726,9 @@ def is_valid_date(in_str: str) -> bool:
     Returns:
         True if the string represents a valid date that we can recognize
         and False otherwise.  This parser is relatively clever; see
-        :class:`datetimez.dateparse_utils` docs for details.
+        :class:`datetimes.dateparse_utils` docs for details.
 
-    See also: :mod:`pyutils.datetimez.dateparse_utils`, :meth:`to_date`,
+    See also: :mod:`pyutils.datetimes.dateparse_utils`, :meth:`to_date`,
     :meth:`extract_date`, :meth:`to_datetime`, :meth:`valid_datetime`.
 
     >>> is_valid_date('1/2/2022')
@@ -1740,7 +1740,7 @@ def is_valid_date(in_str: str) -> bool:
     >>> is_valid_date('xyzzy')
     False
     """
-    import pyutils.datetimez.dateparse_utils as dp
+    import pyutils.datetimes.dateparse_utils as dp
 
     try:
         d = dp.DateParser()  # type: ignore
@@ -1760,15 +1760,15 @@ def to_datetime(in_str: str) -> Optional[datetime.datetime]:
     Returns:
         A python datetime parsed from in_str or None to indicate
         an error.  This parser is relatively clever; see
-        :class:`datetimez.dateparse_utils` docs for details.
+        :class:`datetimes.dateparse_utils` docs for details.
 
-    See also: :mod:`pyutils.datetimez.dateparse_utils`, :meth:`to_date`,
+    See also: :mod:`pyutils.datetimes.dateparse_utils`, :meth:`to_date`,
     :meth:`extract_date`, :meth:`valid_datetime`.
 
     >>> to_datetime('7/20/1969 02:56 GMT')
     datetime.datetime(1969, 7, 20, 2, 56, tzinfo=<StaticTzInfo 'GMT'>)
     """
-    import pyutils.datetimez.dateparse_utils as dp
+    import pyutils.datetimes.dateparse_utils as dp
 
     try:
         d = dp.DateParser()  # type: ignore
@@ -1789,7 +1789,7 @@ def valid_datetime(in_str: str) -> bool:
     Returns:
         True if in_str contains a valid datetime and False otherwise.
         This parser is relatively clever; see
-        :class:`datetimez.dateparse_utils` docs for details.
+        :class:`datetimes.dateparse_utils` docs for details.
 
     >>> valid_datetime('next wednesday at noon')
     True
similarity index 88%
rename from src/pyutils/typez/centcount.py
rename to src/pyutils/types/centcount.py
index c0c841823d8ec7b1e9b3fa92bb599c625308f9d6..e5894717bedf919e442e7a74c0cdc558dd2883d7 100644 (file)
@@ -41,7 +41,7 @@ numbers).
     of this and decide whether it's suitable for your
     application.
 
-See also the :class:`pyutils.typez.Money` class which uses Python
+See also the :class:`pyutils.types.Money` class which uses Python
 Decimals (see: https://docs.python.org/3/library/decimal.html) to
 represent monetary amounts.
 """
@@ -58,8 +58,8 @@ class CentCount(object):
 
     def __init__(
         self,
-        centcount: Union[int, float, str, 'CentCount'] = 0,
-        currency: str = 'USD',
+        centcount: Union[int, float, str, "CentCount"] = 0,
+        currency: str = "USD",
         *,
         strict_mode=False,
     ):
@@ -95,11 +95,11 @@ class CentCount(object):
     def __repr__(self):
         w = self.centcount // 100
         p = self.centcount % 100
-        s = f'{w}.{p:02d}'
+        s = f"{w}.{p:02d}"
         if self.currency is not None:
-            return f'{s} {self.currency}'
+            return f"{s} {self.currency}"
         else:
-            return f'${s}'
+            return f"${s}"
 
     def __pos__(self):
         return CentCount(centcount=self.centcount, currency=self.currency)
@@ -115,10 +115,10 @@ class CentCount(object):
                     currency=self.currency,
                 )
             else:
-                raise TypeError('Incompatible currencies in add expression')
+                raise TypeError("Incompatible currencies in add expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return self.__add__(CentCount(other, self.currency))
 
@@ -130,10 +130,10 @@ class CentCount(object):
                     currency=self.currency,
                 )
             else:
-                raise TypeError('Incompatible currencies in add expression')
+                raise TypeError("Incompatible currencies in add expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return self.__sub__(CentCount(other, self.currency))
 
@@ -163,7 +163,7 @@ class CentCount(object):
             application.
         """
         if isinstance(other, CentCount):
-            raise TypeError('can not multiply monetary quantities')
+            raise TypeError("can not multiply monetary quantities")
         else:
             return CentCount(
                 centcount=int(self.centcount * float(other)),
@@ -196,7 +196,7 @@ class CentCount(object):
             application.
         """
         if isinstance(other, CentCount):
-            raise TypeError('can not divide monetary quantities')
+            raise TypeError("can not divide monetary quantities")
         else:
             return CentCount(
                 centcount=int(float(self.centcount) / float(other)),
@@ -219,10 +219,10 @@ class CentCount(object):
                     currency=self.currency,
                 )
             else:
-                raise TypeError('Incompatible currencies in sub expression')
+                raise TypeError("Incompatible currencies in sub expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return CentCount(
                     centcount=int(other) - self.centcount,
@@ -255,10 +255,10 @@ class CentCount(object):
             if self.currency == other.currency:
                 return self.centcount < other.centcount
             else:
-                raise TypeError('can not directly compare different currencies')
+                raise TypeError("can not directly compare different currencies")
         else:
             if self.strict_mode:
-                raise TypeError('In strict mode, only two CentCounts can be compated')
+                raise TypeError("In strict mode, only two CentCounts can be compated")
             else:
                 return self.centcount < int(other)
 
@@ -267,10 +267,10 @@ class CentCount(object):
             if self.currency == other.currency:
                 return self.centcount > other.centcount
             else:
-                raise TypeError('can not directly compare different currencies')
+                raise TypeError("can not directly compare different currencies")
         else:
             if self.strict_mode:
-                raise TypeError('In strict mode, only two CentCounts can be compated')
+                raise TypeError("In strict mode, only two CentCounts can be compated")
             else:
                 return self.centcount > int(other)
 
@@ -291,7 +291,7 @@ class CentCount(object):
         centcount = None
         currency = None
         s = s.strip()
-        chunks = s.split(' ')
+        chunks = s.split(" ")
         try:
             for chunk in chunks:
                 if CentCount.CENTCOUNT_RE.match(chunk) is not None:
@@ -303,11 +303,11 @@ class CentCount(object):
         if centcount is not None and currency is not None:
             return (centcount, currency)
         elif centcount is not None:
-            return (centcount, 'USD')
+            return (centcount, "USD")
         return None
 
     @classmethod
-    def parse(cls, s: str) -> 'CentCount':
+    def parse(cls, s: str) -> "CentCount":
         """Parses a string format monetary amount and returns a CentCount
         if possible.
 
@@ -320,7 +320,7 @@ class CentCount(object):
         raise Exception(f'Unable to parse money string "{s}"')
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
 
     doctest.testmod()
similarity index 84%
rename from src/pyutils/typez/money.py
rename to src/pyutils/types/money.py
index 099c4f0f61e8895e8936d3b75c7e9e32de7b3d77..589af47100753e9c1843105ebdaf6616dcb9dbd4 100644 (file)
@@ -11,7 +11,7 @@ another, and has a strict mode which disallows comparison or
 aggregation with non-:class:`Money` operands (i.e. no comparison or
 aggregation with literal numbers).
 
-See also :class:`pyutils.typez.centcount.CentCount` which represents
+See also :class:`pyutils.types.centcount.CentCount` which represents
 monetary amounts as an integral number of cents.
 """
 
@@ -27,8 +27,8 @@ class Money(object):
 
     def __init__(
         self,
-        amount: Union[Decimal, str, float, int, 'Money'] = Decimal("0"),
-        currency: str = 'USD',
+        amount: Union[Decimal, str, float, int, "Money"] = Decimal("0"),
+        currency: str = "USD",
         *,
         strict_mode=False,
     ):
@@ -66,10 +66,10 @@ class Money(object):
         digits = list(map(str, digits))
         build, next = result.append, digits.pop
         for i in range(2):
-            build(next() if digits else '0')
-        build('.')
+            build(next() if digits else "0")
+        build(".")
         if not digits:
-            build('0')
+            build("0")
         i = 0
         while digits:
             build(next())
@@ -77,11 +77,11 @@ class Money(object):
             if i == 3 and digits:
                 i = 0
         if sign:
-            build('-')
+            build("-")
         if self.currency:
-            return ''.join(reversed(result)) + ' ' + self.currency
+            return "".join(reversed(result)) + " " + self.currency
         else:
-            return '$' + ''.join(reversed(result))
+            return "$" + "".join(reversed(result))
 
     def __pos__(self):
         return Money(amount=self.amount, currency=self.currency)
@@ -94,10 +94,10 @@ class Money(object):
             if self.currency == other.currency:
                 return Money(amount=self.amount + other.amount, currency=self.currency)
             else:
-                raise TypeError('Incompatible currencies in add expression')
+                raise TypeError("Incompatible currencies in add expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return Money(
                     amount=self.amount + Decimal(float(other)),
@@ -109,10 +109,10 @@ class Money(object):
             if self.currency == other.currency:
                 return Money(amount=self.amount - other.amount, currency=self.currency)
             else:
-                raise TypeError('Incompatible currencies in add expression')
+                raise TypeError("Incompatible currencies in add expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return Money(
                     amount=self.amount - Decimal(float(other)),
@@ -121,7 +121,7 @@ class Money(object):
 
     def __mul__(self, other):
         if isinstance(other, Money):
-            raise TypeError('can not multiply monetary quantities')
+            raise TypeError("can not multiply monetary quantities")
         else:
             return Money(
                 amount=self.amount * Decimal(float(other)),
@@ -130,7 +130,7 @@ class Money(object):
 
     def __truediv__(self, other):
         if isinstance(other, Money):
-            raise TypeError('can not divide monetary quantities')
+            raise TypeError("can not divide monetary quantities")
         else:
             return Money(
                 amount=self.amount / Decimal(float(other)),
@@ -171,7 +171,7 @@ class Money(object):
 
         See also :meth:`round_fractional_cents`
         """
-        self.amount = self.amount.quantize(Decimal('.01'), rounding=ROUND_FLOOR)
+        self.amount = self.amount.quantize(Decimal(".01"), rounding=ROUND_FLOOR)
         return self.amount
 
     def round_fractional_cents(self):
@@ -205,7 +205,7 @@ class Money(object):
 
         See also :meth:`truncate_fractional_cents`
         """
-        self.amount = self.amount.quantize(Decimal('.01'), rounding=ROUND_HALF_DOWN)
+        self.amount = self.amount.quantize(Decimal(".01"), rounding=ROUND_HALF_DOWN)
         return self.amount
 
     __radd__ = __add__
@@ -215,10 +215,10 @@ class Money(object):
             if self.currency == other.currency:
                 return Money(amount=other.amount - self.amount, currency=self.currency)
             else:
-                raise TypeError('Incompatible currencies in sub expression')
+                raise TypeError("Incompatible currencies in sub expression")
         else:
             if self.strict_mode:
-                raise TypeError('In strict_mode only two moneys can be added')
+                raise TypeError("In strict_mode only two moneys can be added")
             else:
                 return Money(
                     amount=Decimal(float(other)) - self.amount,
@@ -251,10 +251,10 @@ class Money(object):
             if self.currency == other.currency:
                 return self.amount < other.amount
             else:
-                raise TypeError('can not directly compare different currencies')
+                raise TypeError("can not directly compare different currencies")
         else:
             if self.strict_mode:
-                raise TypeError('In strict mode, only two Moneys can be compated')
+                raise TypeError("In strict mode, only two Moneys can be compated")
             else:
                 return self.amount < Decimal(float(other))
 
@@ -263,10 +263,10 @@ class Money(object):
             if self.currency == other.currency:
                 return self.amount > other.amount
             else:
-                raise TypeError('can not directly compare different currencies')
+                raise TypeError("can not directly compare different currencies")
         else:
             if self.strict_mode:
-                raise TypeError('In strict mode, only two Moneys can be compated')
+                raise TypeError("In strict mode, only two Moneys can be compated")
             else:
                 return self.amount > Decimal(float(other))
 
@@ -287,7 +287,7 @@ class Money(object):
         amount = None
         currency = None
         s = s.strip()
-        chunks = s.split(' ')
+        chunks = s.split(" ")
         try:
             for chunk in chunks:
                 if Money.AMOUNT_RE.match(chunk) is not None:
@@ -299,11 +299,11 @@ class Money(object):
         if amount is not None and currency is not None:
             return (amount, currency)
         elif amount is not None:
-            return (amount, 'USD')
+            return (amount, "USD")
         return None
 
     @classmethod
-    def parse(cls, s: str) -> 'Money':
+    def parse(cls, s: str) -> "Money":
         """Parses a string an attempts to create a :class:`Money`
         instance.
 
@@ -316,7 +316,7 @@ class Money(object):
         raise Exception(f'Unable to parse money string "{s}"')
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
 
     doctest.testmod()
diff --git a/tests/datetimes/dateparse_utils_test.py b/tests/datetimes/dateparse_utils_test.py
new file mode 100755 (executable)
index 0000000..b6146e9
--- /dev/null
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+
+# © Copyright 2021-2022, Scott Gasch
+
+"""dateparse_utils unittest."""
+
+import datetime
+import random
+import re
+import unittest
+
+import pytz
+
+import pyutils.datetimes.dateparse_utils as du
+import pyutils.unittest_utils as uu
+
+parsable_expressions = [
+    ("today", datetime.datetime(2021, 7, 2)),
+    ("tomorrow", datetime.datetime(2021, 7, 3)),
+    ("yesterday", datetime.datetime(2021, 7, 1)),
+    ("21:30", datetime.datetime(2021, 7, 2, 21, 30, 0, 0)),
+    (
+        "21:30 EST",
+        datetime.datetime(2021, 7, 2, 21, 30, 0, 0, tzinfo=pytz.timezone("EST")),
+    ),
+    (
+        "21:30 -0500",
+        datetime.datetime(2021, 7, 2, 21, 30, 0, 0, tzinfo=pytz.timezone("EST")),
+    ),
+    ("12:01am", datetime.datetime(2021, 7, 2, 0, 1, 0, 0)),
+    ("12:02p", datetime.datetime(2021, 7, 2, 12, 2, 0, 0)),
+    ("0:03", datetime.datetime(2021, 7, 2, 0, 3, 0, 0)),
+    ("last wednesday", datetime.datetime(2021, 6, 30)),
+    ("this wed", datetime.datetime(2021, 7, 7)),
+    ("next wed", datetime.datetime(2021, 7, 14)),
+    ("this coming tues", datetime.datetime(2021, 7, 6)),
+    ("this past monday", datetime.datetime(2021, 6, 28)),
+    ("4 days ago", datetime.datetime(2021, 6, 28)),
+    ("4 mondays ago", datetime.datetime(2021, 6, 7)),
+    ("4 months ago", datetime.datetime(2021, 3, 2)),
+    ("3 days back", datetime.datetime(2021, 6, 29)),
+    ("13 weeks from now", datetime.datetime(2021, 10, 1)),
+    ("1 year from now", datetime.datetime(2022, 7, 2)),
+    ("4 weeks from now", datetime.datetime(2021, 7, 30)),
+    ("3 saturdays ago", datetime.datetime(2021, 6, 12)),
+    ("4 months from today", datetime.datetime(2021, 11, 2)),
+    ("4 years from yesterday", datetime.datetime(2025, 7, 1)),
+    ("4 weeks from tomorrow", datetime.datetime(2021, 7, 31)),
+    ("april 15, 2005", datetime.datetime(2005, 4, 15)),
+    ("april 14", datetime.datetime(2021, 4, 14)),
+    ("9:30am on last wednesday", datetime.datetime(2021, 6, 30, 9, 30)),
+    ("2005/apr/15", datetime.datetime(2005, 4, 15)),
+    ("2005 apr 15", datetime.datetime(2005, 4, 15)),
+    ("the 1st wednesday in may", datetime.datetime(2021, 5, 5)),
+    ("last sun of june", datetime.datetime(2021, 6, 27)),
+    ("this Easter", datetime.datetime(2021, 4, 4)),
+    ("last christmas", datetime.datetime(2020, 12, 25)),
+    ("last Xmas", datetime.datetime(2020, 12, 25)),
+    ("xmas, 1999", datetime.datetime(1999, 12, 25)),
+    ("next mlk day", datetime.datetime(2022, 1, 17)),
+    ("Halloween, 2020", datetime.datetime(2020, 10, 31)),
+    ("5 work days after independence day", datetime.datetime(2021, 7, 12)),
+    ("50 working days from last wed", datetime.datetime(2021, 9, 10)),
+    ("25 working days before columbus day", datetime.datetime(2021, 9, 3)),
+    ("today +1 week", datetime.datetime(2021, 7, 9)),
+    ("sunday -3 weeks", datetime.datetime(2021, 6, 13)),
+    ("4 weeks before xmas, 1999", datetime.datetime(1999, 11, 27)),
+    ("3 days before new years eve, 2000", datetime.datetime(2000, 12, 28)),
+    ("july 4th", datetime.datetime(2021, 7, 4)),
+    ("the ides of march", datetime.datetime(2021, 3, 15)),
+    ("the nones of april", datetime.datetime(2021, 4, 5)),
+    ("the kalends of may", datetime.datetime(2021, 5, 1)),
+    ("9/11/2001", datetime.datetime(2001, 9, 11)),
+    ("4 sundays before veterans' day", datetime.datetime(2021, 10, 17)),
+    ("xmas eve", datetime.datetime(2021, 12, 24)),
+    ("this friday at 5pm", datetime.datetime(2021, 7, 9, 17, 0, 0)),
+    ("presidents day", datetime.datetime(2021, 2, 15)),
+    ("memorial day, 1921", datetime.datetime(1921, 5, 30)),
+    ("today -4 wednesdays", datetime.datetime(2021, 6, 9)),
+    ("thanksgiving", datetime.datetime(2021, 11, 25)),
+    ("2 sun in jun", datetime.datetime(2021, 6, 13)),
+    ("easter -40 days", datetime.datetime(2021, 2, 23)),
+    ("easter +39 days", datetime.datetime(2021, 5, 13)),
+    ("2nd Sunday in May, 2022", datetime.datetime(2022, 5, 8)),
+    ("1st tuesday in nov, 2024", datetime.datetime(2024, 11, 5)),
+    (
+        "2 days before last xmas at 3:14:15.92a",
+        datetime.datetime(2020, 12, 23, 3, 14, 15, 92),
+    ),
+    (
+        "3 weeks after xmas, 1995 at midday",
+        datetime.datetime(1996, 1, 15, 12, 0, 0),
+    ),
+    (
+        "4 months before easter, 1992 at midnight",
+        datetime.datetime(1991, 12, 19),
+    ),
+    (
+        "5 months before halloween, 1995 at noon",
+        datetime.datetime(1995, 5, 31, 12),
+    ),
+    ("4 days before last wednesday", datetime.datetime(2021, 6, 26)),
+    ("44 months after today", datetime.datetime(2025, 3, 2)),
+    ("44 years before today", datetime.datetime(1977, 7, 2)),
+    ("44 weeks ago", datetime.datetime(2020, 8, 28)),
+    ("15 minutes to 3am", datetime.datetime(2021, 7, 2, 2, 45)),
+    ("quarter past 4pm", datetime.datetime(2021, 7, 2, 16, 15)),
+    ("half past 9", datetime.datetime(2021, 7, 2, 9, 30)),
+    ("4 seconds to midnight", datetime.datetime(2021, 7, 1, 23, 59, 56)),
+    (
+        "4 seconds to midnight, tomorrow",
+        datetime.datetime(2021, 7, 2, 23, 59, 56),
+    ),
+    ("2021/apr/15T21:30:44.55", datetime.datetime(2021, 4, 15, 21, 30, 44, 55)),
+    (
+        "2021/apr/15 at 21:30:44.55",
+        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
+    ),
+    (
+        "2021/4/15 at 21:30:44.55",
+        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
+    ),
+    (
+        "2021/04/15 at 21:30:44.55",
+        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
+    ),
+    (
+        "2021/04/15 at 21:30:44.55Z",
+        datetime.datetime(2021, 4, 15, 21, 30, 44, 55, tzinfo=pytz.timezone("UTC")),
+    ),
+    (
+        "2021/04/15 at 21:30:44.55EST",
+        datetime.datetime(2021, 4, 15, 21, 30, 44, 55, tzinfo=pytz.timezone("EST")),
+    ),
+    (
+        "13 days after last memorial day at 12 seconds before 4pm",
+        datetime.datetime(2020, 6, 7, 15, 59, 48),
+    ),
+    (
+        "    2     days     before   yesterday    at   9am      ",
+        datetime.datetime(2021, 6, 29, 9),
+    ),
+    ("-3 days before today", datetime.datetime(2021, 7, 5)),
+    (
+        "3 days before yesterday at midnight EST",
+        datetime.datetime(2021, 6, 28, tzinfo=pytz.timezone("EST")),
+    ),
+]
+
+
+class TestDateparseUtils(unittest.TestCase):
+    @uu.check_method_for_perf_regressions
+    def test_dateparsing(self):
+        dp = du.DateParser(override_now_for_test_purposes=datetime.datetime(2021, 7, 2))
+
+        for (txt, expected_dt) in parsable_expressions:
+            try:
+                actual_dt = dp.parse(txt)
+                self.assertIsNotNone(actual_dt)
+                self.assertEqual(
+                    actual_dt,
+                    expected_dt,
+                    f'"{txt}", got "{actual_dt}" while expecting "{expected_dt}"',
+                )
+            except du.ParseException:
+                self.fail(f'Expected "{txt}" to parse successfully.')
+
+    def test_whitespace_handling(self):
+        dp = du.DateParser(override_now_for_test_purposes=datetime.datetime(2021, 7, 2))
+
+        for (txt, expected_dt) in parsable_expressions:
+            try:
+                txt = f" {txt} "
+                i = random.randint(2, 5)
+                replacement = " " * i
+                txt = re.sub(r"\s", replacement, txt)
+                actual_dt = dp.parse(txt)
+                self.assertIsNotNone(actual_dt)
+                self.assertEqual(
+                    actual_dt,
+                    expected_dt,
+                    f'"{txt}", got "{actual_dt}" while expecting "{expected_dt}"',
+                )
+            except du.ParseException:
+                self.fail(f'Expected "{txt}" to parse successfully.')
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/datetimez/dateparse_utils_test.py b/tests/datetimez/dateparse_utils_test.py
deleted file mode 100755 (executable)
index 370b00e..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python3
-
-# © Copyright 2021-2022, Scott Gasch
-
-"""dateparse_utils unittest."""
-
-import datetime
-import random
-import re
-import unittest
-
-import pytz
-
-import pyutils.datetimez.dateparse_utils as du
-import pyutils.unittest_utils as uu
-
-parsable_expressions = [
-    ('today', datetime.datetime(2021, 7, 2)),
-    ('tomorrow', datetime.datetime(2021, 7, 3)),
-    ('yesterday', datetime.datetime(2021, 7, 1)),
-    ('21:30', datetime.datetime(2021, 7, 2, 21, 30, 0, 0)),
-    (
-        '21:30 EST',
-        datetime.datetime(2021, 7, 2, 21, 30, 0, 0, tzinfo=pytz.timezone('EST')),
-    ),
-    (
-        '21:30 -0500',
-        datetime.datetime(2021, 7, 2, 21, 30, 0, 0, tzinfo=pytz.timezone('EST')),
-    ),
-    ('12:01am', datetime.datetime(2021, 7, 2, 0, 1, 0, 0)),
-    ('12:02p', datetime.datetime(2021, 7, 2, 12, 2, 0, 0)),
-    ('0:03', datetime.datetime(2021, 7, 2, 0, 3, 0, 0)),
-    ('last wednesday', datetime.datetime(2021, 6, 30)),
-    ('this wed', datetime.datetime(2021, 7, 7)),
-    ('next wed', datetime.datetime(2021, 7, 14)),
-    ('this coming tues', datetime.datetime(2021, 7, 6)),
-    ('this past monday', datetime.datetime(2021, 6, 28)),
-    ('4 days ago', datetime.datetime(2021, 6, 28)),
-    ('4 mondays ago', datetime.datetime(2021, 6, 7)),
-    ('4 months ago', datetime.datetime(2021, 3, 2)),
-    ('3 days back', datetime.datetime(2021, 6, 29)),
-    ('13 weeks from now', datetime.datetime(2021, 10, 1)),
-    ('1 year from now', datetime.datetime(2022, 7, 2)),
-    ('4 weeks from now', datetime.datetime(2021, 7, 30)),
-    ('3 saturdays ago', datetime.datetime(2021, 6, 12)),
-    ('4 months from today', datetime.datetime(2021, 11, 2)),
-    ('4 years from yesterday', datetime.datetime(2025, 7, 1)),
-    ('4 weeks from tomorrow', datetime.datetime(2021, 7, 31)),
-    ('april 15, 2005', datetime.datetime(2005, 4, 15)),
-    ('april 14', datetime.datetime(2021, 4, 14)),
-    ('9:30am on last wednesday', datetime.datetime(2021, 6, 30, 9, 30)),
-    ('2005/apr/15', datetime.datetime(2005, 4, 15)),
-    ('2005 apr 15', datetime.datetime(2005, 4, 15)),
-    ('the 1st wednesday in may', datetime.datetime(2021, 5, 5)),
-    ('last sun of june', datetime.datetime(2021, 6, 27)),
-    ('this Easter', datetime.datetime(2021, 4, 4)),
-    ('last christmas', datetime.datetime(2020, 12, 25)),
-    ('last Xmas', datetime.datetime(2020, 12, 25)),
-    ('xmas, 1999', datetime.datetime(1999, 12, 25)),
-    ('next mlk day', datetime.datetime(2022, 1, 17)),
-    ('Halloween, 2020', datetime.datetime(2020, 10, 31)),
-    ('5 work days after independence day', datetime.datetime(2021, 7, 12)),
-    ('50 working days from last wed', datetime.datetime(2021, 9, 10)),
-    ('25 working days before columbus day', datetime.datetime(2021, 9, 3)),
-    ('today +1 week', datetime.datetime(2021, 7, 9)),
-    ('sunday -3 weeks', datetime.datetime(2021, 6, 13)),
-    ('4 weeks before xmas, 1999', datetime.datetime(1999, 11, 27)),
-    ('3 days before new years eve, 2000', datetime.datetime(2000, 12, 28)),
-    ('july 4th', datetime.datetime(2021, 7, 4)),
-    ('the ides of march', datetime.datetime(2021, 3, 15)),
-    ('the nones of april', datetime.datetime(2021, 4, 5)),
-    ('the kalends of may', datetime.datetime(2021, 5, 1)),
-    ('9/11/2001', datetime.datetime(2001, 9, 11)),
-    ('4 sundays before veterans\' day', datetime.datetime(2021, 10, 17)),
-    ('xmas eve', datetime.datetime(2021, 12, 24)),
-    ('this friday at 5pm', datetime.datetime(2021, 7, 9, 17, 0, 0)),
-    ('presidents day', datetime.datetime(2021, 2, 15)),
-    ('memorial day, 1921', datetime.datetime(1921, 5, 30)),
-    ('today -4 wednesdays', datetime.datetime(2021, 6, 9)),
-    ('thanksgiving', datetime.datetime(2021, 11, 25)),
-    ('2 sun in jun', datetime.datetime(2021, 6, 13)),
-    ('easter -40 days', datetime.datetime(2021, 2, 23)),
-    ('easter +39 days', datetime.datetime(2021, 5, 13)),
-    ('2nd Sunday in May, 2022', datetime.datetime(2022, 5, 8)),
-    ('1st tuesday in nov, 2024', datetime.datetime(2024, 11, 5)),
-    (
-        '2 days before last xmas at 3:14:15.92a',
-        datetime.datetime(2020, 12, 23, 3, 14, 15, 92),
-    ),
-    (
-        '3 weeks after xmas, 1995 at midday',
-        datetime.datetime(1996, 1, 15, 12, 0, 0),
-    ),
-    (
-        '4 months before easter, 1992 at midnight',
-        datetime.datetime(1991, 12, 19),
-    ),
-    (
-        '5 months before halloween, 1995 at noon',
-        datetime.datetime(1995, 5, 31, 12),
-    ),
-    ('4 days before last wednesday', datetime.datetime(2021, 6, 26)),
-    ('44 months after today', datetime.datetime(2025, 3, 2)),
-    ('44 years before today', datetime.datetime(1977, 7, 2)),
-    ('44 weeks ago', datetime.datetime(2020, 8, 28)),
-    ('15 minutes to 3am', datetime.datetime(2021, 7, 2, 2, 45)),
-    ('quarter past 4pm', datetime.datetime(2021, 7, 2, 16, 15)),
-    ('half past 9', datetime.datetime(2021, 7, 2, 9, 30)),
-    ('4 seconds to midnight', datetime.datetime(2021, 7, 1, 23, 59, 56)),
-    (
-        '4 seconds to midnight, tomorrow',
-        datetime.datetime(2021, 7, 2, 23, 59, 56),
-    ),
-    ('2021/apr/15T21:30:44.55', datetime.datetime(2021, 4, 15, 21, 30, 44, 55)),
-    (
-        '2021/apr/15 at 21:30:44.55',
-        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
-    ),
-    (
-        '2021/4/15 at 21:30:44.55',
-        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
-    ),
-    (
-        '2021/04/15 at 21:30:44.55',
-        datetime.datetime(2021, 4, 15, 21, 30, 44, 55),
-    ),
-    (
-        '2021/04/15 at 21:30:44.55Z',
-        datetime.datetime(2021, 4, 15, 21, 30, 44, 55, tzinfo=pytz.timezone('UTC')),
-    ),
-    (
-        '2021/04/15 at 21:30:44.55EST',
-        datetime.datetime(2021, 4, 15, 21, 30, 44, 55, tzinfo=pytz.timezone('EST')),
-    ),
-    (
-        '13 days after last memorial day at 12 seconds before 4pm',
-        datetime.datetime(2020, 6, 7, 15, 59, 48),
-    ),
-    (
-        '    2     days     before   yesterday    at   9am      ',
-        datetime.datetime(2021, 6, 29, 9),
-    ),
-    ('-3 days before today', datetime.datetime(2021, 7, 5)),
-    (
-        '3 days before yesterday at midnight EST',
-        datetime.datetime(2021, 6, 28, tzinfo=pytz.timezone('EST')),
-    ),
-]
-
-
-class TestDateparseUtils(unittest.TestCase):
-    @uu.check_method_for_perf_regressions
-    def test_dateparsing(self):
-        dp = du.DateParser(override_now_for_test_purposes=datetime.datetime(2021, 7, 2))
-
-        for (txt, expected_dt) in parsable_expressions:
-            try:
-                actual_dt = dp.parse(txt)
-                self.assertIsNotNone(actual_dt)
-                self.assertEqual(
-                    actual_dt,
-                    expected_dt,
-                    f'"{txt}", got "{actual_dt}" while expecting "{expected_dt}"',
-                )
-            except du.ParseException:
-                self.fail(f'Expected "{txt}" to parse successfully.')
-
-    def test_whitespace_handling(self):
-        dp = du.DateParser(override_now_for_test_purposes=datetime.datetime(2021, 7, 2))
-
-        for (txt, expected_dt) in parsable_expressions:
-            try:
-                txt = f' {txt} '
-                i = random.randint(2, 5)
-                replacement = ' ' * i
-                txt = re.sub(r'\s', replacement, txt)
-                actual_dt = dp.parse(txt)
-                self.assertIsNotNone(actual_dt)
-                self.assertEqual(
-                    actual_dt,
-                    expected_dt,
-                    f'"{txt}", got "{actual_dt}" while expecting "{expected_dt}"',
-                )
-            except du.ParseException:
-                self.fail(f'Expected "{txt}" to parse successfully.')
-
-
-if __name__ == '__main__':
-    unittest.main()
similarity index 92%
rename from tests/typez/centcount_test.py
rename to tests/types/centcount_test.py
index 2bb6d3e8e3b21b6cdb02d985ae74db35dc231547..62181f4d5b41e8abea040c9db0ddbc0be212c208 100755 (executable)
@@ -7,7 +7,7 @@
 import unittest
 
 from pyutils import unittest_utils
-from pyutils.typez.centcount import CentCount
+from pyutils.types.centcount import CentCount
 
 
 class TestCentCount(unittest.TestCase):
@@ -58,10 +58,10 @@ class TestCentCount(unittest.TestCase):
             amount /= another
 
     def test_equality(self):
-        usa = CentCount(1.0, 'USD')
-        can = CentCount(1.0, 'CAD')
+        usa = CentCount(1.0, "USD")
+        can = CentCount(1.0, "CAD")
         self.assertNotEqual(usa, can)
-        eh = CentCount(1.0, 'CAD')
+        eh = CentCount(1.0, "CAD")
         self.assertEqual(can, eh)
 
     def test_comparison(self):
@@ -73,7 +73,7 @@ class TestCentCount(unittest.TestCase):
         self.assertLess(neg_one, one)
         self.assertGreater(one, neg_one)
         self.assertGreater(three, one)
-        looney = CentCount(1.0, 'CAD')
+        looney = CentCount(1.0, "CAD")
         with self.assertRaises(TypeError):
             print(looney < one)
 
@@ -97,5 +97,5 @@ class TestCentCount(unittest.TestCase):
         self.assertTrue(two > one)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
similarity index 91%
rename from tests/typez/money_test.py
rename to tests/types/money_test.py
index e296e5154a02f242c44e21e9c1be3df4243b45e9..524f10339705f60e999dc6abfcfbeeaa63a67668 100755 (executable)
@@ -8,7 +8,7 @@ import unittest
 from decimal import Decimal
 
 from pyutils import unittest_utils
-from pyutils.typez.money import Money
+from pyutils.types.money import Money
 
 
 class TestMoney(unittest.TestCase):
@@ -57,10 +57,10 @@ class TestMoney(unittest.TestCase):
             amount /= another
 
     def test_equality(self):
-        usa = Money(1.0, 'USD')
-        can = Money(1.0, 'CAD')
+        usa = Money(1.0, "USD")
+        can = Money(1.0, "CAD")
         self.assertNotEqual(usa, can)
-        eh = Money(1.0, 'CAD')
+        eh = Money(1.0, "CAD")
         self.assertEqual(can, eh)
 
     def test_comparison(self):
@@ -72,7 +72,7 @@ class TestMoney(unittest.TestCase):
         self.assertLess(neg_one, one)
         self.assertGreater(one, neg_one)
         self.assertGreater(three, one)
-        looney = Money(1.0, 'CAD')
+        looney = Money(1.0, "CAD")
         with self.assertRaises(TypeError):
             print(looney < one)
 
@@ -99,13 +99,13 @@ class TestMoney(unittest.TestCase):
         ten = Money(10.0)
         x = ten * 2 / 3
         expected = Decimal(6.66)
-        expected = expected.quantize(Decimal('.01'))
+        expected = expected.quantize(Decimal(".01"))
         self.assertEqual(expected, x.truncate_fractional_cents())
         x = ten * 2 / 3
         expected = Decimal(6.67)
-        expected = expected.quantize(Decimal('.01'))
+        expected = expected.quantize(Decimal(".01"))
         self.assertEqual(expected, x.round_fractional_cents())
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
similarity index 94%
rename from tests/typez/rate_test.py
rename to tests/types/rate_test.py
index 800d360359756d34dd1363e20c843c3f64b7c84d..15449ebfed96ff58b5a8da3b6ede9ade2e79719c 100755 (executable)
@@ -7,8 +7,8 @@
 import unittest
 
 from pyutils import unittest_utils
-from pyutils.typez.money import Money
-from pyutils.typez.rate import Rate
+from pyutils.types.money import Money
+from pyutils.types.rate import Rate
 
 
 class TestRate(unittest.TestCase):
@@ -67,5 +67,5 @@ class TestRate(unittest.TestCase):
         self.assertEqual("+50.000%", s)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()