dycw-utilities 0.166.30__py3-none-any.whl → 0.175.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.
Files changed (45) hide show
  1. dycw_utilities-0.175.17.dist-info/METADATA +34 -0
  2. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/RECORD +43 -38
  3. dycw_utilities-0.175.17.dist-info/WHEEL +4 -0
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.175.17.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +9 -4
  7. utilities/asyncio.py +10 -16
  8. utilities/cachetools.py +9 -6
  9. utilities/click.py +76 -20
  10. utilities/docker.py +293 -0
  11. utilities/functions.py +1 -1
  12. utilities/grp.py +28 -0
  13. utilities/hypothesis.py +38 -6
  14. utilities/importlib.py +17 -1
  15. utilities/jinja2.py +148 -0
  16. utilities/logging.py +7 -9
  17. utilities/orjson.py +18 -18
  18. utilities/os.py +38 -0
  19. utilities/parse.py +2 -2
  20. utilities/pathlib.py +18 -1
  21. utilities/permissions.py +298 -0
  22. utilities/platform.py +1 -1
  23. utilities/polars.py +4 -1
  24. utilities/postgres.py +28 -29
  25. utilities/pwd.py +28 -0
  26. utilities/pydantic.py +11 -0
  27. utilities/pydantic_settings.py +81 -8
  28. utilities/pydantic_settings_sops.py +13 -0
  29. utilities/pytest.py +60 -30
  30. utilities/pytest_regressions.py +26 -7
  31. utilities/shutil.py +25 -0
  32. utilities/sqlalchemy.py +15 -0
  33. utilities/subprocess.py +1572 -0
  34. utilities/tempfile.py +60 -1
  35. utilities/text.py +48 -32
  36. utilities/timer.py +2 -2
  37. utilities/traceback.py +1 -1
  38. utilities/types.py +5 -0
  39. utilities/typing.py +8 -2
  40. utilities/whenever.py +36 -5
  41. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  42. dycw_utilities-0.166.30.dist-info/WHEEL +0 -4
  43. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  44. utilities/aeventkit.py +0 -388
  45. utilities/typed_settings.py +0 -152
utilities/tempfile.py CHANGED
@@ -1,13 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import tempfile
4
+ from contextlib import contextmanager
4
5
  from pathlib import Path
6
+ from shutil import move
7
+ from tempfile import NamedTemporaryFile as _NamedTemporaryFile
5
8
  from tempfile import gettempdir as _gettempdir
6
9
  from typing import TYPE_CHECKING, override
7
10
 
8
11
  from utilities.warnings import suppress_warnings
9
12
 
10
13
  if TYPE_CHECKING:
14
+ from collections.abc import Iterator
11
15
  from types import TracebackType
12
16
 
13
17
  from utilities.types import PathLike
@@ -66,6 +70,61 @@ class _TemporaryDirectoryNoResourceWarning(tempfile.TemporaryDirectory):
66
70
  ##
67
71
 
68
72
 
73
+ @contextmanager
74
+ def TemporaryFile( # noqa: N802
75
+ *,
76
+ suffix: str | None = None,
77
+ prefix: str | None = None,
78
+ dir: PathLike | None = None, # noqa: A002
79
+ ignore_cleanup_errors: bool = False,
80
+ delete: bool = True,
81
+ name: str | None = None,
82
+ text: str | None = None,
83
+ ) -> Iterator[Path]:
84
+ """Yield a temporary file."""
85
+ with _temporary_file_inner(
86
+ suffix=suffix,
87
+ prefix=prefix,
88
+ dir=dir,
89
+ ignore_cleanup_errors=ignore_cleanup_errors,
90
+ delete=delete,
91
+ name=name,
92
+ ) as temp:
93
+ if text is not None:
94
+ _ = temp.write_text(text)
95
+ yield temp
96
+
97
+
98
+ @contextmanager
99
+ def _temporary_file_inner(
100
+ *,
101
+ suffix: str | None = None,
102
+ prefix: str | None = None,
103
+ dir: PathLike | None = None, # noqa: A002
104
+ ignore_cleanup_errors: bool = False,
105
+ delete: bool = True,
106
+ name: str | None = None,
107
+ ) -> Iterator[Path]:
108
+ with TemporaryDirectory(
109
+ suffix=suffix,
110
+ prefix=prefix,
111
+ dir=dir,
112
+ ignore_cleanup_errors=ignore_cleanup_errors,
113
+ delete=delete,
114
+ ) as temp_dir:
115
+ temp_file = _NamedTemporaryFile( # noqa: SIM115
116
+ dir=temp_dir, delete=delete, delete_on_close=False
117
+ )
118
+ if name is None:
119
+ yield temp_dir / temp_file.name
120
+ else:
121
+ _ = move(temp_dir / temp_file.name, temp_dir / name)
122
+ yield temp_dir / name
123
+
124
+
125
+ ##
126
+
127
+
69
128
  def gettempdir() -> Path:
70
129
  """Get the name of the directory used for temporary files."""
71
130
  return Path(_gettempdir())
@@ -74,4 +133,4 @@ def gettempdir() -> Path:
74
133
  TEMP_DIR = gettempdir()
75
134
 
76
135
 
77
- __all__ = ["TEMP_DIR", "TemporaryDirectory", "gettempdir"]
136
+ __all__ = ["TEMP_DIR", "TemporaryDirectory", "TemporaryFile", "gettempdir"]
utilities/text.py CHANGED
@@ -31,7 +31,15 @@ if TYPE_CHECKING:
31
31
  from utilities.types import MaybeCallableBoolLike, MaybeCallableStr, StrStrMapping
32
32
 
33
33
 
34
- DEFAULT_SEPARATOR = ","
34
+ _DEFAULT_SEPARATOR = ","
35
+
36
+
37
+ ##
38
+
39
+
40
+ def kebab_case(text: str, /) -> str:
41
+ """Convert text into kebab case."""
42
+ return _kebab_snake_case(text, "-")
35
43
 
36
44
 
37
45
  ##
@@ -110,27 +118,13 @@ def repr_encode(obj: Any, /) -> bytes:
110
118
 
111
119
  def snake_case(text: str, /) -> str:
112
120
  """Convert text into snake case."""
113
- leading = bool(search(r"^_", text))
114
- trailing = bool(search(r"_$", text))
115
- parts = _SPLIT_TEXT.findall(text)
116
- parts = (p for p in parts if len(p) >= 1)
117
- parts = chain([""] if leading else [], parts, [""] if trailing else [])
118
- return "_".join(parts).lower()
121
+ return _kebab_snake_case(text, "_")
119
122
 
120
123
 
121
- _SPLIT_TEXT = re.compile(
122
- r"""
123
- [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
124
- [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
125
- [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
126
- """,
127
- flags=VERBOSE,
128
- )
129
-
130
124
  ##
131
125
 
132
126
 
133
- LIST_SEPARATOR = DEFAULT_SEPARATOR
127
+ LIST_SEPARATOR = _DEFAULT_SEPARATOR
134
128
  PAIR_SEPARATOR = "="
135
129
  BRACKETS = [("(", ")"), ("[", "]"), ("{", "}")]
136
130
 
@@ -140,7 +134,7 @@ def split_key_value_pairs(
140
134
  text: str,
141
135
  /,
142
136
  *,
143
- list_separator: str = DEFAULT_SEPARATOR,
137
+ list_separator: str = _DEFAULT_SEPARATOR,
144
138
  pair_separator: str = PAIR_SEPARATOR,
145
139
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
146
140
  mapping: Literal[True],
@@ -150,7 +144,7 @@ def split_key_value_pairs(
150
144
  text: str,
151
145
  /,
152
146
  *,
153
- list_separator: str = DEFAULT_SEPARATOR,
147
+ list_separator: str = _DEFAULT_SEPARATOR,
154
148
  pair_separator: str = PAIR_SEPARATOR,
155
149
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
156
150
  mapping: Literal[False] = False,
@@ -160,7 +154,7 @@ def split_key_value_pairs(
160
154
  text: str,
161
155
  /,
162
156
  *,
163
- list_separator: str = DEFAULT_SEPARATOR,
157
+ list_separator: str = _DEFAULT_SEPARATOR,
164
158
  pair_separator: str = PAIR_SEPARATOR,
165
159
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
166
160
  mapping: bool = False,
@@ -169,7 +163,7 @@ def split_key_value_pairs(
169
163
  text: str,
170
164
  /,
171
165
  *,
172
- list_separator: str = DEFAULT_SEPARATOR,
166
+ list_separator: str = _DEFAULT_SEPARATOR,
173
167
  pair_separator: str = PAIR_SEPARATOR,
174
168
  brackets: Iterable[tuple[str, str]] | None = BRACKETS,
175
169
  mapping: bool = False,
@@ -228,7 +222,7 @@ def split_str(
228
222
  text: str,
229
223
  /,
230
224
  *,
231
- separator: str = DEFAULT_SEPARATOR,
225
+ separator: str = _DEFAULT_SEPARATOR,
232
226
  brackets: Iterable[tuple[str, str]] | None = None,
233
227
  n: Literal[1],
234
228
  ) -> tuple[str]: ...
@@ -237,7 +231,7 @@ def split_str(
237
231
  text: str,
238
232
  /,
239
233
  *,
240
- separator: str = DEFAULT_SEPARATOR,
234
+ separator: str = _DEFAULT_SEPARATOR,
241
235
  brackets: Iterable[tuple[str, str]] | None = None,
242
236
  n: Literal[2],
243
237
  ) -> tuple[str, str]: ...
@@ -246,7 +240,7 @@ def split_str(
246
240
  text: str,
247
241
  /,
248
242
  *,
249
- separator: str = DEFAULT_SEPARATOR,
243
+ separator: str = _DEFAULT_SEPARATOR,
250
244
  brackets: Iterable[tuple[str, str]] | None = None,
251
245
  n: Literal[3],
252
246
  ) -> tuple[str, str, str]: ...
@@ -255,7 +249,7 @@ def split_str(
255
249
  text: str,
256
250
  /,
257
251
  *,
258
- separator: str = DEFAULT_SEPARATOR,
252
+ separator: str = _DEFAULT_SEPARATOR,
259
253
  brackets: Iterable[tuple[str, str]] | None = None,
260
254
  n: Literal[4],
261
255
  ) -> tuple[str, str, str, str]: ...
@@ -264,7 +258,7 @@ def split_str(
264
258
  text: str,
265
259
  /,
266
260
  *,
267
- separator: str = DEFAULT_SEPARATOR,
261
+ separator: str = _DEFAULT_SEPARATOR,
268
262
  brackets: Iterable[tuple[str, str]] | None = None,
269
263
  n: Literal[5],
270
264
  ) -> tuple[str, str, str, str, str]: ...
@@ -273,7 +267,7 @@ def split_str(
273
267
  text: str,
274
268
  /,
275
269
  *,
276
- separator: str = DEFAULT_SEPARATOR,
270
+ separator: str = _DEFAULT_SEPARATOR,
277
271
  brackets: Iterable[tuple[str, str]] | None = None,
278
272
  n: int | None = None,
279
273
  ) -> tuple[str, ...]: ...
@@ -281,7 +275,7 @@ def split_str(
281
275
  text: str,
282
276
  /,
283
277
  *,
284
- separator: str = DEFAULT_SEPARATOR,
278
+ separator: str = _DEFAULT_SEPARATOR,
285
279
  brackets: Iterable[tuple[str, str]] | None = None,
286
280
  n: int | None = None,
287
281
  ) -> tuple[str, ...]:
@@ -306,7 +300,7 @@ def _split_str_brackets(
306
300
  brackets: Iterable[tuple[str, str]],
307
301
  /,
308
302
  *,
309
- separator: str = DEFAULT_SEPARATOR,
303
+ separator: str = _DEFAULT_SEPARATOR,
310
304
  ) -> list[str]:
311
305
  brackets = list(brackets)
312
306
  opens, closes = transpose(brackets)
@@ -397,7 +391,7 @@ class _SplitStrOpeningBracketUnmatchedError(SplitStrError):
397
391
 
398
392
 
399
393
  def join_strs(
400
- texts: Iterable[str], /, *, sort: bool = False, separator: str = DEFAULT_SEPARATOR
394
+ texts: Iterable[str], /, *, sort: bool = False, separator: str = _DEFAULT_SEPARATOR
401
395
  ) -> str:
402
396
  """Join a collection of strings, with a special provision for the empty list."""
403
397
  texts = list(texts)
@@ -410,7 +404,7 @@ def join_strs(
410
404
  return separator.join(texts)
411
405
 
412
406
 
413
- def _escape_separator(*, separator: str = DEFAULT_SEPARATOR) -> str:
407
+ def _escape_separator(*, separator: str = _DEFAULT_SEPARATOR) -> str:
414
408
  return f"\\{separator}"
415
409
 
416
410
 
@@ -513,9 +507,30 @@ def unique_str() -> str:
513
507
  return f"{now}_{pid}_{ident}_{key}"
514
508
 
515
509
 
510
+ ##
511
+
512
+
513
+ def _kebab_snake_case(text: str, separator: str, /) -> str:
514
+ """Convert text into kebab/snake case."""
515
+ leading = bool(search(r"^_", text))
516
+ trailing = bool(search(r"_$", text))
517
+ parts = _SPLIT_TEXT.findall(text)
518
+ parts = (p for p in parts if len(p) >= 1)
519
+ parts = chain([""] if leading else [], parts, [""] if trailing else [])
520
+ return separator.join(parts).lower()
521
+
522
+
523
+ _SPLIT_TEXT = re.compile(
524
+ r"""
525
+ [A-Z]+(?=[A-Z][a-z0-9]) | # all caps followed by Upper+lower or digit (API in APIResponse2)
526
+ [A-Z]?[a-z]+[0-9]* | # normal words with optional trailing digits (Text123)
527
+ [A-Z]+[0-9]* | # consecutive caps with optional trailing digits (ID2)
528
+ """,
529
+ flags=VERBOSE,
530
+ )
531
+
516
532
  __all__ = [
517
533
  "BRACKETS",
518
- "DEFAULT_SEPARATOR",
519
534
  "LIST_SEPARATOR",
520
535
  "PAIR_SEPARATOR",
521
536
  "ParseBoolError",
@@ -523,6 +538,7 @@ __all__ = [
523
538
  "SplitKeyValuePairsError",
524
539
  "SplitStrError",
525
540
  "join_strs",
541
+ "kebab_case",
526
542
  "parse_bool",
527
543
  "parse_none",
528
544
  "pascal_case",
utilities/timer.py CHANGED
@@ -56,11 +56,11 @@ class Timer:
56
56
 
57
57
  @override
58
58
  def __repr__(self) -> str:
59
- return self.timedelta.format_common_iso()
59
+ return self.timedelta.format_iso()
60
60
 
61
61
  @override
62
62
  def __str__(self) -> str:
63
- return self.timedelta.format_common_iso()
63
+ return self.timedelta.format_iso()
64
64
 
65
65
  # comparison
66
66
 
utilities/traceback.py CHANGED
@@ -98,7 +98,7 @@ def _yield_header_lines(
98
98
  yield f"Date/time | {format_compact(now)}"
99
99
  start_use = to_zoned_date_time(start).to_tz(LOCAL_TIME_ZONE_NAME)
100
100
  yield f"Started | {format_compact(start_use)}"
101
- yield f"Duration | {(now - start_use).format_common_iso()}"
101
+ yield f"Duration | {(now - start_use).format_iso()}"
102
102
  yield f"User | {getuser()}"
103
103
  yield f"Host | {gethostname()}"
104
104
  yield f"Process ID | {getpid()}"
utilities/types.py CHANGED
@@ -231,6 +231,10 @@ type Seed = int | float | str | bytes | bytearray | Random
231
231
  type PatternLike = MaybeStr[Pattern[str]]
232
232
 
233
233
 
234
+ # retry
235
+ type Retry = tuple[int, Delta | None]
236
+
237
+
234
238
  # text
235
239
  type MaybeCallableStr = MaybeCallable[str]
236
240
 
@@ -332,6 +336,7 @@ __all__ = [
332
336
  "PathLike",
333
337
  "PatternLike",
334
338
  "PlainDateTimeLike",
339
+ "Retry",
335
340
  "Seed",
336
341
  "SequenceStr",
337
342
  "SerializeObjectExtra",
utilities/typing.py CHANGED
@@ -42,7 +42,13 @@ from whenever import (
42
42
 
43
43
  from utilities.iterables import unique_everseen
44
44
  from utilities.sentinel import Sentinel
45
- from utilities.types import Dataclass, StrMapping, TupleOrStrMapping, TypeLike
45
+ from utilities.types import (
46
+ Dataclass,
47
+ StrMapping,
48
+ StrStrMapping,
49
+ TupleOrStrMapping,
50
+ TypeLike,
51
+ )
46
52
 
47
53
 
48
54
  def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...]:
@@ -58,7 +64,7 @@ def get_args(obj: Any, /, *, optional_drop_none: bool = False) -> tuple[Any, ...
58
64
  ##
59
65
 
60
66
 
61
- def get_forward_ref_args(obj: Any, /) -> Mapping[str, str]:
67
+ def get_forward_ref_args(obj: Any, /) -> StrStrMapping:
62
68
  """Get the forward args."""
63
69
  return {
64
70
  k: v.__forward_arg__
utilities/whenever.py CHANGED
@@ -433,6 +433,36 @@ TODAY_LOCAL = get_today_local()
433
433
  ##
434
434
 
435
435
 
436
+ def is_weekend(
437
+ date_time: ZonedDateTime,
438
+ /,
439
+ *,
440
+ start: tuple[Weekday, Time] = (Weekday.SATURDAY, Time.MIN),
441
+ end: tuple[Weekday, Time] = (Weekday.SUNDAY, Time.MAX),
442
+ ) -> bool:
443
+ """Check if a datetime is in the weekend."""
444
+ weekday, time = date_time.date().day_of_week(), date_time.time()
445
+ start_weekday, start_time = start
446
+ end_weekday, end_time = end
447
+ if start_weekday.value == end_weekday.value:
448
+ return start_time <= time <= end_time
449
+ if start_weekday.value < end_weekday.value:
450
+ return (
451
+ ((weekday == start_weekday) and (time >= start_time))
452
+ or (start_weekday.value < weekday.value < end_weekday.value)
453
+ or ((weekday == end_weekday) and (time <= end_time))
454
+ )
455
+ return (
456
+ ((weekday == start_weekday) and (time >= start_time))
457
+ or (weekday.value > start_weekday.value)
458
+ or (weekday.value < end_weekday.value)
459
+ or ((weekday == end_weekday) and (time <= end_time))
460
+ )
461
+
462
+
463
+ ##
464
+
465
+
436
466
  def mean_datetime(
437
467
  datetimes: Iterable[ZonedDateTime],
438
468
  /,
@@ -892,7 +922,7 @@ def to_date(
892
922
  case None:
893
923
  return get_today(time_zone)
894
924
  case str():
895
- return Date.parse_common_iso(date)
925
+ return Date.parse_iso(date)
896
926
  case dt.date():
897
927
  return Date.from_py_date(date)
898
928
  case Callable() as func:
@@ -1475,7 +1505,7 @@ def to_time(
1475
1505
  case None:
1476
1506
  return get_time(time_zone)
1477
1507
  case str():
1478
- return Time.parse_common_iso(time)
1508
+ return Time.parse_iso(time)
1479
1509
  case dt.time():
1480
1510
  return Time.from_py_time(time)
1481
1511
  case Callable() as func:
@@ -1703,7 +1733,7 @@ def to_zoned_date_time(
1703
1733
  case None:
1704
1734
  return get_now(UTC if time_zone is None else time_zone)
1705
1735
  case str() as text:
1706
- date_time_use = ZonedDateTime.parse_common_iso(text.replace("~", "/"))
1736
+ date_time_use = ZonedDateTime.parse_iso(text.replace("~", "/"))
1707
1737
  case dt.datetime() as py_date_time:
1708
1738
  if isinstance(date_time.tzinfo, ZoneInfo):
1709
1739
  py_date_time_use = py_date_time
@@ -1767,7 +1797,7 @@ class WheneverLogRecord(LogRecord):
1767
1797
  name, level, pathname, lineno, msg, args, exc_info, func, sinfo
1768
1798
  )
1769
1799
  length = self._get_length()
1770
- plain = format(get_now_local().to_plain().format_common_iso(), f"{length}s")
1800
+ plain = format(get_now_local().to_plain().format_iso(), f"{length}s")
1771
1801
  self.zoned_datetime = f"{plain}[{LOCAL_TIME_ZONE_NAME}]"
1772
1802
 
1773
1803
  @classmethod
@@ -1775,7 +1805,7 @@ class WheneverLogRecord(LogRecord):
1775
1805
  def _get_length(cls) -> int:
1776
1806
  """Get maximum length of a formatted string."""
1777
1807
  now = get_now_local().replace(nanosecond=1000).to_plain()
1778
- return len(now.format_common_iso())
1808
+ return len(now.format_iso())
1779
1809
 
1780
1810
 
1781
1811
  ##
@@ -2033,6 +2063,7 @@ __all__ = [
2033
2063
  "get_time_local",
2034
2064
  "get_today",
2035
2065
  "get_today_local",
2066
+ "is_weekend",
2036
2067
  "mean_datetime",
2037
2068
  "min_max_date",
2038
2069
  "round_date_or_date_time",
@@ -1,41 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: dycw-utilities
3
- Version: 0.166.30
4
- Author-email: Derek Wan <d.wan@icloud.com>
5
- License-File: LICENSE
6
- Requires-Python: >=3.12
7
- Requires-Dist: atomicwrites<1.5,>=1.4.1
8
- Requires-Dist: typing-extensions<4.16,>=4.15.0
9
- Requires-Dist: tzlocal<5.4,>=5.3.1
10
- Requires-Dist: whenever<0.9,>=0.8.8
11
- Provides-Extra: logging
12
- Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
- Provides-Extra: test
14
- Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
- Requires-Dist: hypothesis<6.139,>=6.138.16; extra == 'test'
16
- Requires-Dist: pytest-asyncio<1.3,>=1.2.0; extra == 'test'
17
- Requires-Dist: pytest-cov<7.1,>=7.0.0; extra == 'test'
18
- Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
19
- Requires-Dist: pytest-lazy-fixtures<1.4,>=1.3.4; extra == 'test'
20
- Requires-Dist: pytest-randomly<4.1,>=4.0.1; extra == 'test'
21
- Requires-Dist: pytest-regressions<2.9,>=2.8.3; extra == 'test'
22
- Requires-Dist: pytest-repeat<0.10,>=0.9.4; extra == 'test'
23
- Requires-Dist: pytest-rerunfailures<16.1,>=16.0.1; extra == 'test'
24
- Requires-Dist: pytest-rng<1.1,>=1.0.0; extra == 'test'
25
- Requires-Dist: pytest-timeout<2.5,>=2.4.0; extra == 'test'
26
- Requires-Dist: pytest-xdist<3.9,>=3.8.0; extra == 'test'
27
- Requires-Dist: pytest<8.5,>=8.4.2; extra == 'test'
28
- Requires-Dist: testbook<0.5,>=0.4.2; extra == 'test'
29
- Description-Content-Type: text/markdown
30
-
31
- [![PyPI version](https://badge.fury.io/py/dycw-utilities.svg)](https://badge.fury.io/py/dycw-utilities)
32
-
33
- # `dycw-utilities`
34
-
35
- [All the Python functions I don't want to write twice.](https://github.com/nvim-lua/plenary.nvim)
36
-
37
- ## Installation
38
-
39
- - `pip install dycw-utilities`
40
-
41
- or with [extras](https://github.com/dycw/python-utilities/blob/master/pyproject.toml).
@@ -1,4 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
3
- Root-Is-Purelib: true
4
- Tag: py3-none-any
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Derek Wan
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.