discordcn 0.0.1a2__py3-none-any.whl → 0.0.1a4__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.
discordcn/__init__.py CHANGED
@@ -1,4 +1 @@
1
- def dummy():
2
- print("Coming soon 👀")
3
-
4
- __all__ = ["dummy"]
1
+ """discordcn root package."""
@@ -0,0 +1,3 @@
1
+ from .dependencies import require_extra
2
+
3
+ __all__ = ("require_extra",)
@@ -0,0 +1,194 @@
1
+ """_utils.dependencies.py — dependency / backend guards for optional extras.
2
+
3
+ Usage:
4
+ from discordcn._utils import _require_extra
5
+
6
+ _require_extra(package="py-cord", import_name="discord", version=(2, 7, 0))
7
+
8
+ What it enforces:
9
+ - The top-level import namespace must be provided by exactly ONE installed
10
+ distribution, and it must be `package`.
11
+ - `package`'s installed version must be >= `version` (same major version).
12
+ """
13
+
14
+ import importlib
15
+ import importlib.metadata as md
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Mapping
20
+
21
+
22
+ class ExtraRequirementError(RuntimeError):
23
+ """Raised when an extra/backend requirement is not satisfied."""
24
+
25
+
26
+ def _norm_dist_name(name: str) -> str:
27
+ """Normalize distribution name per PEP 503.
28
+
29
+ Converts to lowercase and replaces underscores/dots with hyphens.
30
+
31
+ Args:
32
+ name: Distribution name to normalize.
33
+
34
+ Returns:
35
+ Normalized distribution name.
36
+
37
+ Example:
38
+ >>> _norm_dist_name("Py_Cord")
39
+ 'py-cord'
40
+ """
41
+ return name.strip().casefold().replace("_", "-").replace(".", "-")
42
+
43
+
44
+ def _parse_version(v: str) -> tuple[int, ...]:
45
+ """Parse version string into numeric tuple.
46
+
47
+ Extracts numeric components and stops at first non-numeric part.
48
+
49
+ Args:
50
+ v: Version string to parse (e.g., "2.7.0rc2").
51
+
52
+ Returns:
53
+ Tuple of version numbers (e.g., (2, 7, 0)).
54
+
55
+ Example:
56
+ >>> _parse_version("2.7.0rc2")
57
+ (2, 7, 0)
58
+ >>> _parse_version("3.11")
59
+ (3, 11)
60
+ """
61
+ parts: list[int] = []
62
+ buf = ""
63
+ for ch in v:
64
+ if ch.isdigit():
65
+ buf += ch
66
+ elif buf:
67
+ parts.append(int(buf))
68
+ buf = ""
69
+ if buf:
70
+ parts.append(int(buf))
71
+ return tuple(parts) if parts else (0,)
72
+
73
+
74
+ def _packages_providing(top_level: str) -> set[str]:
75
+ """Get normalized distribution names providing a top-level module.
76
+
77
+ Args:
78
+ top_level: Top-level module name (e.g., "discord").
79
+
80
+ Returns:
81
+ Set of normalized distribution names that provide the module.
82
+
83
+ Example:
84
+ >>> _packages_providing("discord")
85
+ {'py-cord'}
86
+ """
87
+ mapping: Mapping[str, list[str]] = md.packages_distributions()
88
+ providers = mapping.get(top_level, [])
89
+ return {_norm_dist_name(p) for p in providers}
90
+
91
+
92
+ def require_extra(
93
+ *,
94
+ package: str,
95
+ import_name: str,
96
+ version: tuple[int, ...] | None = None,
97
+ ) -> None:
98
+ """Ensure import is provided exclusively by package with correct version.
99
+
100
+ Validates that:
101
+ 1. The import name can be imported successfully.
102
+ 2. Exactly one distribution provides it, and it's the specified package.
103
+ 3. The package version is >= the required version (same major version).
104
+
105
+ Args:
106
+ package: Required package distribution name (e.g., "py-cord").
107
+ import_name: Top-level import name to check (e.g., "discord").
108
+ version: Minimum required version tuple (e.g., (2, 7, 0)).
109
+ Major version must match exactly. Defaults to None (no check).
110
+
111
+ Raises:
112
+ ModuleNotFoundError: If import fails or package not found in metadata.
113
+ ExtraRequirementError: If wrong provider or version mismatch detected.
114
+
115
+ Example:
116
+ >>> require_extra(package="py-cord", import_name="discord", version=(2, 7, 0))
117
+ # Raises if discord.py is installed instead of py-cord
118
+ # Raises if py-cord version is 2.6.x or 3.x
119
+ """
120
+ pkg_norm = _norm_dist_name(package)
121
+
122
+ # 1) Ensure the import works
123
+ try:
124
+ importlib.import_module(import_name)
125
+ except ModuleNotFoundError as e:
126
+ version_str = f" (need >= {'.'.join(map(str, version))})" if version else ""
127
+ msg = (
128
+ f"`{import_name}` is required but could not be imported.\n"
129
+ + f"Install with: pip install '{package}'{version_str}"
130
+ )
131
+
132
+ raise ModuleNotFoundError(msg) from e
133
+
134
+ # 2) Ensure ONLY the requested package provides it
135
+ providers = _packages_providing(import_name)
136
+
137
+ if not providers:
138
+ msg = (
139
+ f"Imported `{import_name}`, but no distribution claims it in metadata.\n"
140
+ + f"Try: pip install -U --force-reinstall {package}"
141
+ )
142
+
143
+ raise ExtraRequirementError(msg)
144
+
145
+ if providers != {pkg_norm}:
146
+ other = sorted(p for p in providers if p != pkg_norm)
147
+ uninstall = f" pip uninstall -y {' '.join(other)}\n" if other else ""
148
+ msg = (
149
+ f"`{import_name}` must be provided exclusively by `{package}`, "
150
+ + f"but found: {', '.join(sorted(providers))}.\n"
151
+ + f"Fix by uninstalling conflicts:\n{uninstall}"
152
+ + f" pip install -U '{package}'"
153
+ )
154
+
155
+ raise ExtraRequirementError(msg)
156
+
157
+ # 3) Version check
158
+ if version is None:
159
+ return
160
+
161
+ try:
162
+ installed_str = md.version(package)
163
+ except md.PackageNotFoundError as e:
164
+ msg = f"Distribution `{package}` not found in metadata.\nInstall with: pip install '{package}'"
165
+
166
+ raise ModuleNotFoundError(msg) from e
167
+
168
+ installed = _parse_version(installed_str)
169
+ required = tuple(version)
170
+
171
+ max_len = max(len(installed), len(required))
172
+ inst_padded = installed + (0,) * (max_len - len(installed))
173
+ req_padded = required + (0,) * (max_len - len(required))
174
+
175
+ if installed[0] != required[0]:
176
+ msg = (
177
+ f"`{package}` major version must be {required[0]}.x, "
178
+ + f"but found {installed_str}.\n"
179
+ + f"Install: pip install '{package}>={required[0]}.{required[1] if len(required) > 1 else 0}'"
180
+ )
181
+
182
+ raise ExtraRequirementError(msg)
183
+
184
+ if inst_padded < req_padded:
185
+ msg = (
186
+ f"`{package}` >= {'.'.join(map(str, required))} required, "
187
+ + f"found {installed_str}.\n"
188
+ + f"Upgrade: pip install -U '{package}'"
189
+ )
190
+
191
+ raise ExtraRequirementError(msg)
192
+
193
+
194
+ __all__ = ("require_extra",)
discordcn/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.1a2'
32
- __version_tuple__ = version_tuple = (0, 0, 1, 'a2')
31
+ __version__ = version = '0.0.1a4'
32
+ __version_tuple__ = version_tuple = (0, 0, 1, 'a4')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -0,0 +1,30 @@
1
+ """discordcn for pycord."""
2
+
3
+ # ruff: noqa: E402
4
+ from discordcn._utils import require_extra
5
+
6
+ require_extra(package="py-cord", import_name="discord", version=(2, 7, 0))
7
+
8
+ from .interfaces import (
9
+ AccordionInterface,
10
+ ConfirmInterface,
11
+ PageButton,
12
+ PageIndicatorButton,
13
+ PaginatorControls,
14
+ PaginatorControlsBase,
15
+ PaginatorIndicatorModal,
16
+ PaginatorInterface,
17
+ PaginatorInterfaceBase,
18
+ )
19
+
20
+ __all__ = (
21
+ "AccordionInterface",
22
+ "ConfirmInterface",
23
+ "PageButton",
24
+ "PageIndicatorButton",
25
+ "PaginatorControls",
26
+ "PaginatorControlsBase",
27
+ "PaginatorIndicatorModal",
28
+ "PaginatorInterface",
29
+ "PaginatorInterfaceBase",
30
+ )
@@ -0,0 +1,8 @@
1
+ from ._asyncio import MaybeAwaitable, maybe_awaitable, safe_set_future_exception, safe_set_future_result
2
+
3
+ __all__ = (
4
+ "MaybeAwaitable",
5
+ "maybe_awaitable",
6
+ "safe_set_future_exception",
7
+ "safe_set_future_result",
8
+ )
@@ -0,0 +1,34 @@
1
+ from asyncio import Future
2
+ from collections.abc import Awaitable
3
+ from inspect import isawaitable
4
+ from typing import Any, TypeAlias, TypeVar
5
+
6
+ T = TypeVar("T")
7
+
8
+ MaybeAwaitable: TypeAlias = Awaitable[T] | T
9
+
10
+
11
+ async def maybe_awaitable(obj: MaybeAwaitable[T]) -> T:
12
+ if isawaitable(obj):
13
+ return await obj
14
+ return obj
15
+
16
+
17
+ def safe_set_future_result(future: Future[T], value: T) -> bool:
18
+ if not future.done():
19
+ future.set_result(value)
20
+ return True
21
+ return False
22
+
23
+
24
+ def safe_set_future_exception(future: Future[Any], exc: BaseException) -> None:
25
+ if not future.done():
26
+ future.set_exception(exc)
27
+
28
+
29
+ __all__ = (
30
+ "MaybeAwaitable",
31
+ "maybe_awaitable",
32
+ "safe_set_future_exception",
33
+ "safe_set_future_result",
34
+ )
@@ -0,0 +1,25 @@
1
+ """discordcn interfaces for pycord."""
2
+
3
+ from .accordion import AccordionInterface
4
+ from .confirm import ConfirmInterface
5
+ from .paginator import (
6
+ PageButton,
7
+ PageIndicatorButton,
8
+ PaginatorControls,
9
+ PaginatorControlsBase,
10
+ PaginatorIndicatorModal,
11
+ PaginatorInterface,
12
+ PaginatorInterfaceBase,
13
+ )
14
+
15
+ __all__ = (
16
+ "AccordionInterface",
17
+ "ConfirmInterface",
18
+ "PageButton",
19
+ "PageIndicatorButton",
20
+ "PaginatorControls",
21
+ "PaginatorControlsBase",
22
+ "PaginatorIndicatorModal",
23
+ "PaginatorInterface",
24
+ "PaginatorInterfaceBase",
25
+ )
@@ -0,0 +1,14 @@
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import Any, TypeAlias, TypeVar
3
+
4
+ import discord
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ Callback: TypeAlias = Callable[[T], Awaitable[Any] | Any]
10
+
11
+ EmojiType: TypeAlias = discord.PartialEmoji | discord.AppEmoji | discord.GuildEmoji
12
+ """Accepted emoji types for section accessory buttons."""
13
+
14
+ __all__ = ("Callback",)