none-shall-parse 0.4.5__py3-none-any.whl → 0.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,28 +10,48 @@ translates to "none". Combined this with our parsing intentions
10
10
  to create a name which nods to the Black Knight in Monty Python's Holy Grail.
11
11
  https://www.youtube.com/watch?v=zKhEw7nD9C4
12
12
  """
13
+
13
14
  from __future__ import annotations
14
15
  from .dates import (
15
- DateUtilsError, assert_week_start_date_is_valid,
16
+ DateUtilsError,
17
+ assert_week_start_date_is_valid,
16
18
  assert_month_start_date_is_valid,
17
- get_datetime_now, za_now,
19
+ get_datetime_now,
20
+ za_now,
21
+ utc_now,
18
22
  za_ordinal_year_day_now,
19
- za_ordinal_year_day_tomorrow, utc_epoch_start,
20
- now_offset_n_minutes, now_offset_n_hours,
21
- now_offset_n_days, get_datetime_tomorrow,
23
+ za_ordinal_year_day_tomorrow,
24
+ utc_epoch_start,
25
+ now_offset_n_minutes,
26
+ now_offset_n_hours,
27
+ now_offset_n_days,
28
+ now_offset_n_months,
29
+ get_datetime_tomorrow,
22
30
  get_datetime_yesterday,
23
31
  get_utc_datetime_offset_n_days,
24
- epoch_to_datetime, epoch_to_utc_datetime,
32
+ epoch_to_datetime,
33
+ epoch_to_utc_datetime,
25
34
  is_office_hours_in_timezone,
26
35
  get_datetime_from_ordinal_and_sentinel,
27
- day_span, week_span, month_span, arb_span,
28
- calendar_month_start_end, unix_timestamp,
36
+ day_span,
37
+ week_span,
38
+ month_span,
39
+ arb_span,
40
+ calendar_month_start_end,
41
+ unix_timestamp,
29
42
  sentinel_date_and_ordinal_to_date,
30
- seconds_to_end_of_month, standard_tz_timestring,
31
- get_notice_end_date, dt_to_za_time_string,
32
- months_ago_selection, is_aware, make_aware,
33
- unaware_to_utc_aware, timer_decorator, ZA_TZ,
34
- UTC_TZ, keys_for_span_func,
43
+ seconds_to_end_of_month,
44
+ standard_tz_timestring,
45
+ get_notice_end_date,
46
+ dt_to_za_time_string,
47
+ months_ago_selection,
48
+ is_aware,
49
+ make_aware,
50
+ unaware_to_utc_aware,
51
+ timer_decorator,
52
+ ZA_TZ,
53
+ UTC_TZ,
54
+ keys_for_span_func,
35
55
  )
36
56
  from .imeis import (
37
57
  get_luhn_digit,
@@ -83,26 +103,45 @@ __email__ = "andries.niemandt@trintel.co.za, jan@trintel.co.za"
83
103
  __license__ = "MIT"
84
104
 
85
105
  __all__ = (
86
- "DateUtilsError", "assert_week_start_date_is_valid",
106
+ "DateUtilsError",
107
+ "assert_week_start_date_is_valid",
87
108
  "assert_month_start_date_is_valid",
88
- "get_datetime_now", "za_now",
109
+ "get_datetime_now",
110
+ "za_now",
111
+ "utc_now",
89
112
  "za_ordinal_year_day_now",
90
- "za_ordinal_year_day_tomorrow", "utc_epoch_start",
91
- "now_offset_n_minutes", "now_offset_n_hours",
92
- "now_offset_n_days", "get_datetime_tomorrow",
113
+ "za_ordinal_year_day_tomorrow",
114
+ "utc_epoch_start",
115
+ "now_offset_n_minutes",
116
+ "now_offset_n_hours",
117
+ "now_offset_n_days",
118
+ "now_offset_n_months",
119
+ "get_datetime_tomorrow",
93
120
  "get_datetime_yesterday",
94
121
  "get_utc_datetime_offset_n_days",
95
- "epoch_to_datetime", "epoch_to_utc_datetime",
122
+ "epoch_to_datetime",
123
+ "epoch_to_utc_datetime",
96
124
  "is_office_hours_in_timezone",
97
125
  "get_datetime_from_ordinal_and_sentinel",
98
- "day_span", "week_span", "month_span", "arb_span",
99
- "calendar_month_start_end", "unix_timestamp",
126
+ "day_span",
127
+ "week_span",
128
+ "month_span",
129
+ "arb_span",
130
+ "calendar_month_start_end",
131
+ "unix_timestamp",
100
132
  "sentinel_date_and_ordinal_to_date",
101
- "seconds_to_end_of_month", "standard_tz_timestring",
102
- "get_notice_end_date", "dt_to_za_time_string",
103
- "months_ago_selection", "is_aware", "make_aware",
104
- "unaware_to_utc_aware", "timer_decorator", "ZA_TZ",
105
- "UTC_TZ", "keys_for_span_func",
133
+ "seconds_to_end_of_month",
134
+ "standard_tz_timestring",
135
+ "get_notice_end_date",
136
+ "dt_to_za_time_string",
137
+ "months_ago_selection",
138
+ "is_aware",
139
+ "make_aware",
140
+ "unaware_to_utc_aware",
141
+ "timer_decorator",
142
+ "ZA_TZ",
143
+ "UTC_TZ",
144
+ "keys_for_span_func",
106
145
  "get_luhn_digit",
107
146
  "is_valid_luhn",
108
147
  "is_valid_imei",
@@ -136,4 +175,4 @@ __all__ = (
136
175
  "DateTimeLike",
137
176
  "DateLike",
138
177
  "DateTimeOrDateLike",
139
- )
178
+ )
none_shall_parse/dates.py CHANGED
@@ -3,15 +3,15 @@ import logging
3
3
  import time
4
4
  from datetime import date, datetime
5
5
  from typing import Callable, Any, Tuple, Sequence, List
6
- from .types import DateTimeLike
6
+ from .types import DateTimeLike, DateTimeOrDateLike
7
7
 
8
8
  import pendulum
9
9
  from pendulum import DateTime, Date
10
10
  from pendulum import local_timezone
11
11
  from pendulum.tz.exceptions import InvalidTimezone
12
12
 
13
- ZA_TZ = 'Africa/Johannesburg'
14
- UTC_TZ = 'UTC'
13
+ ZA_TZ = "Africa/Johannesburg"
14
+ UTC_TZ = "UTC"
15
15
 
16
16
 
17
17
  class DateUtilsError(Exception):
@@ -48,13 +48,17 @@ def get_datetime_now(naive: bool = False, tz: str | None = None) -> DateTime:
48
48
  naive (bool): If True, returns a naive DateTime object without timezone information.
49
49
  Defaults to True.
50
50
  tz (Optional[str]): The timezone to use if a timezone-aware DateTime is requested.
51
- If not provided and naive is False, the default system timezone
52
- is used.
51
+ If not provided and naive is False, defaults to ZA_TZ.
53
52
 
54
53
  Returns:
55
54
  pendulum.DateTime: The current date and time based on the specified parameters.
56
55
  """
57
- return pendulum.now().naive() if naive else pendulum.now(tz=tz)
56
+ if naive:
57
+ return pendulum.now().naive()
58
+ else:
59
+ # Default to ZA_TZ if no timezone is specified for aware datetime
60
+ timezone = tz if tz is not None else ZA_TZ
61
+ return pendulum.now(tz=timezone)
58
62
 
59
63
 
60
64
  def za_now() -> DateTime:
@@ -71,6 +75,15 @@ def za_now() -> DateTime:
71
75
  return get_datetime_now(naive=False, tz=ZA_TZ)
72
76
 
73
77
 
78
+ def utc_now() -> DateTime:
79
+ """
80
+ Shorthand for the current date and time in the UTC timezone.
81
+ Returns:
82
+ pendulum.DateTime: The current date and time in the UTC timezone.
83
+ """
84
+ return get_datetime_now(naive=False, tz=UTC_TZ)
85
+
86
+
74
87
  def za_ordinal_year_day_now() -> int:
75
88
  """
76
89
  Returns the current ordinal day of the year for the ZA_TZ timezone.
@@ -111,8 +124,9 @@ def utc_epoch_start() -> DateTime:
111
124
  return pendulum.from_timestamp(0)
112
125
 
113
126
 
114
- def _now_offset_n_units(n: int, units: str, naive: bool = False,
115
- tz: str | None = None) -> DateTime:
127
+ def _now_offset_n_units(
128
+ n: int, units: str, naive: bool = False, tz: str | None = None
129
+ ) -> DateTime:
116
130
  """
117
131
  Calculate a DateTime object offset by a specified number of time units.
118
132
 
@@ -132,33 +146,37 @@ def _now_offset_n_units(n: int, units: str, naive: bool = False,
132
146
  DateTime: The calculated DateTime object, optionally timezone-aware or naive.
133
147
  """
134
148
  kwargs = {units: n}
135
- return pendulum.now().add(**kwargs).naive() if naive else pendulum.now(tz).add(
136
- **kwargs)
149
+ return (
150
+ pendulum.now().add(**kwargs).naive()
151
+ if naive
152
+ else pendulum.now(tz).add(**kwargs)
153
+ )
137
154
 
138
155
 
139
- def now_offset_n_minutes(n: int, naive: bool = False,
140
- tz: str | None = None) -> DateTime:
156
+ def now_offset_n_minutes(
157
+ n: int, naive: bool = False, tz: str | None = None
158
+ ) -> DateTime:
141
159
  return _now_offset_n_units(n, units="minutes", naive=naive, tz=tz)
142
160
 
143
161
 
144
- def now_offset_n_hours(n: int, naive: bool = False,
145
- tz: str | None = None) -> DateTime:
162
+ def now_offset_n_hours(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
146
163
  return _now_offset_n_units(n, units="hours", naive=naive, tz=tz)
147
164
 
148
165
 
149
- def now_offset_n_days(n: int, naive: bool = False,
150
- tz: str | None = None) -> DateTime:
166
+ def now_offset_n_days(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
151
167
  return _now_offset_n_units(n, units="days", naive=naive, tz=tz)
152
168
 
153
169
 
154
- def get_datetime_tomorrow(naive: bool = False,
155
- tz: str | None = None) -> DateTime:
170
+ def now_offset_n_months(n: int, naive: bool = False, tz: str | None = None) -> DateTime:
171
+ return _now_offset_n_units(n, units="months", naive=naive, tz=tz)
172
+
173
+
174
+ def get_datetime_tomorrow(naive: bool = False, tz: str | None = None) -> DateTime:
156
175
  """Get tomorrow's DateTime"""
157
176
  return now_offset_n_days(1, naive=naive, tz=tz)
158
177
 
159
178
 
160
- def get_datetime_yesterday(naive: bool = False,
161
- tz: str | None = None) -> DateTime:
179
+ def get_datetime_yesterday(naive: bool = False, tz: str | None = None) -> DateTime:
162
180
  """Get yesterday's DateTime"""
163
181
  return now_offset_n_days(-1, naive=naive, tz=tz)
164
182
 
@@ -168,8 +186,9 @@ def get_utc_datetime_offset_n_days(n: int = 0) -> DateTime:
168
186
  return pendulum.now(UTC_TZ).add(days=n)
169
187
 
170
188
 
171
- def epoch_to_datetime(epoch_int: int | float, naive: bool = False,
172
- tz: str | None = None) -> DateTime:
189
+ def epoch_to_datetime(
190
+ epoch_int: int | float, naive: bool = False, tz: str | None = None
191
+ ) -> DateTime:
173
192
  """
174
193
  Converts an epoch timestamp to a DateTime object.
175
194
 
@@ -218,7 +237,9 @@ def epoch_to_utc_datetime(epoch_int: int | float | str) -> DateTime:
218
237
  return pendulum.from_timestamp(int(epoch_int), tz=UTC_TZ)
219
238
 
220
239
 
221
- def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = None) -> bool:
240
+ def is_office_hours_in_timezone(
241
+ epoch_int: int | float | str, tz: str | None = None
242
+ ) -> bool:
222
243
  """
223
244
  Determines if a given epoch timestamp falls within office hours for a
224
245
  specific timezone.
@@ -250,7 +271,8 @@ def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = N
250
271
 
251
272
 
252
273
  def get_datetime_from_ordinal_and_sentinel(
253
- sentinel: DateTimeLike | None = None) -> Callable[[int], DateTime]:
274
+ sentinel: DateTimeLike | None = None,
275
+ ) -> Callable[[int], DateTime]:
254
276
  """
255
277
  Given an ordinal year day, and a sentinel datetime, get the closest past
256
278
  datetime to the sentinel that had the given ordinal year day.
@@ -311,8 +333,8 @@ def day_span(pts: DateTimeLike) -> Tuple[DateTime, DateTime]:
311
333
  pdt = pendulum.instance(pts)
312
334
 
313
335
  # Use Pendulum's clean API
314
- begin = pdt.start_of('day')
315
- end = pdt.add(days=1).start_of('day')
336
+ begin = pdt.start_of("day")
337
+ end = pdt.add(days=1).start_of("day")
316
338
 
317
339
  if naive:
318
340
  return begin.naive(), end.naive()
@@ -349,7 +371,7 @@ def week_span(wso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
349
371
  current_weekday = pdt.weekday() + 1 # Pendulum uses 0-6, we want 1-7
350
372
  days_back = (current_weekday - wso) % 7
351
373
 
352
- begin = pdt.start_of('day').subtract(days=days_back)
374
+ begin = pdt.start_of("day").subtract(days=days_back)
353
375
  end = begin.add(days=7)
354
376
 
355
377
  if naive:
@@ -388,10 +410,10 @@ def month_span(mso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
388
410
 
389
411
  if current_day >= mso:
390
412
  # We're in the current month period
391
- begin = pdt.start_of('day').replace(day=mso)
413
+ begin = pdt.start_of("day").replace(day=mso)
392
414
  else:
393
415
  # We're in the previous month period
394
- begin = pdt.start_of('day').subtract(months=1).replace(day=mso)
416
+ begin = pdt.start_of("day").subtract(months=1).replace(day=mso)
395
417
 
396
418
  # End is mso of next month from `begin`
397
419
  end = begin.add(months=1)
@@ -404,8 +426,9 @@ def month_span(mso: int) -> Callable[[DateTimeLike], Tuple[DateTime, DateTime]]:
404
426
  return find_dates
405
427
 
406
428
 
407
- def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callable[
408
- [Any], Tuple[DateTime, DateTime]]:
429
+ def arb_span(
430
+ dates: Sequence[str | DateTimeOrDateLike], naive: bool = False
431
+ ) -> Callable[[Any], Tuple[DateTime, DateTime]]:
409
432
  """
410
433
  Parses two given dates and returns a callable function that provides the date range
411
434
  as a tuple of datetime objects. The function ensures the date range is valid and
@@ -434,22 +457,33 @@ def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callab
434
457
  parsed = pendulum.parse(date)
435
458
 
436
459
  # If it's a date-only string (no time/timezone info), treat as naive
437
- if 'T' not in date and ' ' not in date:
460
+ if "T" not in date and " " not in date:
438
461
  parsed = parsed.naive()
439
462
 
440
- parsed_dates.append(parsed.start_of('day'))
463
+ parsed_dates.append(parsed.start_of("day"))
441
464
  else:
442
- # It's already a datetime
443
- if date.tzinfo is None:
444
- # Input is naive, keep it naive using pendulum.naive()
445
- parsed = pendulum.naive(date.year, date.month, date.day,
446
- date.hour, date.minute, date.second,
447
- date.microsecond)
465
+ # It's already a datetime-like object
466
+ if isinstance(date, datetime):
467
+ # datetime objects have tzinfo, hour, minute, etc.
468
+ if date.tzinfo is None:
469
+ # Input is naive, keep it naive using pendulum.naive()
470
+ parsed = pendulum.naive(
471
+ date.year,
472
+ date.month,
473
+ date.day,
474
+ date.hour,
475
+ date.minute,
476
+ date.second,
477
+ date.microsecond,
478
+ )
479
+ else:
480
+ # Input is timezone-aware, preserve it
481
+ parsed = pendulum.instance(date)
448
482
  else:
449
- # Input is timezone-aware, preserve it
450
- parsed = pendulum.instance(date)
483
+ # date objects (no time component, no tzinfo) - treat as naive
484
+ parsed = pendulum.naive(date.year, date.month, date.day)
451
485
 
452
- parsed_dates.append(parsed.start_of('day'))
486
+ parsed_dates.append(parsed.start_of("day"))
453
487
 
454
488
  a, b = parsed_dates
455
489
 
@@ -479,8 +513,8 @@ def arb_span(dates: Sequence[str | DateTimeLike], naive: bool = False) -> Callab
479
513
 
480
514
 
481
515
  def unroll_span_func(
482
- f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
483
- cover: DateTimeLike | None = None,
516
+ f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
517
+ cover: DateTimeLike | None = None,
484
518
  ) -> Tuple[List[DateTime], List[int], List[str], DateTime, DateTime]:
485
519
  """
486
520
  Generate keys for a date range based on a provided function.
@@ -505,13 +539,21 @@ def unroll_span_func(
505
539
  DateUtilsError: If the date range cannot be processed due to invalid dates or formatting.
506
540
  """
507
541
  cover = pendulum.now() if cover is None else cover
508
- naive = cover.tzinfo is None
542
+
509
543
  try:
510
544
  start, end = f(cover)
511
545
  start = pendulum.instance(start)
512
- start = start.naive() if naive else start
513
546
  end = pendulum.instance(end)
514
- end = end.naive() if naive else end
547
+
548
+ # Determine naiveness from the dates returned by f, not from cover
549
+ naive = start.tzinfo is None
550
+ if naive:
551
+ start = start.naive()
552
+ end = end.naive()
553
+
554
+ # Get actual current time for filtering future dates (always "now", not cover)
555
+ current_date_sentinel = pendulum.now().naive() if naive else pendulum.now()
556
+
515
557
  except Exception as e:
516
558
  raise DateUtilsError(f"Function f failed to compute date range: {str(e)}")
517
559
 
@@ -524,9 +566,11 @@ def unroll_span_func(
524
566
  ord_days = []
525
567
  iso_date_strings = []
526
568
  for dt in interval.range(unit="days"):
527
- date_range.append(dt)
528
- ord_days.append(dt.day_of_year)
529
- iso_date_strings.append(dt.format('YYYY-MM-DD'))
569
+ # Filter: only include dates up to today (not future dates)
570
+ if dt <= current_date_sentinel and dt < end:
571
+ date_range.append(dt)
572
+ ord_days.append(dt.day_of_year)
573
+ iso_date_strings.append(dt.format("YYYY-MM-DD"))
530
574
 
531
575
  return date_range, ord_days, iso_date_strings, start, end
532
576
 
@@ -535,10 +579,10 @@ def unroll_span_func(
535
579
 
536
580
 
537
581
  def keys_for_span_func(
538
- f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
539
- cover: DateTimeLike | None = None,
540
- key_in_format: str = "ODIN_{}",
541
- key_out_format: str = "ODOUT_{}",
582
+ f: Callable[[DateTimeLike], Tuple[DateTime, DateTime]],
583
+ cover: DateTimeLike | None = None,
584
+ key_in_format: str = "ODIN_{}",
585
+ key_out_format: str = "ODOUT_{}",
542
586
  ):
543
587
  """
544
588
  Generate keys for a date range based on a provided function.
@@ -558,14 +602,17 @@ def keys_for_span_func(
558
602
  Raises:
559
603
  DateUtilsError: If the date range cannot be processed.
560
604
  """
561
- date_range, ord_days, iso_date_strings, start, end = unroll_span_func(f=f, cover=cover)
605
+ date_range, ord_days, iso_date_strings, start, end = unroll_span_func(
606
+ f=f, cover=cover
607
+ )
562
608
  keys_in = [key_in_format.format(d) for d in ord_days]
563
609
  keys_out = [key_out_format.format(d) for d in ord_days]
564
610
  return keys_in, keys_out, start, end
565
611
 
566
612
 
567
- def calendar_month_start_end(date_in_month: DateTimeLike | None = None) -> Tuple[
568
- DateTime, DateTime]:
613
+ def calendar_month_start_end(
614
+ date_in_month: DateTimeLike | None = None,
615
+ ) -> Tuple[DateTime, DateTime]:
569
616
  naive = date_in_month.tzinfo is None
570
617
 
571
618
  if date_in_month is None:
@@ -573,7 +620,7 @@ def calendar_month_start_end(date_in_month: DateTimeLike | None = None) -> Tuple
573
620
 
574
621
  pdt = pendulum.instance(date_in_month)
575
622
 
576
- start = pdt.start_of('month')
623
+ start = pdt.start_of("month")
577
624
  end = start.add(months=1)
578
625
 
579
626
  if naive:
@@ -592,8 +639,9 @@ def unix_timestamp() -> int:
592
639
  return round(time.time())
593
640
 
594
641
 
595
- def sentinel_date_and_ordinal_to_date(sentinel_date: DateTimeLike | date,
596
- ordinal: int | float | str) -> date:
642
+ def sentinel_date_and_ordinal_to_date(
643
+ sentinel_date: DateTimeLike | date, ordinal: int | float | str
644
+ ) -> date:
597
645
  """Convert sentinel date and ordinal day to actual date"""
598
646
  year = sentinel_date.year
599
647
  int_ordinal = int(ordinal)
@@ -610,7 +658,7 @@ def sentinel_date_and_ordinal_to_date(sentinel_date: DateTimeLike | date,
610
658
  def seconds_to_end_of_month() -> int:
611
659
  """Calculate seconds remaining until the end of the current month"""
612
660
  now = pendulum.now(UTC_TZ)
613
- end_of_month = now.end_of('month')
661
+ end_of_month = now.end_of("month")
614
662
  return int((end_of_month - now).total_seconds())
615
663
 
616
664
 
@@ -639,24 +687,42 @@ def get_notice_end_date(given_date: DateTimeLike | date | Date | None = None) ->
639
687
  given_date = given_date.date()
640
688
  elif not isinstance(given_date, date):
641
689
  raise ValueError(
642
- "Given date must be a datetime.date or datetime.datetime object")
690
+ "Given date must be a datetime.date or datetime.datetime object"
691
+ )
643
692
 
644
693
  pdt = pendulum.instance(given_date)
645
694
  if given_date.day <= 15:
646
695
  # End of current month
647
- end_date = pdt.add(months=1).start_of('month')
696
+ end_date = pdt.add(months=1).start_of("month")
648
697
  else:
649
698
  # End of next month
650
- end_date = pdt.add(months=2).start_of('month')
699
+ end_date = pdt.add(months=2).start_of("month")
651
700
 
652
701
  if isinstance(end_date, DateTime):
653
702
  return end_date.date()
654
703
  return end_date
655
704
 
656
705
 
657
- def dt_to_za_time_string(v: DateTimeLike) -> str:
658
- """Convert DateTime to South Africa time string"""
659
- # Convert to Pendulum
706
+ def dt_to_za_time_string(v: DateTimeOrDateLike | float | None) -> str | None:
707
+ """Convert DateTime or Date to South Africa time string.
708
+
709
+ For datetime objects: returns "YYYY-MM-DD HH:MM:SS" in ZA timezone.
710
+ For date objects: returns "YYYY-MM-DD" (no time component).
711
+ For None: returns None.
712
+ For float values (NaN): returns None.
713
+ """
714
+ if v is None:
715
+ return None
716
+
717
+ # Handle float values (NaN from pandas NULL database values)
718
+ if isinstance(v, float):
719
+ return None
720
+
721
+ # Handle date objects (no time component)
722
+ if isinstance(v, date) and not isinstance(v, datetime):
723
+ return v.strftime("%Y-%m-%d")
724
+
725
+ # Convert datetime to Pendulum
660
726
  naive = v.tzinfo is None
661
727
  if naive:
662
728
  pdt = pendulum.instance(v, tz=ZA_TZ)
@@ -669,10 +735,7 @@ def months_ago_selection() -> List[Tuple[int, str]]:
669
735
  """Generate list of (index, "Month-Year") tuples for last 12 months"""
670
736
  today = pendulum.today()
671
737
 
672
- return [
673
- (i, today.subtract(months=i).strftime("%B-%Y"))
674
- for i in range(12)
675
- ]
738
+ return [(i, today.subtract(months=i).strftime("%B-%Y")) for i in range(12)]
676
739
 
677
740
 
678
741
  def is_aware(dt: DateTimeLike | Date | date) -> bool:
@@ -680,7 +743,9 @@ def is_aware(dt: DateTimeLike | Date | date) -> bool:
680
743
  return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
681
744
 
682
745
 
683
- def make_aware(dt: DateTimeLike | date | Date | None, tz: str = None) -> DateTime | Date | None:
746
+ def make_aware(
747
+ dt: DateTimeLike | date | Date | None, tz: str = None
748
+ ) -> DateTime | Date | None:
684
749
  """
685
750
  Convert a naive DateTime to a timezone-aware DateTime using Pendulum.
686
751
 
@@ -715,7 +780,9 @@ def make_aware(dt: DateTimeLike | date | Date | None, tz: str = None) -> DateTim
715
780
  raise DateUtilsError(f"Invalid timezone: {tz}") from e
716
781
 
717
782
 
718
- def unaware_to_utc_aware(dt: DateTimeLike | date | Date | None) -> DateTime | Date | None:
783
+ def unaware_to_utc_aware(
784
+ dt: DateTimeLike | date | Date | None,
785
+ ) -> DateTime | Date | None:
719
786
  """Convert naive DateTime to UTC-aware DateTime using Pendulum."""
720
787
  if not isinstance(dt, (datetime, type(None))):
721
788
  raise TypeError(f"Expected datetime or None, got {type(dt)}")
none_shall_parse/imeis.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  LUHN_DOUBLES = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
3
2
 
4
3
 
none_shall_parse/lists.py CHANGED
@@ -1,4 +1,3 @@
1
- import collections
2
1
  from typing import Iterable, Generator, Any, List
3
2
 
4
3
  from .types import T
@@ -29,20 +28,20 @@ def flatten(some_list: Iterable) -> Generator[Any, None, None]:
29
28
  def safe_list_get(lst: List[T], idx: int, default: T = None) -> T:
30
29
  """
31
30
  Retrieve an element from a list by its index or return a default value if the index
32
- is out of range. This function ensures no IndexError is raised during the retrieval
33
- process by providing a fallback value.
31
+ is out of range or the input is not subscriptable. This function ensures safe retrieval
32
+ by providing a fallback value when access fails.
34
33
 
35
- :param lst: The list from which the element is to be retrieved.
34
+ :param lst: The list from which the element is to be retrieved (may be None).
36
35
  :type lst: List[T]
37
36
  :param idx: The index of the element to retrieve from the list.
38
37
  :type idx: int
39
- :param default: The fallback value to be returned if the index is out of range.
38
+ :param default: The fallback value to be returned if retrieval fails.
40
39
  :type default: T
41
40
  :return: The element at the specified index, or the default value
42
- if the index is out of range.
41
+ if the index is out of range or lst is None/not subscriptable.
43
42
  :rtype: T
44
43
  """
45
44
  try:
46
45
  return lst[idx]
47
- except IndexError:
46
+ except (IndexError, TypeError):
48
47
  return default
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: none-shall-parse
3
- Version: 0.4.5
3
+ Version: 0.6.3
4
4
  Summary: Trinity Shared Python utilities.
5
5
  Author: Andries Niemandt, Jan Badenhorst
6
6
  Author-email: Andries Niemandt <andries.niemandt@trintel.co.za>, Jan Badenhorst <jan@trintel.co.za>
@@ -0,0 +1,10 @@
1
+ none_shall_parse/__init__.py,sha256=VQrtFMNFZGpZAWhbSnVuJIhGamkb2WUzXOzleUQL274,4051
2
+ none_shall_parse/dates.py,sha256=rT_1gE5QeS9YxOQJET0qWesEZ5u2qS5ATN0SnM6LPXY,28459
3
+ none_shall_parse/imeis.py,sha256=1fFRdFryOAg2djbZjtDmJb8aRdKZ-vhqbJUVy-AFaEA,6769
4
+ none_shall_parse/lists.py,sha256=AZ5UbPYcLJQkLTP5X6mD72c2gcJ6helgfzBAVRWft2U,1746
5
+ none_shall_parse/parse.py,sha256=77bXZAtwFksRwuZ9Ax0lPxEjFpyjkQBqRa5mBc1WkF4,6843
6
+ none_shall_parse/strings.py,sha256=F7491CJAHJjL7vdEGwoH_4S6PjaovYUS_yzVGJ-bYIE,8463
7
+ none_shall_parse/types.py,sha256=WAgILMtW2_fm9MBpUuQvq68yXYBNd3rnSoQk70ibOd4,1320
8
+ none_shall_parse-0.6.3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
9
+ none_shall_parse-0.6.3.dist-info/METADATA,sha256=df4CPiPG9v1IURs8NVlUpqbIiWc0UPbW3R-lC97yDxY,1701
10
+ none_shall_parse-0.6.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.13
2
+ Generator: uv 0.8.24
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,10 +0,0 @@
1
- none_shall_parse/__init__.py,sha256=CTi08pRGuTem3KhOAKP_hO1oBfMzEHZymDhn4jIH0tU,3833
2
- none_shall_parse/dates.py,sha256=DRgVVAFUOHj42nIiXuMYrwOtCEsf6qz1eU0y8eP6q9o,26661
3
- none_shall_parse/imeis.py,sha256=20pONoUhLKomZxAJegqSSjG72hZjYs60r8IcaRt-15M,6770
4
- none_shall_parse/lists.py,sha256=PKtYD8H1Z7MqbJQ5eFpsK_jgexLwiratMJiYeRMiOwc,1701
5
- none_shall_parse/parse.py,sha256=77bXZAtwFksRwuZ9Ax0lPxEjFpyjkQBqRa5mBc1WkF4,6843
6
- none_shall_parse/strings.py,sha256=F7491CJAHJjL7vdEGwoH_4S6PjaovYUS_yzVGJ-bYIE,8463
7
- none_shall_parse/types.py,sha256=WAgILMtW2_fm9MBpUuQvq68yXYBNd3rnSoQk70ibOd4,1320
8
- none_shall_parse-0.4.5.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
9
- none_shall_parse-0.4.5.dist-info/METADATA,sha256=eTXaYu1c9TKU4CnZmqj6U3XWqQXnwNxsB7ejV05IFQA,1701
10
- none_shall_parse-0.4.5.dist-info/RECORD,,