none-shall-parse 0.4.0__tar.gz → 0.4.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: none-shall-parse
3
- Version: 0.4.0
3
+ Version: 0.4.1
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>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "none-shall-parse"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Trinity Shared Python utilities."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -200,7 +200,7 @@ def epoch_to_datetime(epoch_int: int | float, naive: bool = False,
200
200
  return pendulum.from_timestamp(int(epoch_int), tz=tz)
201
201
 
202
202
 
203
- def epoch_to_utc_datetime(epoch_int: int | float) -> datetime:
203
+ def epoch_to_utc_datetime(epoch_int: int | float | str) -> datetime:
204
204
  """
205
205
  Converts an epoch timestamp to a UTC datetime object.
206
206
 
@@ -216,7 +216,7 @@ def epoch_to_utc_datetime(epoch_int: int | float) -> datetime:
216
216
  return pendulum.from_timestamp(int(epoch_int), tz=UTC_TZ)
217
217
 
218
218
 
219
- def is_office_hours_in_timezone(epoch_int: int | float, tz: str | None = None) -> bool:
219
+ def is_office_hours_in_timezone(epoch_int: int | float | str, tz: str | None = None) -> bool:
220
220
  """
221
221
  Determines if a given epoch timestamp falls within office hours for a
222
222
  specific timezone.
@@ -259,23 +259,18 @@ def get_datetime_from_ordinal_and_sentinel(
259
259
  # Check timezone awareness
260
260
  naive = sentinel.tzinfo is None
261
261
 
262
- # Convert to Pendulum for easier manipulation
263
262
  sentinel_pdt = pendulum.instance(sentinel)
264
263
  sentinel_doy = sentinel_pdt.day_of_year
265
264
  sentinel_year = sentinel_pdt.year
266
265
 
267
- # Create start of years with CORRECT timezone handling
268
266
  if naive:
269
- # Use pendulum.naive() to create naive datetimes
270
267
  this_year = pendulum.naive(sentinel_year, 1, 1)
271
268
  last_year = pendulum.naive(sentinel_year - 1, 1, 1)
272
269
  else:
273
- # Create in the same timezone as sentinel (not UTC!)
274
270
  this_year = pendulum.datetime(sentinel_year, 1, 1, tz=sentinel_pdt.timezone)
275
271
  last_year = pendulum.datetime(sentinel_year - 1, 1, 1, tz=sentinel_pdt.timezone)
276
272
 
277
273
  def f(ordinal: int) -> datetime:
278
- # Choose year based on whether ordinal is before or after sentinel's day of year
279
274
  dt = this_year if ordinal <= sentinel_doy else last_year
280
275
 
281
276
  # Handle leap year edge case
@@ -285,10 +280,7 @@ def get_datetime_from_ordinal_and_sentinel(
285
280
  else:
286
281
  return pendulum.datetime(1970, 1, 1, tz=sentinel_pdt.timezone)
287
282
 
288
- # Add (ordinal - 1) days to start of year
289
283
  result = dt.add(days=ordinal - 1)
290
-
291
- # Return as-is (Pendulum datetime preserves timezone awareness)
292
284
  return result
293
285
 
294
286
  return f
@@ -412,26 +404,25 @@ def month_span(mso: int) -> Callable[[datetime], Tuple[datetime, datetime]]:
412
404
 
413
405
 
414
406
  def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
415
- [], Tuple[datetime, datetime]]:
407
+ [Any], Tuple[datetime, datetime]]:
416
408
  """
417
409
  Parses two given dates and returns a callable function that provides the date range
418
410
  as a tuple of datetime objects. The function ensures the date range is valid and
419
411
  always returns the earlier date as the start and the later date as the end.
420
412
 
421
413
  Parameters:
422
- dates (Sequence[str | datetime]): A sequence containing exactly two dates where each
423
- date is either a string or a datetime object.
414
+ dates (Sequence[str | datetime]): A sequence containing exactly two dates where
415
+ each date is either a string or a datetime object.
424
416
  naive (bool): Optional flag. If True, the returned datetime objects will not
425
- have timezone information (naive datetime). Defaults to False.
417
+ have timezone information (naive datetime). Defaults to False.
426
418
 
427
419
  Returns:
428
- Callable[[], Tuple[datetime, datetime]]: A function that, when invoked, returns a
429
- tuple of datetime objects (start, end)
430
- representing the date range.
420
+ Callable[[Any], Tuple[datetime, datetime]]: A function that, when invoked,
421
+ returns a tuple of datetime objects (start, end) representing the date range.
431
422
 
432
423
  Raises:
433
424
  DateUtilsError: If the provided dates are invalid, identical, or there's an error
434
- during parsing.
425
+ during parsing.
435
426
  """
436
427
  try:
437
428
  parsed_dates = []
@@ -475,7 +466,7 @@ def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
475
466
  except Exception as ex:
476
467
  raise DateUtilsError(f"Error parsing dates: {ex}")
477
468
 
478
- def find_dates() -> Tuple[datetime, datetime]:
469
+ def find_dates(*args) -> Tuple[datetime, datetime]:
479
470
  """
480
471
  :return: tuple of datetime objects (start, end)
481
472
  """
@@ -486,6 +477,94 @@ def arb_span(dates: Sequence[str | datetime], naive: bool = False) -> Callable[
486
477
  return find_dates
487
478
 
488
479
 
480
+ def unroll_span_func(
481
+ f: Callable[[datetime], Tuple[datetime, datetime]],
482
+ cover: datetime | None = None,
483
+ ) -> Tuple[List[datetime], List[int], List[str], datetime, datetime]:
484
+ """
485
+ Generate keys for a date range based on a provided function.
486
+
487
+ This function computes a date range using the provided function `f`, which takes a base date and returns
488
+ start and end dates. It generates input and output keys for each day in the range based on ordinal days
489
+ and optionally returns only ordinal day integers.
490
+
491
+ Args:
492
+ f: Function that takes a base date and returns a tuple of start and end dates.
493
+ cover: Base date for computing the range. Defaults to the current date if None.
494
+
495
+ Returns:
496
+ A tuple containing:
497
+ - List of datetime objects.
498
+ - List of ordinal day integers.
499
+ - Start date of the range (as datetime).
500
+ - End date of the range (as datetime).
501
+ If ord_ints_only is True, returns (ordinal_days, start, end, iso_dates).
502
+
503
+ Raises:
504
+ DateUtilsError: If the date range cannot be processed due to invalid dates or formatting.
505
+ """
506
+ cover = pendulum.now() if cover is None else cover
507
+ naive = cover.tzinfo is None
508
+ try:
509
+ start, end = f(cover)
510
+ except Exception as e:
511
+ raise DateUtilsError(f"Function f failed to compute date range: {str(e)}")
512
+
513
+ # Make sure we can use pendulum with these dates
514
+ start = pendulum.instance(start) if isinstance(start, datetime) else start
515
+ start = start.naive() if naive else start
516
+ end = pendulum.instance(end) if isinstance(end, datetime) else end
517
+ end = end.naive() if naive else end
518
+
519
+ try:
520
+ # Generate date range using pendulum.interval
521
+ # The absolute kwarg ensures that we do not have to care about dates passed
522
+ # in the wrong order. We will always range from start to end, inclusive.
523
+ interval = pendulum.interval(start, end.subtract(days=1), absolute=True)
524
+ date_range = []
525
+ ord_days = []
526
+ iso_date_strings = []
527
+ for dt in interval.range(unit="days"):
528
+ date_range.append(dt)
529
+ ord_days.append(dt.day_of_year)
530
+ iso_date_strings.append(dt.format('YYYY-MM-DD'))
531
+
532
+ return date_range, ord_days, iso_date_strings, start, end
533
+
534
+ except (TypeError, ValueError) as e:
535
+ raise DateUtilsError(f"Error processing date range: {str(e)}")
536
+
537
+
538
+ def keys_for_span_func(
539
+ f: Callable[[datetime], Tuple[datetime, datetime]],
540
+ cover: datetime | None = None,
541
+ key_in_format: str = "ODIN_{}",
542
+ key_out_format: str = "ODOUT_{}",
543
+ ):
544
+ """
545
+ Generate keys for a date range based on a provided function.
546
+
547
+ Args:
548
+ f: Function that takes a base date and returns a tuple of start and end dates.
549
+ cover: Base date for computing the range. Defaults to the current date if None.
550
+ key_in_format: Format string for input keys. Defaults to "ODIN_{}".
551
+ key_out_format: Format string for output keys, Defaults to "ODOUT_{}"
552
+
553
+ Returns:
554
+ - List of input keys (empty if key_in_format is None).
555
+ - List of output keys (empty if key_out_format is None).
556
+ - Start date of the range (as datetime).
557
+ - End date of the range (as datetime).
558
+
559
+ Raises:
560
+ DateUtilsError: If the date range cannot be processed.
561
+ """
562
+ date_range, ord_days, iso_date_strings, start, end = unroll_span_func(f=f, cover=cover)
563
+ keys_in = [key_in_format.format(d) for d in ord_days]
564
+ keys_out = [key_out_format.format(d) for d in ord_days]
565
+ return keys_in, keys_out, start, end
566
+
567
+
489
568
  def calendar_month_start_end(date_in_month: datetime | None = None) -> Tuple[
490
569
  datetime, datetime]:
491
570
  naive = date_in_month.tzinfo is None
@@ -495,7 +574,6 @@ def calendar_month_start_end(date_in_month: datetime | None = None) -> Tuple[
495
574
 
496
575
  pdt = pendulum.instance(date_in_month)
497
576
 
498
- # One-liner for both values
499
577
  start = pdt.start_of('month')
500
578
  end = start.add(months=1)
501
579
 
@@ -515,8 +593,8 @@ def unix_timestamp() -> int:
515
593
  return round(time.time())
516
594
 
517
595
 
518
- def sentinel_date_and_ordinal_to_date(sentinel_date: datetime,
519
- ordinal: int | float) -> date:
596
+ def sentinel_date_and_ordinal_to_date(sentinel_date: datetime | date,
597
+ ordinal: int | float | str) -> date:
520
598
  """Convert sentinel date and ordinal day to actual date"""
521
599
  year = sentinel_date.year
522
600
  int_ordinal = int(ordinal)