tuttifrutti 0.1.0__tar.gz
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.
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tuttifrutti
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A collection of tiny, reusable Python utilities.
|
|
5
|
+
Author: Kunal Goel
|
|
6
|
+
Author-email: Kunal Goel <hexcodex101@gmail.com>
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Maintainer: Kunal Goel
|
|
15
|
+
Maintainer-email: Kunal Goel <hexcodex101@gmail.com>
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Project-URL: Homepage, https://github.com/kgcodex/tuttifrutti
|
|
18
|
+
Project-URL: Documentation, https://github.com/kgcodex/tuttifrutti#readme
|
|
19
|
+
Project-URL: Repository, https://github.com/kgcodex/tuttifrutti
|
|
20
|
+
Project-URL: Issues, https://github.com/kgcodex/tuttifrutti/issues
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tuttifrutti"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A collection of tiny, reusable Python utilities."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Kunal Goel", email = "hexcodex101@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
maintainers = [
|
|
10
|
+
{ name = "Kunal Goel", email = "hexcodex101@gmail.com" }
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
dependencies = []
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/kgcodex/tuttifrutti"
|
|
26
|
+
Documentation = "https://github.com/kgcodex/tuttifrutti#readme"
|
|
27
|
+
Repository = "https://github.com/kgcodex/tuttifrutti"
|
|
28
|
+
Issues = "https://github.com/kgcodex/tuttifrutti/issues"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv_build>=0.11.8,<0.12.0"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"mypy>=2.1.0",
|
|
37
|
+
"pre-commit>=4.6.0",
|
|
38
|
+
"pytest>=9.1.1",
|
|
39
|
+
"ruff>=0.15.20",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
[tool.mypy]
|
|
44
|
+
python_version = "3.12"
|
|
45
|
+
strict = true
|
|
46
|
+
warn_return_any = true
|
|
47
|
+
warn_unused_configs = true
|
|
48
|
+
disallow_untyped_defs = true
|
|
49
|
+
disallow_any_generics = true
|
|
50
|
+
check_untyped_defs = true
|
|
51
|
+
|
|
52
|
+
mypy_path = "src"
|
|
53
|
+
explicit_package_bases = true
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
[[tool.mypy.overrides]]
|
|
57
|
+
module = "pytest.*"
|
|
58
|
+
ignore_missing_imports = true
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py312"
|
|
63
|
+
line-length = 88
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = [
|
|
67
|
+
"E",
|
|
68
|
+
"W",
|
|
69
|
+
"F",
|
|
70
|
+
"I",
|
|
71
|
+
"UP",
|
|
72
|
+
"B",
|
|
73
|
+
"SIM",
|
|
74
|
+
"N",
|
|
75
|
+
"ASYNC",
|
|
76
|
+
"PTH",
|
|
77
|
+
]
|
|
78
|
+
# ignore = ["E501"]
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.isort]
|
|
81
|
+
combine-as-imports = true
|
|
82
|
+
force-single-line = false
|
|
83
|
+
|
|
84
|
+
[tool.ruff.format]
|
|
85
|
+
quote-style = "double"
|
|
86
|
+
indent-style = "space"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
from importlib.metadata import version
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
__version__ = version("tuttifrutti")
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ._optional_chain import OptionalChain
|
|
9
|
+
|
|
10
|
+
__all__ = ["OptionalChain"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def __getattr__(name: str) -> Any:
|
|
14
|
+
if name == "OptionalChain":
|
|
15
|
+
return import_module("._optional_chain", __name__).OptionalChain
|
|
16
|
+
|
|
17
|
+
raise AttributeError(f"module '{__name__}' has not attribute {name}")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OptionalChain[T]:
|
|
8
|
+
def __init__(self, obj: T | None) -> None:
|
|
9
|
+
self._obj = obj
|
|
10
|
+
|
|
11
|
+
def __repr__(self) -> str:
|
|
12
|
+
return (
|
|
13
|
+
f"OptionalChain("
|
|
14
|
+
f"{self._obj.__class__.__name__ if self._obj is not None else None}"
|
|
15
|
+
f")"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def __getattr__(self, attr: str) -> OptionalChain[Any]:
|
|
19
|
+
if self._obj is None:
|
|
20
|
+
return OptionalChain(None)
|
|
21
|
+
|
|
22
|
+
if isinstance(self._obj, dict):
|
|
23
|
+
return OptionalChain(self._obj.get(attr, None))
|
|
24
|
+
|
|
25
|
+
return OptionalChain(getattr(self._obj, attr, None))
|
|
26
|
+
|
|
27
|
+
def get(self) -> T | None:
|
|
28
|
+
return self._obj
|
|
29
|
+
|
|
30
|
+
def __getitem__(self, key: Any) -> OptionalChain[Any]:
|
|
31
|
+
if self._obj is None:
|
|
32
|
+
return OptionalChain(None)
|
|
33
|
+
try:
|
|
34
|
+
return OptionalChain(self._obj[key]) # type:ignore[index]
|
|
35
|
+
except (KeyError, IndexError, TypeError):
|
|
36
|
+
return OptionalChain(None)
|
|
37
|
+
|
|
38
|
+
def or_else[K](self, default: K) -> T | K:
|
|
39
|
+
return self._obj if self._obj is not None else default
|
|
40
|
+
|
|
41
|
+
def map[R](self, func: Callable[[T], R]) -> OptionalChain[R]:
|
|
42
|
+
if self._obj is None:
|
|
43
|
+
return OptionalChain(None)
|
|
44
|
+
return OptionalChain(func(self._obj))
|
|
File without changes
|