dycw-utilities 0.166.15__py3-none-any.whl → 0.166.17__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.166.15
3
+ Version: 0.166.17
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -18,13 +18,13 @@ Requires-Dist: pytest-cov<6.3,>=6.2.1; extra == 'test'
18
18
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
19
  Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
20
20
  Requires-Dist: pytest-randomly<3.17,>=3.16.0; extra == 'test'
21
- Requires-Dist: pytest-regressions<2.9,>=2.8.2; extra == 'test'
21
+ Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
22
22
  Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
23
23
  Requires-Dist: pytest-rerunfailures<16.1,>=16.0.1; extra == 'test'
24
24
  Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
25
25
  Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
26
26
  Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
27
- Requires-Dist: pytest<8.5,>=8.4.1; extra == 'test'
27
+ Requires-Dist: pytest<8.5,>=8.4.2; extra == 'test'
28
28
  Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
29
29
  Description-Content-Type: text/markdown
30
30
 
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=jOtUA_AbgiE81h3dMxZMFbVi1ROzPoQzJX8qlRlwxKs,61
1
+ utilities/__init__.py,sha256=zjtoeFc-ojZa5CJiIUK9ZCv9HTNHqODG_fmFppulLvA,61
2
2
  utilities/aeventkit.py,sha256=ddoleSwW9zdc2tjX5Ge0pMKtYwV_JMxhHYOxnWX2AGM,12609
3
3
  utilities/altair.py,sha256=nHdpWt8ZwdUwRQN970MvHd5bRWokNqzHcZQEdSHKRuE,9033
4
4
  utilities/asyncio.py,sha256=PUedzQ5deqlSECQ33sam9cRzI9TnygHz3FdOqWJWPTM,15288
@@ -46,15 +46,15 @@ utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
46
46
  utilities/pathlib.py,sha256=X6pHmfT3hnBGysdTr73uHsNaBEgKviZhk7aGWvEXDXo,8912
47
47
  utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
48
48
  utilities/platform.py,sha256=pTn7gw6N4T6LdKrf0virwarof_mze9WtoQlrGMzhGVI,2798
49
- utilities/polars.py,sha256=3C96YhfyKvmk1JiCvwTQ1mMUnsZKGlRDa6gFFlgc_uo,83235
49
+ utilities/polars.py,sha256=qsiYY9p_41fORGnc7HNkA4zhlsycK7sgD74xuigMDAc,87466
50
50
  utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
51
51
  utilities/postgres.py,sha256=ynCTTaF-bVEOSW-KEAR-dlLh_hYjeVVjm__-4pEU8Zk,12269
52
52
  utilities/pottery.py,sha256=ggMN72Y7wx7Js8VN6eyNyodpm8TIYqZHGghkDPXIVWk,3949
53
53
  utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
54
54
  utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
55
55
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- utilities/pydantic_settings.py,sha256=oUosQ7KExpXTC1PioIc4bju6C6Yj6gMZQLBpOKRL4Xc,2166
57
- utilities/pydantic_settings_sops.py,sha256=3RGZbRgfJjAxveMUNpdf7TNBtGuEBYZZ5_TkIxf1mNE,1194
56
+ utilities/pydantic_settings.py,sha256=xcm8TUa3Ok3kWuC7RojwKNimiO333iugbw1doqcKv7Q,3375
57
+ utilities/pydantic_settings_sops.py,sha256=LWI3NTQP8hSaGkoRbahyqaalF0HuBg7o63-p7boHgpQ,1119
58
58
  utilities/pyinstrument.py,sha256=hnXaL-4HE7wWBI5JKaPfYTpsrXe68YiuZKahHV0VJn8,841
59
59
  utilities/pytest.py,sha256=Pu8jmeNQq659uQmZsmFj6lb0sHMDsNN3fcd6M21J-ww,7723
60
60
  utilities/pytest_regressions.py,sha256=ocjHTtfOeiGfQAKIei8pKNd61sxN9dawrJJ9gPt2wzA,4097
@@ -91,8 +91,8 @@ utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
91
91
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
92
92
  utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
93
93
  utilities/pytest_plugins/pytest_regressions.py,sha256=9v8kAXDM2ycIXJBimoiF4EgrwbUvxTycFWJiGR_GHhM,1466
94
- dycw_utilities-0.166.15.dist-info/METADATA,sha256=tZarKfDKJCz0QAcuEaTEObHvD4g5MTe-eNSIe_DbDkM,1702
95
- dycw_utilities-0.166.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.166.15.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
97
- dycw_utilities-0.166.15.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
- dycw_utilities-0.166.15.dist-info/RECORD,,
94
+ dycw_utilities-0.166.17.dist-info/METADATA,sha256=ubc8X0TWb00B1HdApe5jrnnYdi2B8hpaCodXZwX1HoM,1702
95
+ dycw_utilities-0.166.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.166.17.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
97
+ dycw_utilities-0.166.17.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
98
+ dycw_utilities-0.166.17.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.166.15"
3
+ __version__ = "0.166.17"
utilities/polars.py CHANGED
@@ -70,6 +70,7 @@ from utilities.iterables import (
70
70
  check_supermapping,
71
71
  is_iterable_not_str,
72
72
  one,
73
+ resolve_include_and_exclude,
73
74
  )
74
75
  from utilities.json import write_formatted_json
75
76
  from utilities.math import (
@@ -1269,6 +1270,111 @@ def expr_to_series(expr: Expr, /) -> Series:
1269
1270
  ##
1270
1271
 
1271
1272
 
1273
+ @overload
1274
+ def filter_date(
1275
+ column: ExprLike = "datetime",
1276
+ /,
1277
+ *,
1278
+ time_zone: ZoneInfo | None = None,
1279
+ include: MaybeIterable[whenever.Date] | None = None,
1280
+ exclude: MaybeIterable[whenever.Date] | None = None,
1281
+ ) -> Expr: ...
1282
+ @overload
1283
+ def filter_date(
1284
+ column: Series,
1285
+ /,
1286
+ *,
1287
+ time_zone: ZoneInfo | None = None,
1288
+ include: MaybeIterable[whenever.Date] | None = None,
1289
+ exclude: MaybeIterable[whenever.Date] | None = None,
1290
+ ) -> Series: ...
1291
+ @overload
1292
+ def filter_date(
1293
+ column: IntoExprColumn = "datetime",
1294
+ /,
1295
+ *,
1296
+ time_zone: ZoneInfo | None = None,
1297
+ include: MaybeIterable[whenever.Date] | None = None,
1298
+ exclude: MaybeIterable[whenever.Date] | None = None,
1299
+ ) -> ExprOrSeries: ...
1300
+ def filter_date(
1301
+ column: IntoExprColumn = "datetime",
1302
+ /,
1303
+ *,
1304
+ time_zone: ZoneInfo | None = None,
1305
+ include: MaybeIterable[whenever.Date] | None = None,
1306
+ exclude: MaybeIterable[whenever.Date] | None = None,
1307
+ ) -> ExprOrSeries:
1308
+ """Compute the filter based on a set of dates."""
1309
+ column = ensure_expr_or_series(column)
1310
+ if time_zone is not None:
1311
+ column = column.dt.convert_time_zone(time_zone.key)
1312
+ keep = true_like(column)
1313
+ date = column.dt.date()
1314
+ include, exclude = resolve_include_and_exclude(include=include, exclude=exclude)
1315
+ if include is not None:
1316
+ keep &= date.is_in([d.py_date() for d in include])
1317
+ if exclude is not None:
1318
+ keep &= ~date.is_in([d.py_date() for d in exclude])
1319
+ return try_reify_expr(keep, column)
1320
+
1321
+
1322
+ @overload
1323
+ def filter_time(
1324
+ column: ExprLike = "datetime",
1325
+ /,
1326
+ *,
1327
+ time_zone: ZoneInfo | None = None,
1328
+ include: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1329
+ exclude: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1330
+ ) -> Expr: ...
1331
+ @overload
1332
+ def filter_time(
1333
+ column: Series,
1334
+ /,
1335
+ *,
1336
+ time_zone: ZoneInfo | None = None,
1337
+ include: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1338
+ exclude: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1339
+ ) -> Series: ...
1340
+ @overload
1341
+ def filter_time(
1342
+ column: IntoExprColumn = "datetime",
1343
+ /,
1344
+ *,
1345
+ time_zone: ZoneInfo | None = None,
1346
+ include: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1347
+ exclude: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1348
+ ) -> ExprOrSeries: ...
1349
+ def filter_time(
1350
+ column: IntoExprColumn = "datetime",
1351
+ /,
1352
+ *,
1353
+ time_zone: ZoneInfo | None = None,
1354
+ include: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1355
+ exclude: MaybeIterable[tuple[whenever.Time, whenever.Time]] | None = None,
1356
+ ) -> ExprOrSeries:
1357
+ """Compute the filter based on a set of times."""
1358
+ column = ensure_expr_or_series(column)
1359
+ if time_zone is not None:
1360
+ column = column.dt.convert_time_zone(time_zone.key)
1361
+ keep = true_like(column)
1362
+ time = column.dt.time()
1363
+ include, exclude = resolve_include_and_exclude(include=include, exclude=exclude)
1364
+ if include is not None:
1365
+ keep &= any_horizontal(
1366
+ time.is_between(s.py_time(), e.py_time()) for s, e in include
1367
+ )
1368
+ if exclude is not None:
1369
+ keep &= ~any_horizontal(
1370
+ time.is_between(s.py_time(), e.py_time()) for s, e in exclude
1371
+ )
1372
+ return try_reify_expr(keep, column)
1373
+
1374
+
1375
+ ##
1376
+
1377
+
1272
1378
  @overload
1273
1379
  def finite_ewm_mean(
1274
1380
  column: ExprLike,
@@ -2643,6 +2749,33 @@ def to_not_false(column: IntoExprColumn, /) -> ExprOrSeries:
2643
2749
  ##
2644
2750
 
2645
2751
 
2752
+ @overload
2753
+ def true_like(column: ExprLike, /) -> Expr: ...
2754
+ @overload
2755
+ def true_like(column: Series, /) -> Series: ...
2756
+ @overload
2757
+ def true_like(column: IntoExprColumn, /) -> ExprOrSeries: ...
2758
+ def true_like(column: IntoExprColumn, /) -> ExprOrSeries:
2759
+ """Compute a column of `True` values."""
2760
+ column = ensure_expr_or_series(column)
2761
+ return column.is_null() | column.is_not_null()
2762
+
2763
+
2764
+ @overload
2765
+ def false_like(column: ExprLike, /) -> Expr: ...
2766
+ @overload
2767
+ def false_like(column: Series, /) -> Series: ...
2768
+ @overload
2769
+ def false_like(column: IntoExprColumn, /) -> ExprOrSeries: ...
2770
+ def false_like(column: IntoExprColumn, /) -> ExprOrSeries:
2771
+ """Compute a column of `False` values."""
2772
+ column = ensure_expr_or_series(column)
2773
+ return column.is_null() & column.is_not_null()
2774
+
2775
+
2776
+ ##
2777
+
2778
+
2646
2779
  def try_reify_expr(
2647
2780
  expr: IntoExprColumn, /, *exprs: IntoExprColumn, **named_exprs: IntoExprColumn
2648
2781
  ) -> ExprOrSeries:
@@ -2798,6 +2931,9 @@ __all__ = [
2798
2931
  "ensure_expr_or_series",
2799
2932
  "ensure_expr_or_series_many",
2800
2933
  "expr_to_series",
2934
+ "false_like",
2935
+ "filter_date",
2936
+ "filter_time",
2801
2937
  "finite_ewm_mean",
2802
2938
  "first_true_horizontal",
2803
2939
  "get_data_type_or_series_time_zone",
@@ -2838,6 +2974,7 @@ __all__ = [
2838
2974
  "to_not_true",
2839
2975
  "to_true",
2840
2976
  "touch",
2977
+ "true_like",
2841
2978
  "try_reify_expr",
2842
2979
  "uniform",
2843
2980
  "unique_element",
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, ClassVar, override
3
+ from functools import reduce
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, ClassVar, assert_never, override
4
6
 
5
7
  from pydantic_settings import (
6
8
  BaseSettings,
@@ -10,22 +12,28 @@ from pydantic_settings import (
10
12
  TomlConfigSettingsSource,
11
13
  YamlConfigSettingsSource,
12
14
  )
15
+ from pydantic_settings.sources import DEFAULT_PATH
13
16
 
14
17
  from utilities.iterables import always_iterable
15
18
 
16
19
  if TYPE_CHECKING:
17
- from collections.abc import Iterator
20
+ from collections.abc import Iterator, Sequence
18
21
 
19
- from utilities.types import MaybeIterable, PathLike
22
+ from pydantic_settings.sources import PathType
23
+
24
+ from utilities.types import MaybeSequenceStr, PathLike
25
+
26
+
27
+ type PathLikeOrWithSection = PathLike | tuple[PathLike, MaybeSequenceStr]
20
28
 
21
29
 
22
30
  class CustomBaseSettings(BaseSettings):
23
31
  """Base settings for loading JSON files."""
24
32
 
25
33
  # paths
26
- json_files: ClassVar[MaybeIterable[PathLike]] = ()
27
- toml_files: ClassVar[MaybeIterable[PathLike]] = ()
28
- yaml_files: ClassVar[MaybeIterable[PathLike]] = ()
34
+ json_files: ClassVar[Sequence[PathLike]] = []
35
+ toml_files: ClassVar[Sequence[PathLikeOrWithSection]] = []
36
+ yaml_files: ClassVar[Sequence[PathLike]] = []
29
37
 
30
38
  # config
31
39
  model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
@@ -53,11 +61,19 @@ class CustomBaseSettings(BaseSettings):
53
61
  /,
54
62
  ) -> Iterator[PydanticBaseSettingsSource]:
55
63
  yield env_settings
56
- for file in always_iterable(cls.json_files):
64
+ for file in cls.json_files:
57
65
  yield JsonConfigSettingsSource(settings_cls, json_file=file)
58
- for file in always_iterable(cls.toml_files):
59
- yield TomlConfigSettingsSource(settings_cls, toml_file=file)
60
- for file in always_iterable(cls.yaml_files):
66
+ for path_or_pair in cls.toml_files:
67
+ match path_or_pair:
68
+ case Path() | str() as file:
69
+ yield TomlConfigSettingsSource(settings_cls, toml_file=file)
70
+ case Path() | str() as file, str() | list() | tuple() as section:
71
+ yield TomlConfigSectionSettingsSource(
72
+ settings_cls, toml_file=file, section=section
73
+ )
74
+ case never:
75
+ assert_never(never)
76
+ for file in cls.yaml_files:
61
77
  yield YamlConfigSettingsSource(settings_cls, yaml_file=file)
62
78
 
63
79
 
@@ -66,4 +82,25 @@ def load_settings[T: BaseSettings](cls: type[T], /) -> T:
66
82
  return cls()
67
83
 
68
84
 
69
- __all__ = ["CustomBaseSettings", "load_settings"]
85
+ class TomlConfigSectionSettingsSource(TomlConfigSettingsSource):
86
+ @override
87
+ def __init__(
88
+ self,
89
+ settings_cls: type[BaseSettings],
90
+ toml_file: PathType | None = DEFAULT_PATH,
91
+ *,
92
+ section: MaybeSequenceStr,
93
+ ) -> None:
94
+ super().__init__(settings_cls, toml_file=toml_file)
95
+ self.section = section
96
+
97
+ @override
98
+ def __call__(self) -> dict[str, Any]:
99
+ return reduce(
100
+ lambda acc, el: acc.get(el, {}),
101
+ always_iterable(self.section),
102
+ super().__call__(),
103
+ )
104
+
105
+
106
+ __all__ = ["CustomBaseSettings", "TomlConfigSectionSettingsSource", "load_settings"]
@@ -4,22 +4,21 @@ from typing import TYPE_CHECKING, ClassVar, override
4
4
 
5
5
  from pydantic_settings_sops import SOPSConfigSettingsSource
6
6
 
7
- from utilities.iterables import always_iterable
8
7
  from utilities.pydantic_settings import CustomBaseSettings
9
8
 
10
9
  if TYPE_CHECKING:
11
- from collections.abc import Iterator
10
+ from collections.abc import Iterator, Sequence
12
11
 
13
12
  from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
14
13
 
15
- from utilities.types import MaybeIterable, PathLike
14
+ from utilities.types import PathLike
16
15
 
17
16
 
18
17
  class SopsBaseSettings(CustomBaseSettings):
19
18
  """Base settings for loading secrets using `sops/age`."""
20
19
 
21
20
  # paths
22
- secret_files: ClassVar[MaybeIterable[PathLike]] = ()
21
+ secret_files: ClassVar[Sequence[PathLike]] = ()
23
22
 
24
23
  @classmethod
25
24
  @override
@@ -30,7 +29,7 @@ class SopsBaseSettings(CustomBaseSettings):
30
29
  /,
31
30
  ) -> Iterator[PydanticBaseSettingsSource]:
32
31
  yield from super()._yield_base_settings_sources(settings_cls, env_settings)
33
- for file in always_iterable(cls.secret_files):
32
+ for file in cls.secret_files:
34
33
  yield SOPSConfigSettingsSource(
35
34
  settings_cls, # pyright: ignore[reportArgumentType],
36
35
  json_file=file,