Skip to content

Commit 66828c5

Browse files
redjaxactions-user
andauthored
Feat/replace arrow (#323) (#324)
* Remove arrow dependency from main group. Add optional [arrow] group. Fix time_utils imports. Default now uses Pendulum, but Arrow utils can be imported from time_utils.arrow_tils. Update time_utils constant names (uppercase) Update demos. * Update demo * Auto-export requirements files --------- Co-authored-by: GitHub Action <[email protected]>
1 parent dfcca68 commit 66828c5

26 files changed

+204
-77
lines changed

demo.py

+55-3
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,19 @@
4545
from red_utils import CustomException
4646
from red_utils.ext.context_managers import cli_spinners
4747

48+
4849
def test_file_utils_list() -> list[Path]:
4950
cwd = Path.cwd()
5051
search_dir = f"{cwd}/red_utils"
5152

5253
list_files_test = path_utils.list_files(in_dir=search_dir, ext_filter=".py")
5354
print(f".py files found in {search_dir}: {len(list_files_test)}")
5455

56+
if len(list_files_test) == 0:
57+
raise FileNotFoundError(
58+
f"Did not find any Python files in directory: {search_dir}"
59+
)
60+
5561
rand_index = random.randint(0, len(list_files_test) - 1)
5662
print(f"Example util file: {list_files_test[rand_index]}")
5763

@@ -209,8 +215,8 @@ def _trim(in_uuid=uuid_utils.get_rand_uuid(), trim=12):
209215

210216

211217
def test_time_utils():
212-
fmt: str = time_utils.default_format
213-
fmt_12: str = time_utils.twelve_hour_format
218+
fmt: str = time_utils.TIME_FMT_24H
219+
fmt_12: str = time_utils.TIME_FMT_12H
214220

215221
def dt_as_dt(ts=time_utils.get_ts(), fmt=fmt):
216222
_dt = time_utils.datetime_as_dt(ts=ts, format=fmt)
@@ -223,7 +229,7 @@ def dt_as_str(ts=time_utils.get_ts(), fmt=fmt):
223229
return _dt
224230

225231
now_unformatted = time_utils.get_ts(format=fmt)
226-
now = now_unformatted.strftime(time_utils.default_format)
232+
now = now_unformatted.strftime(time_utils.TIME_FMT_24H)
227233
print(f"Timestamp ({type(now_unformatted)}): {now_unformatted}")
228234
print(f"Timestamp formatted ({type(now)}): {now}")
229235

@@ -394,6 +400,10 @@ def test_fastapi_utils():
394400

395401

396402
def test_sqlalchemy_utils():
403+
if not pkgutil.find_loader("sqlalchemy"):
404+
print(f"SQLAlchemy dependency missing, skipping tests.")
405+
return None
406+
397407
base = sqlalchemy_utils.Base()
398408
connection = sqlalchemy_utils.saSQLiteConnection()
399409
print(f"Connection: {connection}")
@@ -428,6 +438,48 @@ def test_ensuredirs(_dirs: list[Path] = [Path("test"), Path("test/testing")]):
428438
path_utils.ensure_dirs_exist(ensure_dirs=_dirs)
429439

430440

441+
def test_pendulum():
442+
if not pkgutil.find_loader("pendulum"):
443+
print(f"Pendulum dependency not found, skipping timestamp demo.")
444+
return None
445+
446+
from red_utils.ext.time_utils import (
447+
VALID_TIME_PERIODS,
448+
TIME_FMT_24H,
449+
TIME_FMT_12H,
450+
TS_STR_REPLACE_MAP,
451+
get_ts,
452+
)
453+
454+
test = get_ts()
455+
print(f"Test timestamp 1 - 24h/no-params ({type(test)}): {test}")
456+
457+
test2 = get_ts(as_str=True)
458+
print(f"Test timestamp 2 - 24h ({type(test2)}): {test2}")
459+
460+
test3 = get_ts(as_str=True, str_fmt=TIME_FMT_12H)
461+
print(f"Test timestamp 3 - 12h ({type(test3)}): {test3}")
462+
463+
test4 = get_ts(as_str=True, safe_str=True)
464+
print(f"Test timestamp 4 - 24h/safe-string ({type(test4)}): {test4}")
465+
466+
test5 = get_ts(as_str=True, safe_str=True, str_fmt=TIME_FMT_12H)
467+
print(f"Test timestamp 5 - 12h/safe-string ({type(test5)}): {test5}")
468+
469+
470+
def test_arrow():
471+
if not pkgutil.find_loader("arrow"):
472+
print(f"Arrow dependency not found, skipping timestamp demo.")
473+
return None
474+
475+
import arrow
476+
from red_utils.ext.time_utils.arrow_utils import shift_ts
477+
478+
now = arrow.now()
479+
print(f"Now: {now}")
480+
print(f"+1 day: {now.shift(days=1)}")
481+
482+
431483
def main():
432484
"""Main function to control flow of demo.
433485

pdm.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ dependencies = [
1111
"loguru>=0.7.0",
1212
"msgpack>=1.0.5",
1313
"httpx>=0.24.1",
14-
"arrow>=1.2.3",
1514
"pendulum>=2.1.2",
1615
"rich>=13.5.3",
1716
]
@@ -43,6 +42,7 @@ all = [
4342
"msgpack>=1.0.5",
4443
"pendulum>=2.1.2",
4544
"sqlalchemy>=2.0.21",
45+
"arrow>=1.3.0",
4646
]
4747
fastapi = [
4848
"fastapi>=0.103.1",
@@ -62,6 +62,9 @@ http = [
6262
"pendulum>=2.1.2",
6363
]
6464
"ci.lint" = ["ruff>=0.1.7", "black>=23.12.0"]
65+
arrow = [
66+
"arrow>=1.3.0",
67+
]
6568

6669
[tool.pdm.dev-dependencies]
6770
dev = [

red_utils/ext/__init__.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
## Use pkgutil to only load modules
66
# if dependencies are met
7-
if pkgutil.find_loader("arrow"):
8-
from . import arrow_utils
9-
10-
if pkgutil.find_loader("pendulum"):
11-
from . import pendulum_utils
7+
# if pkgutil.find_loader("arrow"):
8+
# from . import arrow_utils
9+
10+
# if pkgutil.find_loader("pendulum"):
11+
# from . import pendulum_utils
12+
13+
if pkgutil.find_loader("arrow") and pkgutil.find_loader("pendulum"):
14+
from . import time_utils
15+
elif pkgutil.find_loader("arrow"):
16+
from .time_utils import arrow_utils
17+
elif pkgutil.find_loader("pendulum"):
18+
from .time_utils import pendulum_utils
1219

1320
if pkgutil.find_loader("loguru"):
1421
from . import loguru_utils

red_utils/ext/arrow_utils/constants.py

-4
This file was deleted.

red_utils/ext/arrow_utils/validators.py

-12
This file was deleted.

red_utils/ext/pendulum_utils/__init__.py

-10
This file was deleted.

red_utils/ext/pendulum_utils/constants.py

-11
This file was deleted.

red_utils/ext/time_utils/__init__.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pkgutil
2+
3+
if pkgutil.find_loader("arrow"):
4+
from . import arrow_utils
5+
6+
if pkgutil.find_loader("pendulum"):
7+
from . import pendulum_utils
8+
from .pendulum_utils import get_ts
9+
from .pendulum_utils.validators import validate_time_period
10+
from .pendulum_utils.constants import (
11+
TIME_FMT_24H,
12+
TIME_FMT_12H,
13+
DEFAULT_TZ,
14+
TS_STR_REPLACE_MAP,
15+
VALID_TIME_PERIODS,
16+
)

red_utils/ext/arrow_utils/__init__.py renamed to red_utils/ext/time_utils/arrow_utils/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
# i.e. from arrow_utils.shift_ts()
33
from __future__ import annotations
44

5-
from . import constants, operations, validators
5+
from .constants import TIME_FMT_12H, TIME_FMT_24H, VALID_TIME_PERIODS
6+
from .validators import validate_time_period
67
from .operations import shift_ts

red_utils/ext/pendulum_utils/validators.py renamed to red_utils/ext/time_utils/arrow_utils/constants.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import annotations
22

3-
valid_time_periods: list[str] = [
3+
TIME_FMT_24H: str = "%Y-%m-%d_%H:%M:%S"
4+
TIME_FMT_12H: str = "%Y-%m-%d_%I:%M:%S%p"
5+
6+
VALID_TIME_PERIODS: list[str] = [
47
"years",
58
"months",
69
"weeks",

red_utils/ext/arrow_utils/operations.py renamed to red_utils/ext/time_utils/arrow_utils/operations.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
from typing import Union
66

7-
from .constants import default_format, twelve_hour_format
8-
from .validators import valid_time_periods
7+
from .constants import TIME_FMT_24H, TIME_FMT_12H
8+
from .validators import VALID_TIME_PERIODS
99

1010
import arrow
1111

12+
1213
def shift_ts(
1314
start_date: Union[datetime.datetime, str, arrow.Arrow] = None,
1415
_tz: str = "US/Eastern",
@@ -47,15 +48,15 @@ def shift_ts(
4748
## Validate inputs
4849
if not period:
4950
raise ValueError(
50-
f"Missing a period of time. Must be one of {valid_time_periods}"
51+
f"Missing a period of time. Must be one of {VALID_TIME_PERIODS}"
5152
)
5253
if not isinstance(period, str) and not isinstance(period, datetime.datetime):
5354
raise TypeError(
5455
f"Invalid type for period: ({type(period)}). Must be one of [str, datetime.datetime]"
5556
)
56-
if period not in valid_time_periods:
57+
if period not in VALID_TIME_PERIODS:
5758
raise TypeError(
58-
f"Invalid period of time: [{period}]. Must be one of {valid_time_periods}"
59+
f"Invalid period of time: [{period}]. Must be one of {VALID_TIME_PERIODS}"
5960
)
6061
if not amount:
6162
raise ValueError("Missing amount of time to shift timestamp")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## List of accepted values for 'period'
2+
from __future__ import annotations
3+
4+
from .constants import VALID_TIME_PERIODS
5+
6+
7+
def validate_time_period(period: str = None) -> str:
8+
"""Validate a time period string.
9+
10+
Description:
11+
------------
12+
Pass a time period (i.e. "days", "weeks", etc). If the period
13+
matches a valid time period, string is returned, otherwise a
14+
ValueError is raised.
15+
"""
16+
if period is None:
17+
raise ValueError("Missing a time period to evaluate")
18+
if not isinstance(period, str):
19+
raise TypeError(
20+
f"Invalid type for time period: ({type(period)}). Must be one of {VALID_TIME_PERIODS}"
21+
)
22+
if period not in VALID_TIME_PERIODS:
23+
raise ValueError(
24+
f"Invalid time period: '{period}'. Must be one of {VALID_TIME_PERIODS}"
25+
)
26+
27+
return period
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
from .constants import (
4+
TIME_FMT_24H,
5+
DEFAULT_TZ,
6+
TS_STR_REPLACE_MAP,
7+
TIME_FMT_12H,
8+
)
9+
from .operations import get_ts
10+
from .validators import VALID_TIME_PERIODS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import annotations
2+
3+
TIME_FMT_24H: str = "YYYY-MM-DD HH:MM:SS"
4+
TIME_FMT_12H: str = "YYYY-MM-DD hh:mm:ssA"
5+
DEFAULT_TZ: str = "America/New_York"
6+
7+
## Mapping for string character replacement
8+
TS_STR_REPLACE_MAP = [
9+
{"search": ":", "replace": "-"},
10+
{"search": " ", "replace": "_"},
11+
]
12+
13+
VALID_TIME_PERIODS: list[str] = [
14+
"years",
15+
"months",
16+
"weeks",
17+
"days",
18+
"hours",
19+
"minutes",
20+
"seconds",
21+
]

red_utils/ext/pendulum_utils/operations.py renamed to red_utils/ext/time_utils/pendulum_utils/operations.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@
33
from typing import Union
44

55
from .constants import (
6-
default_format,
7-
default_tz,
8-
safe_str_replace_map,
9-
twelve_hour_format,
6+
TIME_FMT_24H,
7+
DEFAULT_TZ,
8+
TS_STR_REPLACE_MAP,
9+
TIME_FMT_12H,
1010
)
11-
from .validators import valid_time_periods
11+
from .validators import VALID_TIME_PERIODS
1212

1313
import pendulum
1414

15+
1516
def get_ts(
16-
tz: str = default_tz,
17+
tz: str = DEFAULT_TZ,
1718
as_str: bool = False,
18-
str_fmt: str = default_format,
19+
str_fmt: str = TIME_FMT_24H,
1920
safe_str: bool = False,
20-
char_replace_map: list[dict] = safe_str_replace_map,
21+
char_replace_map: list[dict] = TS_STR_REPLACE_MAP,
2122
) -> pendulum.DateTime:
2223
"""Return a Pendulum.DateTime object of the current time. Optionally
2324
return timestamp as a string.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import annotations
2+
3+
from .constants import VALID_TIME_PERIODS
4+
5+
6+
def validate_time_period(period: str = None) -> str:
7+
"""Validate a time period string.
8+
9+
Description:
10+
------------
11+
Pass a time period (i.e. "days", "weeks", etc). If the period
12+
matches a valid time period, string is returned, otherwise a
13+
ValueError is raised.
14+
"""
15+
if period is None:
16+
raise ValueError("Missing a time period to evaluate")
17+
if not isinstance(period, str):
18+
raise TypeError(
19+
f"Invalid type for time period: ({type(period)}). Must be one of {VALID_TIME_PERIODS}"
20+
)
21+
if period not in VALID_TIME_PERIODS:
22+
raise ValueError(
23+
f"Invalid time period: '{period}'. Must be one of {VALID_TIME_PERIODS}"
24+
)
25+
26+
return period

0 commit comments

Comments
 (0)