literalenum 0.1.1__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.
literalenum/stubgen.py ADDED
@@ -0,0 +1,438 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import ast
5
+ import importlib
6
+ import importlib.util
7
+ import inspect
8
+ import pkgutil
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Iterable
12
+
13
+ from literalenum import LiteralEnum
14
+
15
+
16
+ # ----------------------------
17
+ # Discovery
18
+ # ----------------------------
19
+
20
+ def _iter_modules(root: str) -> Iterable[str]:
21
+ pkg = importlib.import_module(root)
22
+ if not hasattr(pkg, "__path__"):
23
+ yield root
24
+ return
25
+
26
+ yield root
27
+ for modinfo in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
28
+ yield modinfo.name
29
+
30
+
31
+ def _module_origin_py(module: str) -> Path | None:
32
+ spec = importlib.util.find_spec(module)
33
+ if not spec or not spec.origin or spec.origin in ("built-in", "namespace"):
34
+ return None
35
+ p = Path(spec.origin)
36
+ return p if p.suffix == ".py" else None
37
+
38
+
39
+ def _module_to_adjacent_stub_path(module: str) -> Path | None:
40
+ origin = _module_origin_py(module)
41
+ if origin is None:
42
+ return None
43
+ if origin.name == "__init__.py":
44
+ return origin.with_name("__init__.pyi")
45
+ return origin.with_suffix(".pyi")
46
+
47
+
48
+ def _module_to_stub_path(stub_root: Path, module: str) -> Path:
49
+ rel = Path(*module.split("."))
50
+ return stub_root / (str(rel) + ".pyi")
51
+
52
+
53
+ def _ensure_parent(path: Path) -> None:
54
+ path.parent.mkdir(parents=True, exist_ok=True)
55
+
56
+
57
+ def _py_literal(v: Any) -> str:
58
+ # Emit double quotes for strings for nicer stubs.
59
+ if isinstance(v, str):
60
+ return '"' + v.replace('"', '\\"') + '"'
61
+ return repr(v)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class EnumInfo:
66
+ module: str
67
+ name: str
68
+ qualname: str
69
+ bases: tuple[type, ...]
70
+ members: dict[str, Any] # name -> value (from runtime mapping)
71
+ call_to_validate: bool = False
72
+
73
+
74
+ def _find_literal_enums(root: str) -> list[EnumInfo]:
75
+ infos: list[EnumInfo] = []
76
+ for modname in _iter_modules(root):
77
+ try:
78
+ mod = importlib.import_module(modname)
79
+ except Exception:
80
+ continue
81
+
82
+ for _, obj in inspect.getmembers(mod, inspect.isclass):
83
+ if obj is LiteralEnum:
84
+ continue
85
+ if issubclass(obj, LiteralEnum) and obj.__module__ == mod.__name__:
86
+ members = dict(getattr(obj, "mapping"))
87
+ infos.append(
88
+ EnumInfo(
89
+ module=obj.__module__,
90
+ name=obj.__name__,
91
+ qualname=f"{obj.__module__}.{obj.__name__}",
92
+ bases=getattr(obj, "__bases__", ()),
93
+ members=members,
94
+ call_to_validate=getattr(obj, "_call_to_validate_", False),
95
+ )
96
+ )
97
+ return infos
98
+
99
+
100
+ # ----------------------------
101
+ # Rendering enum stubs
102
+ # ----------------------------
103
+
104
+ def _render_enum_blocks(enums: list[EnumInfo]) -> str:
105
+ """
106
+ Render ONLY enum-related stubs for a module:
107
+ - <EnumName>T = Literal[...]
108
+ - class <EnumName>(Base): ...
109
+ """
110
+ by_qual = {e.qualname: e for e in enums}
111
+
112
+ def _qual_of(cls: type) -> str:
113
+ return f"{getattr(cls, '__module__', '')}.{getattr(cls, '__name__', '')}"
114
+
115
+ def _enum_base_decl(e: EnumInfo) -> str:
116
+ # If base is another emitted enum in the same module, preserve that inheritance.
117
+ for b in e.bases:
118
+ if _qual_of(b) in by_qual:
119
+ return b.__name__
120
+ return f"LiteralEnum[{e.name}T]"
121
+
122
+ def _inherited_member_names(e: EnumInfo) -> set[str]:
123
+ names: set[str] = set()
124
+ for b in e.bases:
125
+ qb = _qual_of(b)
126
+ if qb in by_qual:
127
+ names |= set(by_qual[qb].members.keys())
128
+ return names
129
+
130
+ out: list[str] = []
131
+ for e in sorted(enums, key=lambda x: x.name):
132
+ alias = f"{e.name}T"
133
+ values = list(e.members.values())
134
+ literal_union = ", ".join(_py_literal(v) for v in values) if values else ""
135
+ out.append(f"{alias}: TypeAlias = Literal[{literal_union}]\n\n")
136
+
137
+ base = _enum_base_decl(e)
138
+ inherited = _inherited_member_names(e)
139
+
140
+ # Only emit new member attributes (avoid duplicating inherited ones).
141
+ own_members = [(k, v) for (k, v) in e.members.items() if k not in inherited]
142
+
143
+ out.append(f"class {e.name}({base}):\n")
144
+ out.append(f" T_ = Literal[{literal_union}]\n")
145
+ if not own_members:
146
+ out.append(" ...\n")
147
+ else:
148
+ for k, v in own_members:
149
+ out.append(f" {k}: Final[Literal[{_py_literal(v)}]] = {_py_literal(v)}\n")
150
+
151
+ out.append(f" values: ClassVar[Iterable[{alias}]]\n")
152
+ out.append(f" mapping: ClassVar[dict[str, {alias}]]\n\n")
153
+
154
+ if e.call_to_validate:
155
+ out.append(" @overload\n")
156
+ out.append(f" def __new__(cls, value: {alias}) -> {alias}: ...\n")
157
+ out.append(" @overload\n")
158
+ out.append(f" def __new__(cls, value: object) -> {alias}: ...\n\n")
159
+ else:
160
+ out.append(f" def __new__(cls, value: Never) -> NoReturn: ...\n\n")
161
+
162
+ out.append(" @classmethod\n")
163
+ out.append(f" def is_member(cls, value: object) -> TypeGuard[{alias}]: ...\n\n")
164
+
165
+ return "".join(out)
166
+
167
+
168
+ def stub_for(literalenum_cls: type) -> str:
169
+ """Return a stub string for a single LiteralEnum class.
170
+
171
+ Takes a LiteralEnum subclass at runtime and produces the ``.pyi``
172
+ content for it — a ``TypeAlias``, a class with ``Final`` members,
173
+ and typed helper signatures::
174
+
175
+ from literalenum import LiteralEnum
176
+ from literalenum.stubgen import stub_for
177
+
178
+ class HttpMethod(LiteralEnum):
179
+ GET = "GET"
180
+ POST = "POST"
181
+
182
+ print(stub_for(HttpMethod))
183
+
184
+ The output is a self-contained snippet (no import header). Wrap it
185
+ with your own imports or use the CLI for full-module stubs.
186
+ """
187
+ info = EnumInfo(
188
+ module=getattr(literalenum_cls, "__module__", ""),
189
+ name=literalenum_cls.__name__,
190
+ qualname=f"{getattr(literalenum_cls, '__module__', '')}.{literalenum_cls.__name__}",
191
+ bases=getattr(literalenum_cls, "__bases__", ()),
192
+ members=dict(getattr(literalenum_cls, "mapping")),
193
+ call_to_validate=getattr(literalenum_cls, "_call_to_validate_", False),
194
+ )
195
+ return _render_enum_blocks([info])
196
+
197
+
198
+ def _render_overlay_stub_module(enums: list[EnumInfo]) -> str:
199
+ """
200
+ Overlay stub intended for pyright stubPath:
201
+ only contains the enum stubs (module is "typing overlay").
202
+ """
203
+ out: list[str] = []
204
+ out.append("from __future__ import annotations\n")
205
+ out.append("from typing import ClassVar, Final, Literal, Iterable, Never, NoReturn, TypeGuard, TypeAlias, overload\n")
206
+ out.append("from literalenum import LiteralEnum\n\n")
207
+ out.append(_render_enum_blocks(enums))
208
+ return "".join(out)
209
+
210
+
211
+ # ----------------------------
212
+ # Adjacent stubs (preserve module)
213
+ # ----------------------------
214
+
215
+ _TYPING_INJECT = "from typing import ClassVar, Final, Literal, Iterable, Never, NoReturn, TypeGuard, TypeAlias, overload"
216
+ _LITERALENUM_INJECT = "from literalenum import LiteralEnum"
217
+ _FUTURE = "from __future__ import annotations"
218
+
219
+
220
+ def _read_source(path: Path) -> str:
221
+ return path.read_text(encoding="utf-8")
222
+
223
+
224
+ def _collect_docstring(tree: ast.Module, src: str) -> str | None:
225
+ if tree.body and isinstance(tree.body[0], ast.Expr) and isinstance(tree.body[0].value, ast.Constant):
226
+ if isinstance(tree.body[0].value.value, str):
227
+ seg = ast.get_source_segment(src, tree.body[0])
228
+ return seg.strip() if seg else None
229
+ return None
230
+
231
+
232
+ def _collect_import_lines(tree: ast.Module, src: str) -> list[str]:
233
+ lines: list[str] = []
234
+ for stmt in tree.body:
235
+ if isinstance(stmt, (ast.Import, ast.ImportFrom)):
236
+ seg = ast.get_source_segment(src, stmt)
237
+ if seg:
238
+ lines.append(seg.strip())
239
+ return lines
240
+
241
+
242
+ def _normalize_imports(import_lines: list[str]) -> list[str]:
243
+ """
244
+ Remove imports we will inject exactly once to avoid dupes.
245
+ """
246
+ out: list[str] = []
247
+ for line in import_lines:
248
+ s = line.strip()
249
+ if s == _FUTURE:
250
+ continue
251
+ if s.startswith("from literalenum import") and "LiteralEnum" in s:
252
+ continue
253
+ if s.startswith("from typing import"):
254
+ # drop any typing line that imports our injected symbols
255
+ # (simple heuristic: if it mentions any of them, drop it)
256
+ symbols = ["ClassVar", "Final", "Literal", "Iterable", "TypeGuard", "overload"]
257
+ if any(sym in s for sym in symbols):
258
+ continue
259
+ out.append(s)
260
+ return out
261
+
262
+
263
+ def _is_enum_classdef(stmt: ast.stmt, enum_names: set[str]) -> bool:
264
+ return isinstance(stmt, ast.ClassDef) and stmt.name in enum_names
265
+
266
+
267
+ def _is_safe_preserve_stmt(stmt: ast.stmt) -> bool:
268
+ """
269
+ Preserve harmless top-level statements verbatim.
270
+ - imports handled separately
271
+ - docstring handled separately
272
+ """
273
+ return isinstance(stmt, (ast.Assign, ast.AnnAssign))
274
+
275
+
276
+ def _stub_skeleton(stmt: ast.stmt, src: str) -> str | None:
277
+ # Keep names for everything else, but as stubs.
278
+ if isinstance(stmt, ast.FunctionDef):
279
+ header = (ast.get_source_segment(src, stmt) or f"def {stmt.name}(...):").splitlines()[0]
280
+ if not header.rstrip().endswith(":"):
281
+ header = header.rstrip() + ":"
282
+ return header + "\n ...\n"
283
+ if isinstance(stmt, ast.AsyncFunctionDef):
284
+ header = (ast.get_source_segment(src, stmt) or f"async def {stmt.name}(...):").splitlines()[0]
285
+ if not header.rstrip().endswith(":"):
286
+ header = header.rstrip() + ":"
287
+ return header + "\n ...\n"
288
+ if isinstance(stmt, ast.ClassDef):
289
+ # Non-enum class: preserve header with bases, stub body
290
+ bases = ""
291
+ if stmt.bases:
292
+ bases_src = ", ".join(ast.get_source_segment(src, b) or "object" for b in stmt.bases)
293
+ bases = f"({bases_src})"
294
+ return f"class {stmt.name}{bases}:\n ...\n"
295
+ return None
296
+
297
+
298
+ def _render_adjacent_preserving_stub(module: str, enums: list[EnumInfo]) -> str:
299
+ origin = _module_origin_py(module)
300
+ if origin is None:
301
+ return _render_overlay_stub_module(enums)
302
+
303
+ src = _read_source(origin)
304
+ tree = ast.parse(src, filename=str(origin))
305
+
306
+ enum_names = {e.name for e in enums}
307
+
308
+ doc = _collect_docstring(tree, src)
309
+ imports = _normalize_imports(_collect_import_lines(tree, src))
310
+
311
+ out: list[str] = []
312
+ out.append(_FUTURE + "\n")
313
+ if doc:
314
+ out.append(doc + "\n\n")
315
+
316
+ # Original imports (minus ones we inject)
317
+ if imports:
318
+ out.extend(line + "\n" for line in imports)
319
+ out.append("\n")
320
+
321
+ # Inject what enum blocks need, exactly once
322
+ out.append(_TYPING_INJECT + "\n")
323
+ out.append(_LITERALENUM_INJECT + "\n\n")
324
+
325
+ # Walk original statements in order:
326
+ for stmt in tree.body:
327
+ # skip docstring/imports (already handled)
328
+ if stmt is tree.body[0] and doc:
329
+ continue
330
+ if isinstance(stmt, (ast.Import, ast.ImportFrom)):
331
+ continue
332
+
333
+ # Replace enum classdefs with generated blocks later; skip here.
334
+ if _is_enum_classdef(stmt, enum_names):
335
+ continue
336
+
337
+ # Preserve simple assignments verbatim
338
+ if _is_safe_preserve_stmt(stmt):
339
+ seg = ast.get_source_segment(src, stmt)
340
+ if seg:
341
+ out.append(seg.strip() + "\n\n")
342
+ continue
343
+
344
+ # For everything else, emit skeleton stubs so names remain
345
+ sk = _stub_skeleton(stmt, src)
346
+ if sk:
347
+ out.append(sk + "\n")
348
+ continue
349
+
350
+ # Drop other statements (loops, runtime code, etc.)—not valid/meaningful in stubs.
351
+
352
+ # Now append enum stubs (aliases + class stubs)
353
+ out.append(_render_enum_blocks(enums))
354
+
355
+ return "".join(out)
356
+
357
+
358
+ # ----------------------------
359
+ # CLI
360
+ # ----------------------------
361
+
362
+ def _parse_out_args(raw: list[str] | None) -> list[Path]:
363
+ """
364
+ Supports:
365
+ --out typings
366
+ --out typings --out stubs
367
+ --out "typings,stubs"
368
+ --out "typings;stubs"
369
+ """
370
+ if not raw:
371
+ return [Path("typings")]
372
+ out: list[Path] = []
373
+ for item in raw:
374
+ parts = [p.strip() for p in item.replace(";", ",").split(",") if p.strip()]
375
+ out.extend(Path(p) for p in parts)
376
+
377
+ # de-dupe while preserving order
378
+ seen: set[Path] = set()
379
+ uniq: list[Path] = []
380
+ for p in out:
381
+ if p not in seen:
382
+ seen.add(p)
383
+ uniq.append(p)
384
+ return uniq
385
+
386
+
387
+ def main() -> int:
388
+ ap = argparse.ArgumentParser()
389
+ ap.add_argument("root", help="Root import (package or module) to scan, e.g. myapp")
390
+ ap.add_argument(
391
+ "--out",
392
+ action="append",
393
+ help="Also write overlay stubs to this directory (e.g. typings). "
394
+ "Repeatable or comma/semicolon-separated.",
395
+ )
396
+ ap.add_argument(
397
+ "--no-adjacent",
398
+ action="store_true",
399
+ help="Skip writing module.pyi next to module.py.",
400
+ )
401
+ args = ap.parse_args()
402
+
403
+ write_adjacent: bool = not args.no_adjacent
404
+ out_roots: list[Path] = _parse_out_args(args.out) if args.out else []
405
+ infos = _find_literal_enums(args.root)
406
+
407
+ by_module: dict[str, list[EnumInfo]] = {}
408
+ for e in infos:
409
+ by_module.setdefault(e.module, []).append(e)
410
+
411
+ written = 0
412
+ for module, enums in by_module.items():
413
+ # Adjacent stubs (module.pyi next to module.py) — default
414
+ if write_adjacent:
415
+ adjacent_text = _render_adjacent_preserving_stub(module, enums)
416
+ adj_path = _module_to_adjacent_stub_path(module)
417
+ if adj_path is not None:
418
+ _ensure_parent(adj_path)
419
+ adj_path.write_text(adjacent_text, encoding="utf-8")
420
+ written += 1
421
+
422
+ # Overlay stubs (e.g. typings/) — only when --out is given
423
+ if out_roots:
424
+ overlay_text = _render_overlay_stub_module(enums)
425
+ for stub_root in out_roots:
426
+ out_path = _module_to_stub_path(stub_root, module)
427
+ _ensure_parent(out_path)
428
+ out_path.write_text(overlay_text, encoding="utf-8")
429
+ written += 1
430
+
431
+ print(f"Wrote {written} stub file(s) for {len(infos)} LiteralEnum subclasses.")
432
+ return 0
433
+
434
+
435
+
436
+
437
+ if __name__ == "__main__":
438
+ raise SystemExit(main())
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: literalenum
3
+ Version: 0.1.1
4
+ Summary: A Python typing construct that provides namespaced literal constants with advanced typing features.
5
+ Project-URL: Homepage, https://github.com/modularizer/literalenum
6
+ Project-URL: Repository, https://github.com/modularizer/literalenum
7
+ Project-URL: Issues, https://github.com/modularizer/literalenum/issues
8
+ Author-email: Torin Halsted <modularizer@gmail.com>
9
+ License: Unlicense
10
+ License-File: LICENSE
11
+ Keywords: python,typehinting
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+
15
+ # LiteralEnum
16
+
17
+ **LiteralEnum** is an experiment/prototype for a proposed Python typing construct:
18
+ a *finite, named set of runtime literals* (usually strings) that type checkers can treat as
19
+ an **exhaustive `Literal[...]` union**.
20
+
21
+ > Status: Prototype / exploration for typing-sig discussion. Not an accepted PEP.
22
+
23
+ ## Why this exists
24
+ In typed Python today you often have to pick one:
25
+
26
+ - `Enum` / `StrEnum`: great runtime namespace, but APIs want callers to pass enum members instead of raw strings
27
+ - `Literal[...]`: great static checking, but no runtime namespace/iteration/validation
28
+
29
+ So people duplicate values or accept `str` and validate at runtime.
30
+
31
+ LiteralEnum aims to make the common case a single source of truth.
32
+
33
+ It’s designed for “protocol token” style values—HTTP methods, event names, command identifiers, config keys—where you want:
34
+ - **plain literals at runtime** (e.g. `"GET"`),
35
+ - **namespaced constants** (e.g. `HttpMethod.GET`), and
36
+ - **static exhaustiveness checking** (i.e. the type is equivalent to `Literal["GET", "POST", ...]`).
37
+
38
+ ---
39
+ ## Table of Contents
40
+ - [Typing discussion](https://discuss.python.org/t/proposal-literalenum-runtime-literals-with-static-exhaustiveness/106000) with the Python community
41
+ - [PEP.md](/PEP.md) is a draft PEP
42
+ - [LITMUS.md](/LITMUS.md) describes the project goals
43
+ - [TYPING_DISCUSSION.md](/TYPING_DISCUSSION.md) is shows drafts from the discussion
44
+ - [src/typing_literalenum.py](/src/typing_literalenum.py) is a the draft of the core runtime functionality (proposed to become `typing.LiteralEnum` or `typing_extensions.LiteralEnum`)
45
+ - [src/literalenum](/src/literalenum) is the full proposed PyPi module
46
+ - [src/literalenum/mypy_plugin.py](/src/literalenum/mypy_plugin.py) is an experimental **mypy plugin**
47
+ - [src/literalenum/samples](/src/literalenum/samples) shows sample usage
48
+ - [src/literalenum/stubgen.py](/src/literalenum/stubgen.py) provides tools for generating stubs, usable through CLI tool `lestub`
49
+
50
+ ---
51
+
52
+ ## Quickstart
53
+
54
+ ## Install
55
+
56
+ This repo is currently set up as a package under `src/`.
57
+
58
+ ```bash
59
+ #python -m venv .venv
60
+ #source .venv/bin/activate
61
+ pip install literalenum
62
+ ```
63
+
64
+ ```python
65
+ from literalenum import LiteralEnum
66
+
67
+ class HttpMethod(LiteralEnum):
68
+ GET = "GET"
69
+ POST = "POST"
70
+ DELETE = "DELETE"
71
+
72
+ def handle(method: HttpMethod) -> None:
73
+ print(f"{method=}")
74
+
75
+ handle("GET") # ✅ should type-check
76
+ handle(HttpMethod.GET) # ✅ should type-check
77
+ handle("git") # ❌ should be rejected by a type checker
78
+
79
+ assert HttpMethod.GET == "GET"
80
+ assert list(HttpMethod) == ["GET", "POST", "DELETE"]
81
+ assert "GET" in HttpMethod
82
+ print(HttpMethod.keys())
83
+ print(HttpMethod.values())
84
+ print(HttpMethod.mapping)
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Contributing / discussion
90
+
91
+ Actively looking for feedback!
92
+ Please comment at https://discuss.python.org/t/proposal-literalenum-runtime-literals-with-static-exhaustiveness/106000
93
+
94
+ It would be especially helpful if you are familiar with mypy/pright/pylance and have suggestions on how
95
+ a future Python version could support the type hinting goals.
96
+
97
+ ---
98
+
99
+ ## License
100
+
101
+ This project is released into the **public domain** under **The Unlicense**.
102
+
103
+ You are free to copy, modify, publish, use, compile, sell, or distribute this software,
104
+ either in source code form or as a compiled binary, for any purpose, commercial or non-commercial,
105
+ and by any means.
106
+
107
+ See the `LICENSE` file for full details.
108
+
@@ -0,0 +1,27 @@
1
+ literalenum/__init__.py,sha256=Cle3alhK2TPqkjKZ6yEFB3_2yv-y0EwtdYUnbkRG_-s,477
2
+ literalenum/literal_enum.py,sha256=OfB73GCHeZ5G1cAz3EA99zzZsmQ6jtL3xx3AKK-XP-0,3907
3
+ literalenum/mypy_plugin.py,sha256=JExHoi8NP_QJI0eESRed3TdR3DxEVrj7qA6q-bhj3KU,11020
4
+ literalenum/stubgen.py,sha256=U-Ch7-KyAoJXMmGFW5sLRNNszYnMd0T_3mjXHtAOWEo,14456
5
+ literalenum/compatibility_extensions/__init__.py,sha256=nIY32idsLpo9VyXtDREWMzLAfYUU7K7Zm3Cl69mWR0k,551
6
+ literalenum/compatibility_extensions/annotated.py,sha256=68TxWaCzMdcuFgnyWfWHUtzUClP5r99BMydSufdgcRA,146
7
+ literalenum/compatibility_extensions/bare_class.py,sha256=LXhT5BfIBJmfA8ZQmHfAVyF4IpM9911oUcj4xtonLFM,75
8
+ literalenum/compatibility_extensions/base_model.py,sha256=3-QWUnGLhL9R5-bEW8xpsRMaTo3tdLE7Lqmwnm8xmMg,667
9
+ literalenum/compatibility_extensions/click_choice.py,sha256=oaceRBUDXeOOpRkil-9R2pfBQCF7F5BN_G_x-s4VNeY,74
10
+ literalenum/compatibility_extensions/django_choices.py,sha256=hZVrZLhbmpHL5g12xlzKBbErQXLZjwOImfwCrumCrCQ,75
11
+ literalenum/compatibility_extensions/enum.py,sha256=VvWsWab8gjNKsNCGoBFr-Mj_fSWCY7ZHjUqwbZ1WGro,154
12
+ literalenum/compatibility_extensions/graphene_enum.py,sha256=cwovO1C82YxXXk1kRacZKGJKj44loYSFa_1jKgUiRY4,522
13
+ literalenum/compatibility_extensions/int_enum.py,sha256=JNs3BYZdnLxkcR7JaqSGwtr-01Wqfec8b6XUd2-JuSg,326
14
+ literalenum/compatibility_extensions/json_schema.py,sha256=hnIk8ACNXO3jqw1qu0RaVZPXvUGIBFTjwA4ish4tc3g,5197
15
+ literalenum/compatibility_extensions/literal.py,sha256=3R25lT4mlUzRdICgjjGyTdQy87PdbiwdWEP9sKUUPOc,223
16
+ literalenum/compatibility_extensions/random_choice.py,sha256=49-oSH6ebQJv0GIcYMDq_N9piI2kRG0RCY9bvWt7Q_s,88
17
+ literalenum/compatibility_extensions/regex.py,sha256=4XDtC8VlehMlcZkcE5L9nXbKkR4w2MXk411Q0OdfCyw,441
18
+ literalenum/compatibility_extensions/sqlalchemy_enum.py,sha256=DF_ce3Q7YNp98mTFXPsyWagQVzTTaczI-dRklOPh_LY,237
19
+ literalenum/compatibility_extensions/str_enum.py,sha256=y-VWFyaEDXmNicgyqzdEpsx-ImUdnydbF_YWhzvEVq0,329
20
+ literalenum/compatibility_extensions/strawberry_enum.py,sha256=mjg3Pyy0w_AggRRB_UtuWQiOguiG66eTN5E_rpwd7cg,526
21
+ typing_literalenum.py,sha256=ZoM0rbr-7Hp72uqohJ6enm-eIhUwAHTGBsb-FsQmXJs,24837
22
+ literalenum/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ literalenum-0.1.1.dist-info/METADATA,sha256=ZO4NgWWrqRpFZgexLjkyMX5wBVYmn2UMfS5KSdfXuiA,3957
24
+ literalenum-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
+ literalenum-0.1.1.dist-info/entry_points.txt,sha256=AK8je10LoLMOXuWLjXSqHV2rsTxPW9HlG7YTJGfwkCY,46
26
+ literalenum-0.1.1.dist-info/licenses/LICENSE,sha256=tQZYOMusRS38hVum5uAxSBrSxoQG9w0h6tkyE3RlPmw,1212
27
+ literalenum-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lestub = literalenum:lestub
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>