dycw-utilities 0.165.1__py3-none-any.whl → 0.165.2__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.2
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=ZnsWqCnQBd7LRuMGzf7ent48hxqCg33kMckVmWtmXfo,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
@@ -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.2.dist-info/METADATA,sha256=jw-a2WMCwJGXlvhvV3CVjpssOr7e-1QUnw-3i4WuM2k,1696
92
+ dycw_utilities-0.165.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.165.2.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
94
+ dycw_utilities-0.165.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.165.2.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.2"
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",