dycw-utilities 0.165.1__py3-none-any.whl → 0.165.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.165.1
3
+ Version: 0.165.3
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=jXIaJyG5YtjB8gWKCypYzBJ3Xi_oFaGo1ydJol5wBR0,60
1
+ utilities/__init__.py,sha256=dGyOX1Z3m0mhIPCR1cqAXlybAWUxvEasnTNSAfT9Fh8,60
2
2
  utilities/aeventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
3
3
  utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
4
4
  utilities/asyncio.py,sha256=PUedzQ5deqlSECQ33sam9cRzI9TnygHz3FdOqWJWPTM,15288
@@ -45,7 +45,7 @@ utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
45
45
  utilities/pathlib.py,sha256=qGuU8XPmdgGpy8tOMUgelfXx3kxI8h9IaV3TI_06QGE,8428
46
46
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
47
47
  utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
48
- utilities/polars.py,sha256=X-TaklmHmyJazckJIRNMwwvgSp3q6EGGb0mVnwHEDrI,80811
48
+ utilities/polars.py,sha256=6_k8cU7LePQ942Qfqe5l_tR8ND0M0bLlpnaMvDbRCM8,81754
49
49
  utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
50
50
  utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
51
51
  utilities/pottery.py,sha256=ggMN72Y7wx7Js8VN6eyNyodpm8TIYqZHGghkDPXIVWk,3949
@@ -76,7 +76,7 @@ utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
76
76
  utilities/traceback.py,sha256=b1nSvlyrGmI1MyZLkkoLVET3DQBSGt9qqIlAAQbyjEw,9629
77
77
  utilities/typed_settings.py,sha256=SFWqS3lAzV7IfNRwqFcTk0YynTcQ7BmrcW2mr_KUnos,4466
78
78
  utilities/types.py,sha256=oeH-hEC3-67Eja4nLz-Nj9WvK6Z9-3T1zobO_XJpuVg,18735
79
- utilities/typing.py,sha256=vPqxHE2G5_dbDEgWAyAaTSDbUQE0pmihIj0vdXogbRU,24185
79
+ utilities/typing.py,sha256=QYoCIc71e_u5W-kBeiNzrD-GXxpLtMOGc9PqgZjMXeE,25274
80
80
  utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
81
81
  utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
82
82
  utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
@@ -88,8 +88,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
88
88
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
89
89
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
90
90
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
91
- dycw_utilities-0.165.1.dist-info/METADATA,sha256=Lo1wurJzugPUwkNpIOKHWWc7zSUL4xWGCSQ7FCWI0ZA,1696
92
- dycw_utilities-0.165.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.165.1.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
- dycw_utilities-0.165.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
- dycw_utilities-0.165.1.dist-info/RECORD,,
91
+ dycw_utilities-0.165.3.dist-info/METADATA,sha256=pkTtRCmg_YK5SUclEGBHKWThH6nFfAdR3vn_EJZjGO8,1696
92
+ dycw_utilities-0.165.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.165.3.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
+ dycw_utilities-0.165.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.165.3.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.165.1"
3
+ __version__ = "0.165.3"
utilities/polars.py CHANGED
@@ -105,6 +105,7 @@ from utilities.whenever import (
105
105
  from utilities.zoneinfo import UTC, to_time_zone_name
106
106
 
107
107
  if TYPE_CHECKING:
108
+ import datetime as dt
108
109
  from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
109
110
  from collections.abc import Set as AbstractSet
110
111
 
@@ -2460,6 +2461,34 @@ class RoundToFloatError(Exception):
2460
2461
  ##
2461
2462
 
2462
2463
 
2464
+ def search_period(
2465
+ series: Series,
2466
+ date_time: ZonedDateTime,
2467
+ /,
2468
+ *,
2469
+ start_or_end: Literal["start", "end"] = "end",
2470
+ ) -> int | None:
2471
+ """Search a series of periods for the one containing a given date-time."""
2472
+ start, end = [series.struct[k] for k in ["start", "end"]]
2473
+ py_date_time = date_time.py_datetime()
2474
+ match start_or_end:
2475
+ case "start":
2476
+ index = end.search_sorted(py_date_time, side="right")
2477
+ if index >= len(series):
2478
+ return None
2479
+ item: dt.datetime = series[index]["start"]
2480
+ return index if py_date_time >= item else None
2481
+ case "end":
2482
+ index = end.search_sorted(py_date_time, side="left")
2483
+ if index >= len(series):
2484
+ return None
2485
+ item: dt.datetime = series[index]["start"]
2486
+ return index if py_date_time > item else None
2487
+
2488
+
2489
+ ##
2490
+
2491
+
2463
2492
  def select_exact(
2464
2493
  df: DataFrame, /, *columns: IntoExprColumn, drop: MaybeIterable[str] | None = None
2465
2494
  ) -> DataFrame:
@@ -2750,6 +2779,7 @@ __all__ = [
2750
2779
  "read_series",
2751
2780
  "replace_time_zone",
2752
2781
  "round_to_float",
2782
+ "search_period",
2753
2783
  "select_exact",
2754
2784
  "serialize_dataframe",
2755
2785
  "set_first_row_as_columns",
utilities/typing.py CHANGED
@@ -6,12 +6,14 @@ from dataclasses import dataclass, is_dataclass
6
6
  from functools import partial
7
7
  from itertools import chain
8
8
  from pathlib import Path
9
+ from re import search
9
10
  from types import NoneType, UnionType
10
11
  from typing import (
11
12
  Any,
12
13
  ForwardRef,
13
14
  Literal,
14
15
  NamedTuple,
16
+ NotRequired,
15
17
  Optional, # pyright: ignore[reportDeprecated]
16
18
  TypeAliasType,
17
19
  TypeGuard,
@@ -56,6 +58,18 @@ def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...
56
58
  ##
57
59
 
58
60
 
61
+ def get_forward_ref_args(obj: Any, /) -> Mapping[str, str]:
62
+ """Get the forward args."""
63
+ return {
64
+ k: v.__forward_arg__
65
+ for k, v in obj.__annotations__.items()
66
+ if isinstance(v, ForwardRef)
67
+ }
68
+
69
+
70
+ ##
71
+
72
+
59
73
  def get_literal_elements(obj: Any, /) -> list[Any]:
60
74
  """Get the elements of a literal annotation."""
61
75
  return _get_literal_elements_inner(obj)
@@ -151,21 +165,20 @@ def get_type_hints(
151
165
  globalns_use = globals() | ({} if globalns is None else dict(globalns))
152
166
  localns_use = {} if localns is None else dict(localns)
153
167
  result: dict[str, Any] = obj.__annotations__
154
- result = {
155
- k: v.__forward_arg__ if isinstance(v, ForwardRef) else v
156
- for k, v in result.items()
157
- }
168
+ result = result | dict(get_forward_ref_args(obj))
158
169
  try:
159
- hints = _get_type_hints(obj, globalns=globalns_use, localns=localns_use)
170
+ hints = _get_type_hints(
171
+ obj, globalns=globalns_use, localns=localns_use, include_extras=True
172
+ )
160
173
  except NameError as error:
161
174
  if warn_name_errors:
162
175
  warn(f"Error getting type hints for {obj!r}; {error}", stacklevel=2)
163
176
  else:
164
- result.update({
177
+ result = result | {
165
178
  key: value
166
179
  for key, value in hints.items()
167
180
  if (key not in result) or isinstance(result[key], str)
168
- })
181
+ }
169
182
  return result
170
183
 
171
184
 
@@ -393,17 +406,21 @@ def _is_instance_typed_dict[T: _TypedDictMeta](
393
406
  hints = get_type_hints(
394
407
  type_, globalns=globalns, localns=localns, warn_name_errors=warn_name_errors
395
408
  )
396
- if not set(obj).issuperset(hints):
409
+ optional = {
410
+ k for k, v in type_.__annotations__.items() if is_not_required_annotation(v)
411
+ }
412
+ required = {k: v for k, v in hints.items() if k not in optional}
413
+ if not set(obj).issuperset(required):
397
414
  return False
398
415
  return all(
399
416
  is_instance_gen(
400
417
  obj[k],
401
- hints[k],
418
+ required[k],
402
419
  globalns=globalns,
403
420
  localns=localns,
404
421
  warn_name_errors=warn_name_errors,
405
422
  )
406
- for k in hints
423
+ for k in required
407
424
  )
408
425
 
409
426
 
@@ -577,6 +594,30 @@ def _is_namedtuple_core(obj: Any, /) -> bool:
577
594
  ##
578
595
 
579
596
 
597
+ def is_not_required_annotation(obj: Any) -> bool:
598
+ """Check if an annotation is not required."""
599
+ if is_not_required_type(obj):
600
+ return True
601
+ match obj:
602
+ case str() as text:
603
+ return bool(search("NotRequired", text))
604
+ case ForwardRef() as fr:
605
+ return is_not_required_annotation(fr.__forward_arg__)
606
+ case _:
607
+ return False
608
+
609
+
610
+ ##
611
+
612
+
613
+ def is_not_required_type(obj: Any, /) -> bool:
614
+ """Check if an object is a not-required type annotation."""
615
+ return (obj is NotRequired) or _is_annotation_of_type(obj, NotRequired)
616
+
617
+
618
+ ##
619
+
620
+
580
621
  def is_optional_type(obj: Any, /) -> bool:
581
622
  """Check if an object is an optional type annotation."""
582
623
  is_optional = _is_annotation_of_type(obj, Optional) # pyright: ignore[reportDeprecated]
@@ -889,6 +930,8 @@ __all__ = [
889
930
  "is_mapping_type",
890
931
  "is_namedtuple_class",
891
932
  "is_namedtuple_instance",
933
+ "is_not_required_annotation",
934
+ "is_not_required_type",
892
935
  "is_optional_type",
893
936
  "is_sequence_of",
894
937
  "is_sequence_of_tuple_or_str_mapping",