dycw-utilities 0.109.12__py3-none-any.whl → 0.109.14__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.
- {dycw_utilities-0.109.12.dist-info → dycw_utilities-0.109.14.dist-info}/METADATA +1 -1
- {dycw_utilities-0.109.12.dist-info → dycw_utilities-0.109.14.dist-info}/RECORD +9 -8
- utilities/__init__.py +1 -1
- utilities/altair.py +1 -2
- utilities/iterables.py +26 -6
- utilities/lightweight_charts.py +96 -0
- utilities/polars.py +1 -1
- {dycw_utilities-0.109.12.dist-info → dycw_utilities-0.109.14.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.109.12.dist-info → dycw_utilities-0.109.14.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,5 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
2
|
-
utilities/altair.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=9sl1XiZAUQGAGfCyJEHhTC7Kfmx8TtcYJHtVOZdUugw,61
|
2
|
+
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
4
|
utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -26,8 +26,9 @@ utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
|
26
26
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
27
27
|
utilities/hypothesis.py,sha256=sLqYcrFn0I3o0R7maqliIERRtAcREk2CKG8rSHY1t5U,46205
|
28
28
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
29
|
-
utilities/iterables.py,sha256=
|
29
|
+
utilities/iterables.py,sha256=2Yy9gZ7BR4LXR4nlX7outFAjd4dpb3lgUo7ji_sdylY,45076
|
30
30
|
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
31
|
+
utilities/lightweight_charts.py,sha256=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
|
31
32
|
utilities/logging.py,sha256=opIwFjGKOYyMntVeCsFNXOmTY2z02hMf2UtCB76SaI4,25142
|
32
33
|
utilities/loguru.py,sha256=MEMQVWrdECxk1e3FxGzmOf21vWT9j8CAir98SEXFKPA,3809
|
33
34
|
utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
|
@@ -45,7 +46,7 @@ utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
|
|
45
46
|
utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
|
46
47
|
utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
|
47
48
|
utilities/platform.py,sha256=NU7ycTvAXAG-fdYmDXaM1m4EOml2cGiaYwaUzfzSqyU,1767
|
48
|
-
utilities/polars.py,sha256=
|
49
|
+
utilities/polars.py,sha256=aOQNVyV04qYZjg7Exi6zYERhSQoCMzBP74oufxqANFY,52167
|
49
50
|
utilities/polars_ols.py,sha256=AQe3RFOMv8CEI_ZCoscb_-PxB4JWjO0TAEmk8DKLeaI,2138
|
50
51
|
utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
|
51
52
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -85,7 +86,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
|
|
85
86
|
utilities/whenever.py,sha256=TjoTAJ1R27-rKXiXzdE4GzPidmYqm0W58XydDXp-QZM,17786
|
86
87
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
87
88
|
utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
|
88
|
-
dycw_utilities-0.109.
|
89
|
-
dycw_utilities-0.109.
|
90
|
-
dycw_utilities-0.109.
|
91
|
-
dycw_utilities-0.109.
|
89
|
+
dycw_utilities-0.109.14.dist-info/METADATA,sha256=sjPmXifZcBMY4js2lgCXzweOgdeXLZ1qihJdZ8lFsTA,13005
|
90
|
+
dycw_utilities-0.109.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
91
|
+
dycw_utilities-0.109.14.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
92
|
+
dycw_utilities-0.109.14.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/altair.py
CHANGED
@@ -22,7 +22,6 @@ from altair import (
|
|
22
22
|
vconcat,
|
23
23
|
)
|
24
24
|
from altair.utils.schemapi import Undefined
|
25
|
-
from polars import Date, Datetime
|
26
25
|
|
27
26
|
from utilities.functions import ensure_bytes, ensure_number
|
28
27
|
from utilities.iterables import always_iterable
|
@@ -66,7 +65,7 @@ def plot_dataframes(
|
|
66
65
|
) -> VConcatChart:
|
67
66
|
"""Plot a DataFrame as a set of time series, with a multi-line tooltip."""
|
68
67
|
import polars as pl
|
69
|
-
from polars import int_range
|
68
|
+
from polars import Date, Datetime, int_range
|
70
69
|
|
71
70
|
from utilities.polars import replace_time_zone
|
72
71
|
|
utilities/iterables.py
CHANGED
@@ -19,7 +19,7 @@ from enum import Enum
|
|
19
19
|
from functools import cmp_to_key, partial, reduce
|
20
20
|
from itertools import accumulate, chain, groupby, islice, pairwise, product
|
21
21
|
from math import isnan
|
22
|
-
from operator import add, or_
|
22
|
+
from operator import add, itemgetter, or_
|
23
23
|
from typing import (
|
24
24
|
TYPE_CHECKING,
|
25
25
|
Any,
|
@@ -861,6 +861,24 @@ def filter_include_and_exclude(
|
|
861
861
|
##
|
862
862
|
|
863
863
|
|
864
|
+
def group_consecutive_integers(iterable: Iterable[int], /) -> Iterable[tuple[int, int]]:
|
865
|
+
"""Group consecutive integers."""
|
866
|
+
integers = sorted(iterable)
|
867
|
+
for _, group in groupby(enumerate(integers), key=lambda x: x[1] - x[0]):
|
868
|
+
as_list = list(map(itemgetter(1), group))
|
869
|
+
yield as_list[0], as_list[-1]
|
870
|
+
|
871
|
+
|
872
|
+
def ungroup_consecutive_integers(
|
873
|
+
iterable: Iterable[tuple[int, int]], /
|
874
|
+
) -> Iterable[int]:
|
875
|
+
"""Ungroup consecutive integers."""
|
876
|
+
return chain.from_iterable(range(start, end + 1) for start, end in iterable)
|
877
|
+
|
878
|
+
|
879
|
+
##
|
880
|
+
|
881
|
+
|
864
882
|
@overload
|
865
883
|
def groupby_lists(
|
866
884
|
iterable: Iterable[_T], /, *, key: None = None
|
@@ -991,7 +1009,7 @@ def one(*iterables: Iterable[_T]) -> _T:
|
|
991
1009
|
try:
|
992
1010
|
first = next(it)
|
993
1011
|
except StopIteration:
|
994
|
-
raise OneEmptyError from None
|
1012
|
+
raise OneEmptyError(iterables=iterables) from None
|
995
1013
|
try:
|
996
1014
|
second = next(it)
|
997
1015
|
except StopIteration:
|
@@ -1000,19 +1018,19 @@ def one(*iterables: Iterable[_T]) -> _T:
|
|
1000
1018
|
|
1001
1019
|
|
1002
1020
|
@dataclass(kw_only=True, slots=True)
|
1003
|
-
class OneError(Exception):
|
1021
|
+
class OneError(Exception, Generic[_T]):
|
1022
|
+
iterables: tuple[Iterable[_T], ...]
|
1004
1023
|
|
1005
1024
|
|
1006
1025
|
@dataclass(kw_only=True, slots=True)
|
1007
|
-
class OneEmptyError(OneError):
|
1026
|
+
class OneEmptyError(OneError[_T]):
|
1008
1027
|
@override
|
1009
1028
|
def __str__(self) -> str:
|
1010
|
-
return "Iterable(s) must not be empty"
|
1029
|
+
return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
|
1011
1030
|
|
1012
1031
|
|
1013
1032
|
@dataclass(kw_only=True, slots=True)
|
1014
1033
|
class OneNonUniqueError(OneError, Generic[_T]):
|
1015
|
-
iterables: tuple[Iterable[_T], ...]
|
1016
1034
|
first: _T
|
1017
1035
|
second: _T
|
1018
1036
|
|
@@ -1504,6 +1522,7 @@ __all__ = [
|
|
1504
1522
|
"ensure_iterable_not_str",
|
1505
1523
|
"expanding_window",
|
1506
1524
|
"filter_include_and_exclude",
|
1525
|
+
"group_consecutive_integers",
|
1507
1526
|
"groupby_lists",
|
1508
1527
|
"hashable_to_iterable",
|
1509
1528
|
"is_iterable",
|
@@ -1525,5 +1544,6 @@ __all__ = [
|
|
1525
1544
|
"sum_mappings",
|
1526
1545
|
"take",
|
1527
1546
|
"transpose",
|
1547
|
+
"ungroup_consecutive_integers",
|
1528
1548
|
"unique_everseen",
|
1529
1549
|
]
|
@@ -0,0 +1,96 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import TYPE_CHECKING, override
|
6
|
+
|
7
|
+
from utilities.iterables import OneEmptyError, OneNonUniqueError, one
|
8
|
+
from utilities.reprlib import get_repr
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from collections.abc import AsyncIterator
|
12
|
+
|
13
|
+
from lightweight_charts import AbstractChart, Chart
|
14
|
+
from lightweight_charts.abstract import SeriesCommon
|
15
|
+
from polars import DataFrame
|
16
|
+
from polars._typing import SchemaDict
|
17
|
+
|
18
|
+
from utilities.types import PathLike
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
def save_chart(chart: Chart, path: PathLike, /, *, overwrite: bool = False) -> None:
|
25
|
+
"""Atomically save a chart to disk."""
|
26
|
+
from utilities.atomicwrites import writer # pragma: no cover
|
27
|
+
|
28
|
+
chart.show(block=False) # pragma: no cover
|
29
|
+
with ( # pragma: no cover
|
30
|
+
writer(path, overwrite=overwrite) as temp,
|
31
|
+
temp.open(mode="wb") as fh,
|
32
|
+
):
|
33
|
+
_ = fh.write(chart.screenshot())
|
34
|
+
chart.exit() # pragma: no cover
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
def set_dataframe(df: DataFrame, obj: AbstractChart | SeriesCommon, /) -> None:
|
41
|
+
"""Set a `polars` DataFrame onto a Chart."""
|
42
|
+
from polars import Date, Datetime, col # pragma: no cover
|
43
|
+
|
44
|
+
try:
|
45
|
+
name = one(k for k, v in df.schema.items() if isinstance(v, Date | Datetime))
|
46
|
+
except OneEmptyError:
|
47
|
+
raise _SetDataFrameEmptyError(schema=df.schema) from None
|
48
|
+
except OneNonUniqueError as error:
|
49
|
+
raise _SetDataFrameNonUniqueError(
|
50
|
+
schema=df.schema, first=error.first, second=error.second
|
51
|
+
) from None
|
52
|
+
return obj.set(
|
53
|
+
df.select(
|
54
|
+
col(name).alias("date").dt.strftime("iso"),
|
55
|
+
*[c for c in df.columns if c != name],
|
56
|
+
).to_pandas()
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
@dataclass(kw_only=True, slots=True)
|
61
|
+
class SetDataFrameError(Exception):
|
62
|
+
schema: SchemaDict
|
63
|
+
|
64
|
+
|
65
|
+
@dataclass(kw_only=True, slots=True)
|
66
|
+
class _SetDataFrameEmptyError(SetDataFrameError):
|
67
|
+
@override
|
68
|
+
def __str__(self) -> str:
|
69
|
+
return "At least 1 column must be of date/datetime type; got 0"
|
70
|
+
|
71
|
+
|
72
|
+
@dataclass(kw_only=True, slots=True)
|
73
|
+
class _SetDataFrameNonUniqueError(SetDataFrameError):
|
74
|
+
first: str
|
75
|
+
second: str
|
76
|
+
|
77
|
+
@override
|
78
|
+
def __str__(self) -> str:
|
79
|
+
return f"{get_repr(self.schema)} must contain exactly 1 date/datetime column; got {self.first!r}, {self.second!r} and perhaps more"
|
80
|
+
|
81
|
+
|
82
|
+
##
|
83
|
+
|
84
|
+
|
85
|
+
@asynccontextmanager
|
86
|
+
async def yield_chart(chart: Chart, /) -> AsyncIterator[None]:
|
87
|
+
"""Yield a chart for visualization in a notebook."""
|
88
|
+
try: # pragma: no cover
|
89
|
+
yield await chart.show_async()
|
90
|
+
except BaseException: # pragma: no cover # noqa: BLE001, S110
|
91
|
+
pass
|
92
|
+
finally: # pragma: no cover
|
93
|
+
chart.exit()
|
94
|
+
|
95
|
+
|
96
|
+
__all__ = ["save_chart", "set_dataframe", "yield_chart"]
|
utilities/polars.py
CHANGED
@@ -714,7 +714,7 @@ class _DataClassToDataFrameNonUniqueError(DataClassToDataFrameError):
|
|
714
714
|
|
715
715
|
@override
|
716
716
|
def __str__(self) -> str:
|
717
|
-
return f"Iterable {get_repr(self.objs)} must contain exactly
|
717
|
+
return f"Iterable {get_repr(self.objs)} must contain exactly 1 class; got {self.first}, {self.second} and perhaps more"
|
718
718
|
|
719
719
|
|
720
720
|
##
|
File without changes
|
File without changes
|