type-python 0.2.0__py3-none-win_amd64.whl → 0.3.1__py3-none-win_amd64.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.
- type_python-0.3.1.dist-info/METADATA +170 -0
- type_python-0.3.1.dist-info/RECORD +11 -0
- typepython/__init__.py +15 -2
- typepython/annotation_compat.py +251 -0
- typepython/bin/typepython.exe +0 -0
- type_python-0.2.0.dist-info/METADATA +0 -98
- type_python-0.2.0.dist-info/RECORD +0 -11
- {type_python-0.2.0.dist-info → type_python-0.3.1.dist-info}/WHEEL +0 -0
- {type_python-0.2.0.dist-info → type_python-0.3.1.dist-info}/entry_points.txt +0 -0
- {type_python-0.2.0.dist-info → type_python-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {type_python-0.2.0.dist-info → type_python-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: type-python
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: A statically-typed authoring language that compiles to standard Python
|
|
5
|
+
Author: unadlib
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/type-python/type-python
|
|
8
|
+
Project-URL: Repository, https://github.com/type-python/type-python
|
|
9
|
+
Project-URL: Documentation, https://github.com/type-python/type-python/tree/main/docs
|
|
10
|
+
Project-URL: Issues, https://github.com/type-python/type-python/issues
|
|
11
|
+
Keywords: typepython,type-checking,compiler,python,static-typing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Rust
|
|
20
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img src="https://raw.githubusercontent.com/type-python/type-python/main/logo.png" alt="TypePython" width="160" />
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<h1 align="center">TypePython</h1>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<strong>Write richer types in <code>.tpy</code>. Ship plain <code>.py</code> + <code>.pyi</code>.</strong>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
TypePython is a typed dialect of Python that compiles to standard `.py` + `.pyi`.
|
|
41
|
+
It brings TypeScript-class ergonomics — `sealed` classes, exhaustive `match`,
|
|
42
|
+
strict null checks, `unknown`, `interface`, `data class` — to a language whose
|
|
43
|
+
output runs anywhere CPython runs.
|
|
44
|
+
|
|
45
|
+
**No custom runtime. No per-checker plugin. No vendor lock-in.**
|
|
46
|
+
|
|
47
|
+
> Status: **alpha** (v0.3.0). The compiler core, type checker, and LSP are
|
|
48
|
+
> stable; framework adapters and runtime validators are in prototype.
|
|
49
|
+
> Bug reports and contributions are very welcome.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install type-python
|
|
55
|
+
typepython init --dir hello && cd hello
|
|
56
|
+
typepython check --project .
|
|
57
|
+
typepython build --project .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
You now have `.typepython/build/` with `.py` + `.pyi` + `py.typed` ready for
|
|
61
|
+
any Python interpreter, IDE, or downstream type checker.
|
|
62
|
+
|
|
63
|
+
- **Wheels** are prebuilt for Windows AMD64, macOS x86_64, macOS arm64, and Linux x86_64.
|
|
64
|
+
Other platforms fall back to source and need Rust + `cargo`.
|
|
65
|
+
- **Python**: the package bridge supports 3.9+; generated projects target Python 3.10 through 3.14.
|
|
66
|
+
|
|
67
|
+
## See it in 15 lines
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# src/app/expr.tpy
|
|
71
|
+
|
|
72
|
+
sealed class Expr:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
class Num(Expr): value: int
|
|
76
|
+
class Add(Expr): left: Expr; right: Expr
|
|
77
|
+
class Neg(Expr): operand: Expr
|
|
78
|
+
|
|
79
|
+
def evaluate(expr: Expr) -> int:
|
|
80
|
+
match expr:
|
|
81
|
+
case Num(value=v): return v
|
|
82
|
+
case Add(left=l, right=r): return evaluate(l) + evaluate(r)
|
|
83
|
+
case Neg(operand=o): return -evaluate(o)
|
|
84
|
+
# No default branch needed — the compiler proves the match is exhaustive.
|
|
85
|
+
# Add a fourth subclass and TypePython tells you exactly where to update.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`typepython build` lowers that to ordinary Python and writes a matching `.pyi`
|
|
89
|
+
that any modern type checker can consume — no TypePython runtime required.
|
|
90
|
+
|
|
91
|
+
## What you write vs. what ships
|
|
92
|
+
|
|
93
|
+
| You write (`.tpy`) | TypePython emits (`.py` / `.pyi`) |
|
|
94
|
+
| ------------------------------------- | --------------------------------------------------------- |
|
|
95
|
+
| `interface Drawable:` | `class Drawable(Protocol):` |
|
|
96
|
+
| `data class User:` | `@dataclass class User:` |
|
|
97
|
+
| `sealed class Expr:` | ordinary class + `tpy:sealed` marker |
|
|
98
|
+
| `overload def parse(...)` | `@overload def parse(...)` |
|
|
99
|
+
| `typealias Pair[T] = tuple[T, T]` | `T = TypeVar("T")` + `Pair: TypeAlias = tuple[T, T]` |
|
|
100
|
+
| `def first[T](xs: list[T]) -> T:` | inline generic on 3.13+, `TypeVar`-based on 3.10 – 3.12 |
|
|
101
|
+
| `unsafe: eval(expr)` | valid Python, marker erased — runtime behavior preserved |
|
|
102
|
+
| `unknown` (must narrow before use) | `object` in `.pyi` |
|
|
103
|
+
| `Partial[Config]` / `Pick[Config, …]` | expanded `TypedDict` shapes (PEP 655 / 705) |
|
|
104
|
+
|
|
105
|
+
## Why not just mypy / pyright / PEP 695?
|
|
106
|
+
|
|
107
|
+
| Capability | mypy strict | pyright strict | PEP 695 `.py` | **TypePython `.tpy`** |
|
|
108
|
+
| ------------------------------------------------------- | :---------: | :------------: | :-----------: | :-------------------: |
|
|
109
|
+
| `sealed class` + compiler-proved exhaustiveness | — | — | — | ✅ |
|
|
110
|
+
| `unknown` — safe dynamic boundary, must narrow | — | — | — | ✅ |
|
|
111
|
+
| `unsafe:` audit fence for `eval` / `exec` / `setattr` | — | — | — | ✅ |
|
|
112
|
+
| First-class `interface` / `data class` / `typealias` | via | via | via | keyword |
|
|
113
|
+
| Inline generics `def f[T]` on **any** target ≥ 3.10 | 3.12+ | 3.12+ | 3.12+ | ✅ |
|
|
114
|
+
| `TypedDict` transforms (`Partial`, `Pick`, `Readonly`…) | — | — | — | ✅ |
|
|
115
|
+
| Output consumed by mypy / pyright / ty unmodified | N/A | N/A | ✅ | ✅ |
|
|
116
|
+
|
|
117
|
+
TypePython doesn't replace those checkers. It sits **one step earlier**: you
|
|
118
|
+
author in `.tpy`, the compiler emits standard typed Python that those tools
|
|
119
|
+
then consume normally.
|
|
120
|
+
|
|
121
|
+
## What you also get
|
|
122
|
+
|
|
123
|
+
- **Rust-native, incremental compiler** — public-API fingerprints skip
|
|
124
|
+
downstream rechecks when only a body changes.
|
|
125
|
+
- **Full toolchain** — `init`, `check`, `build`, `watch`, `clean`, `verify`,
|
|
126
|
+
`compat`, `api-diff`, `type-health`, `migrate`, `lsp`.
|
|
127
|
+
- **LSP server** — hover, go to definition, references, rename, completions,
|
|
128
|
+
signature help, real-time diagnostics, and code-action quick fixes.
|
|
129
|
+
Bring your own LSP-capable editor (VS Code, Neovim, Helix, Sublime, Emacs).
|
|
130
|
+
- **Standard, portable output** — emitted `.py` + `.pyi` work with mypy,
|
|
131
|
+
pyright, and ty out of the box. PEP 561 `py.typed` is written automatically.
|
|
132
|
+
- **Mixed projects** — `.tpy`, `.py`, and `.pyi` live in the same source tree.
|
|
133
|
+
`.py` is pass-through; `.pyi` is stub-authoritative.
|
|
134
|
+
|
|
135
|
+
## For library authors
|
|
136
|
+
|
|
137
|
+
Five commands aimed at people who *publish* typed Python:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
typepython build --project .
|
|
141
|
+
typepython verify --project . --checker-preset all
|
|
142
|
+
typepython compat --project . --profile library-portable
|
|
143
|
+
typepython api-diff dist/previous.whl .typepython/build
|
|
144
|
+
typepython type-health --project . --fail-under 85
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
These catch missing/stale `py.typed`, wheel ↔ build-tree drift, multi-checker
|
|
148
|
+
portability gaps, public-API drift between releases, and runtime-annotation
|
|
149
|
+
hazards for frameworks that introspect annotations.
|
|
150
|
+
|
|
151
|
+
## Documentation
|
|
152
|
+
|
|
153
|
+
- [Getting Started](https://github.com/type-python/type-python/blob/main/docs/getting-started.md)
|
|
154
|
+
- [Syntax Guide](https://github.com/type-python/type-python/blob/main/docs/syntax-guide.md)
|
|
155
|
+
- [Type System](https://github.com/type-python/type-python/blob/main/docs/type-system.md)
|
|
156
|
+
- [Configuration](https://github.com/type-python/type-python/blob/main/docs/configuration.md)
|
|
157
|
+
- [CLI Reference](https://github.com/type-python/type-python/blob/main/docs/cli-reference.md)
|
|
158
|
+
- [Diagnostics (TPYxxxx codes)](https://github.com/type-python/type-python/blob/main/docs/diagnostics.md)
|
|
159
|
+
- [LSP Integration](https://github.com/type-python/type-python/blob/main/docs/lsp.md)
|
|
160
|
+
- [Interoperability](https://github.com/type-python/type-python/blob/main/docs/interop.md)
|
|
161
|
+
- [Migration Guide](https://github.com/type-python/type-python/blob/main/docs/migration-guide.md)
|
|
162
|
+
- [Framework Adapters](https://github.com/type-python/type-python/blob/main/docs/framework-adapters.md)
|
|
163
|
+
- [Language Spec v1](https://github.com/type-python/type-python/blob/main/docs/spec/language-spec-v1.md)
|
|
164
|
+
|
|
165
|
+
## Links
|
|
166
|
+
|
|
167
|
+
- [Repository](https://github.com/type-python/type-python)
|
|
168
|
+
- [Issues / bug reports](https://github.com/type-python/type-python/issues)
|
|
169
|
+
- [Contributing](https://github.com/type-python/type-python/blob/main/docs/contributing.md)
|
|
170
|
+
- [License (MIT)](https://github.com/type-python/type-python/blob/main/LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type_python-0.3.1.dist-info/licenses/LICENSE,sha256=BMHUZ3id_LOrEdv_GmAbL1ttlKsqjZz14f1_M1tn73k,1089
|
|
2
|
+
typepython/__init__.py,sha256=XDqUGTG_99WfcbfL1J1VlAx6oDcw72TcredigzGC4-s,532
|
|
3
|
+
typepython/__main__.py,sha256=m8cggQyLAOSPtb8r-uUv_g53whMoPXtF2NX9yLuPyLg,89
|
|
4
|
+
typepython/_runner.py,sha256=DySWPTud0FIAv2-0xhmwAyt_kk3w3kFJWK8FcYSe858,1777
|
|
5
|
+
typepython/annotation_compat.py,sha256=F_dLOYhFHtOyGn-vTn22VayGMZKfGF4OoM-YvD_352I,13916
|
|
6
|
+
typepython/bin/typepython.exe,sha256=YcvzJDY_bWlLkEO5RMwBirkH_BEjS31_UPvF4XjImO8,13122048
|
|
7
|
+
type_python-0.3.1.dist-info/METADATA,sha256=HxPj62MNQ0XuJ2LMGPm0NqcSPHDUB5qIM229yZzClj4,8817
|
|
8
|
+
type_python-0.3.1.dist-info/WHEEL,sha256=GjDPPQwEcripVP6P2r3RxLa-h5Lb9ifGB7FYYtbLDT0,98
|
|
9
|
+
type_python-0.3.1.dist-info/entry_points.txt,sha256=B5Yjdi-RWaeRy7YUcni1lU_ya2lVZXM8llSFNSKEq0U,56
|
|
10
|
+
type_python-0.3.1.dist-info/top_level.txt,sha256=JFRFjt3AXvRQ99SKMxUVu9e3TOp2QpspvE-jk85uMjU,11
|
|
11
|
+
type_python-0.3.1.dist-info/RECORD,,
|
typepython/__init__.py
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
from ._runner import main
|
|
2
|
-
from .annotation_compat import
|
|
2
|
+
from .annotation_compat import (
|
|
3
|
+
AnnotationAudit,
|
|
4
|
+
AnnotationAuditFinding,
|
|
5
|
+
AnnotationConsumer,
|
|
6
|
+
AnnotationFormat,
|
|
7
|
+
AnnotationSupport,
|
|
8
|
+
audit_source,
|
|
9
|
+
get_annotations,
|
|
10
|
+
supported_formats,
|
|
11
|
+
)
|
|
3
12
|
|
|
4
13
|
__all__ = [
|
|
5
14
|
"__version__",
|
|
6
15
|
"AnnotationFormat",
|
|
16
|
+
"AnnotationAudit",
|
|
17
|
+
"AnnotationAuditFinding",
|
|
18
|
+
"AnnotationConsumer",
|
|
7
19
|
"AnnotationSupport",
|
|
20
|
+
"audit_source",
|
|
8
21
|
"get_annotations",
|
|
9
22
|
"main",
|
|
10
23
|
"supported_formats",
|
|
11
24
|
]
|
|
12
25
|
|
|
13
|
-
__version__ = "0.
|
|
26
|
+
__version__ = "0.3.1"
|
typepython/annotation_compat.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import sys
|
|
5
|
+
import ast
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from types import ModuleType
|
|
@@ -28,12 +29,51 @@ class AnnotationSupport:
|
|
|
28
29
|
string: bool
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
class AnnotationConsumer(str, Enum):
|
|
33
|
+
TYPING_GET_TYPE_HINTS = "typing.get_type_hints"
|
|
34
|
+
INSPECT_GET_ANNOTATIONS = "inspect.get_annotations"
|
|
35
|
+
ANNOTATIONLIB_GET_ANNOTATIONS = "annotationlib.get_annotations"
|
|
36
|
+
DATACLASS_DECORATOR = "dataclasses.dataclass"
|
|
37
|
+
FASTAPI_ROUTE_DECORATOR = "fastapi.route_decorator"
|
|
38
|
+
FASTAPI_DEPENDS = "fastapi.Depends"
|
|
39
|
+
PYDANTIC_BASEMODEL = "pydantic.BaseModel"
|
|
40
|
+
PYDANTIC_FIELD = "pydantic.Field"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class AnnotationAuditFinding:
|
|
45
|
+
code: str
|
|
46
|
+
message: str
|
|
47
|
+
line: int
|
|
48
|
+
column: int
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class AnnotationAudit:
|
|
53
|
+
consumers: tuple[AnnotationConsumer, ...]
|
|
54
|
+
findings: tuple[AnnotationAuditFinding, ...]
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def safe_for_runtime_introspection(self) -> bool:
|
|
58
|
+
return not self.findings
|
|
59
|
+
|
|
60
|
+
|
|
31
61
|
def supported_formats() -> AnnotationSupport:
|
|
32
62
|
if HAS_ANNOTATIONLIB:
|
|
33
63
|
return AnnotationSupport(value=True, forwardref=True, string=True)
|
|
34
64
|
return AnnotationSupport(value=True, forwardref=False, string=False)
|
|
35
65
|
|
|
36
66
|
|
|
67
|
+
def audit_source(source: str, *, filename: str = "<source>") -> AnnotationAudit:
|
|
68
|
+
tree = ast.parse(source, filename=filename)
|
|
69
|
+
visitor = _AnnotationAuditVisitor()
|
|
70
|
+
visitor.visit(tree)
|
|
71
|
+
return AnnotationAudit(
|
|
72
|
+
consumers=tuple(sorted(visitor.consumers, key=lambda consumer: consumer.value)),
|
|
73
|
+
findings=tuple(visitor.findings),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
37
77
|
def get_annotations(
|
|
38
78
|
obj: Any,
|
|
39
79
|
*,
|
|
@@ -140,3 +180,214 @@ def _legacy_eval_namespaces(
|
|
|
140
180
|
module = sys.modules.get(getattr(obj, "__module__", ""))
|
|
141
181
|
namespace = vars(module) if module is not None else {}
|
|
142
182
|
return namespace, namespace
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class _AnnotationAuditVisitor(ast.NodeVisitor):
|
|
186
|
+
def __init__(self) -> None:
|
|
187
|
+
self.consumers: set[AnnotationConsumer] = set()
|
|
188
|
+
self.findings: list[AnnotationAuditFinding] = []
|
|
189
|
+
self._type_checking_only_names: set[str] = set()
|
|
190
|
+
self._scope_depth = 0
|
|
191
|
+
|
|
192
|
+
def visit_If(self, node: ast.If) -> None:
|
|
193
|
+
if _is_type_checking_guard(node.test):
|
|
194
|
+
self._record_type_checking_only_imports(node.body)
|
|
195
|
+
for statement in node.orelse:
|
|
196
|
+
self.visit(statement)
|
|
197
|
+
return
|
|
198
|
+
self.generic_visit(node)
|
|
199
|
+
|
|
200
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
201
|
+
for decorator in node.decorator_list:
|
|
202
|
+
consumer = _decorator_consumer(decorator)
|
|
203
|
+
if consumer is not None:
|
|
204
|
+
self.consumers.add(consumer)
|
|
205
|
+
self._visit_callable_or_class(node)
|
|
206
|
+
|
|
207
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
208
|
+
for decorator in node.decorator_list:
|
|
209
|
+
consumer = _decorator_consumer(decorator)
|
|
210
|
+
if consumer is not None:
|
|
211
|
+
self.consumers.add(consumer)
|
|
212
|
+
self._visit_callable_or_class(node)
|
|
213
|
+
|
|
214
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
215
|
+
if any(_is_pydantic_base(base) for base in node.bases):
|
|
216
|
+
self.consumers.add(AnnotationConsumer.PYDANTIC_BASEMODEL)
|
|
217
|
+
for decorator in node.decorator_list:
|
|
218
|
+
consumer = _decorator_consumer(decorator)
|
|
219
|
+
if consumer is not None:
|
|
220
|
+
self.consumers.add(consumer)
|
|
221
|
+
self._visit_callable_or_class(node)
|
|
222
|
+
|
|
223
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
224
|
+
consumer = _call_consumer(node.func)
|
|
225
|
+
if consumer is not None:
|
|
226
|
+
self.consumers.add(consumer)
|
|
227
|
+
self.generic_visit(node)
|
|
228
|
+
|
|
229
|
+
def _visit_callable_or_class(
|
|
230
|
+
self,
|
|
231
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
|
|
232
|
+
) -> None:
|
|
233
|
+
if self._scope_depth > 0:
|
|
234
|
+
self._record_nested_annotation_findings(node)
|
|
235
|
+
self._record_type_checking_annotation_findings(node)
|
|
236
|
+
self._scope_depth += 1
|
|
237
|
+
self.generic_visit(node)
|
|
238
|
+
self._scope_depth -= 1
|
|
239
|
+
|
|
240
|
+
def _record_type_checking_only_imports(self, statements: list[ast.stmt]) -> None:
|
|
241
|
+
for statement in statements:
|
|
242
|
+
if isinstance(statement, ast.Import):
|
|
243
|
+
for alias in statement.names:
|
|
244
|
+
self._type_checking_only_names.add(alias.asname or alias.name.split(".", 1)[0])
|
|
245
|
+
elif isinstance(statement, ast.ImportFrom):
|
|
246
|
+
for alias in statement.names:
|
|
247
|
+
if alias.name == "*":
|
|
248
|
+
continue
|
|
249
|
+
self._type_checking_only_names.add(alias.asname or alias.name)
|
|
250
|
+
elif isinstance(statement, ast.If) and _is_type_checking_guard(statement.test):
|
|
251
|
+
self._record_type_checking_only_imports(statement.body)
|
|
252
|
+
|
|
253
|
+
def _record_type_checking_annotation_findings(
|
|
254
|
+
self,
|
|
255
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
|
|
256
|
+
) -> None:
|
|
257
|
+
if not self._type_checking_only_names:
|
|
258
|
+
return
|
|
259
|
+
for annotation in _node_annotations(node):
|
|
260
|
+
names = _annotation_names(annotation)
|
|
261
|
+
blocked = sorted(names & self._type_checking_only_names)
|
|
262
|
+
if not blocked:
|
|
263
|
+
continue
|
|
264
|
+
self.findings.append(
|
|
265
|
+
AnnotationAuditFinding(
|
|
266
|
+
code="TPY-A002",
|
|
267
|
+
message=(
|
|
268
|
+
"annotation references TYPE_CHECKING-only import(s) "
|
|
269
|
+
f"{', '.join(blocked)}; runtime annotation evaluation can fail or "
|
|
270
|
+
"reintroduce import cycles"
|
|
271
|
+
),
|
|
272
|
+
line=annotation.lineno,
|
|
273
|
+
column=annotation.col_offset + 1,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _record_nested_annotation_findings(
|
|
278
|
+
self,
|
|
279
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
|
|
280
|
+
) -> None:
|
|
281
|
+
annotations = _node_annotations(node)
|
|
282
|
+
for annotation in annotations:
|
|
283
|
+
if _annotation_mentions_local_name(annotation):
|
|
284
|
+
self.findings.append(
|
|
285
|
+
AnnotationAuditFinding(
|
|
286
|
+
code="TPY-A001",
|
|
287
|
+
message=(
|
|
288
|
+
"annotation inside a local scope may be unavailable to runtime "
|
|
289
|
+
"consumers such as typing.get_type_hints or annotationlib.get_annotations"
|
|
290
|
+
),
|
|
291
|
+
line=annotation.lineno,
|
|
292
|
+
column=annotation.col_offset + 1,
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _node_annotations(
|
|
298
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef,
|
|
299
|
+
) -> list[ast.expr]:
|
|
300
|
+
annotations: list[ast.expr] = []
|
|
301
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
302
|
+
for arg in [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs]:
|
|
303
|
+
if arg.annotation is not None:
|
|
304
|
+
annotations.append(arg.annotation)
|
|
305
|
+
if node.args.vararg is not None and node.args.vararg.annotation is not None:
|
|
306
|
+
annotations.append(node.args.vararg.annotation)
|
|
307
|
+
if node.args.kwarg is not None and node.args.kwarg.annotation is not None:
|
|
308
|
+
annotations.append(node.args.kwarg.annotation)
|
|
309
|
+
if node.returns is not None:
|
|
310
|
+
annotations.append(node.returns)
|
|
311
|
+
return annotations
|
|
312
|
+
|
|
313
|
+
for statement in node.body:
|
|
314
|
+
if isinstance(statement, ast.AnnAssign) and statement.annotation is not None:
|
|
315
|
+
annotations.append(statement.annotation)
|
|
316
|
+
return annotations
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _annotation_mentions_local_name(annotation: ast.expr) -> bool:
|
|
320
|
+
if isinstance(annotation, ast.Constant) and isinstance(annotation.value, str):
|
|
321
|
+
return True
|
|
322
|
+
return any(isinstance(child, ast.Name) for child in ast.walk(annotation))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _annotation_names(annotation: ast.expr) -> set[str]:
|
|
326
|
+
if isinstance(annotation, ast.Constant) and isinstance(annotation.value, str):
|
|
327
|
+
try:
|
|
328
|
+
annotation = ast.parse(annotation.value, mode="eval").body
|
|
329
|
+
except SyntaxError:
|
|
330
|
+
return set()
|
|
331
|
+
return {child.id for child in ast.walk(annotation) if isinstance(child, ast.Name)}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _is_type_checking_guard(test: ast.expr) -> bool:
|
|
335
|
+
return _dotted_name(test) in {"TYPE_CHECKING", "typing.TYPE_CHECKING"}
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _call_consumer(func: ast.expr) -> AnnotationConsumer | None:
|
|
339
|
+
dotted = _dotted_name(func)
|
|
340
|
+
if dotted in {"typing.get_type_hints", "get_type_hints"}:
|
|
341
|
+
return AnnotationConsumer.TYPING_GET_TYPE_HINTS
|
|
342
|
+
if dotted == "inspect.get_annotations":
|
|
343
|
+
return AnnotationConsumer.INSPECT_GET_ANNOTATIONS
|
|
344
|
+
if dotted == "annotationlib.get_annotations":
|
|
345
|
+
return AnnotationConsumer.ANNOTATIONLIB_GET_ANNOTATIONS
|
|
346
|
+
if dotted in {"Depends", "fastapi.Depends"}:
|
|
347
|
+
return AnnotationConsumer.FASTAPI_DEPENDS
|
|
348
|
+
if dotted in {"Field", "pydantic.Field"}:
|
|
349
|
+
return AnnotationConsumer.PYDANTIC_FIELD
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _decorator_consumer(decorator: ast.expr) -> AnnotationConsumer | None:
|
|
354
|
+
dotted = _dotted_name(decorator.func if isinstance(decorator, ast.Call) else decorator)
|
|
355
|
+
if dotted in {"dataclass", "dataclasses.dataclass"}:
|
|
356
|
+
return AnnotationConsumer.DATACLASS_DECORATOR
|
|
357
|
+
if _is_fastapi_route_decorator_name(dotted):
|
|
358
|
+
return AnnotationConsumer.FASTAPI_ROUTE_DECORATOR
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _is_pydantic_base(expr: ast.expr) -> bool:
|
|
363
|
+
dotted = _dotted_name(expr)
|
|
364
|
+
return dotted in {"BaseModel", "pydantic.BaseModel"}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _is_fastapi_route_decorator_name(dotted: str | None) -> bool:
|
|
368
|
+
if dotted is None:
|
|
369
|
+
return False
|
|
370
|
+
route_suffixes = {
|
|
371
|
+
"get",
|
|
372
|
+
"post",
|
|
373
|
+
"put",
|
|
374
|
+
"delete",
|
|
375
|
+
"patch",
|
|
376
|
+
"options",
|
|
377
|
+
"head",
|
|
378
|
+
"websocket",
|
|
379
|
+
"api_route",
|
|
380
|
+
}
|
|
381
|
+
suffix = dotted.rsplit(".", 1)[-1]
|
|
382
|
+
return suffix in route_suffixes
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _dotted_name(expr: ast.expr) -> str | None:
|
|
386
|
+
if isinstance(expr, ast.Name):
|
|
387
|
+
return expr.id
|
|
388
|
+
if isinstance(expr, ast.Attribute):
|
|
389
|
+
parent = _dotted_name(expr.value)
|
|
390
|
+
if parent is None:
|
|
391
|
+
return None
|
|
392
|
+
return f"{parent}.{expr.attr}"
|
|
393
|
+
return None
|
typepython/bin/typepython.exe
CHANGED
|
Binary file
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: type-python
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A statically-typed authoring language that compiles to standard Python
|
|
5
|
-
Author: unadlib
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/type-python/type-python
|
|
8
|
-
Project-URL: Repository, https://github.com/type-python/type-python
|
|
9
|
-
Project-URL: Documentation, https://github.com/type-python/type-python/tree/main/docs
|
|
10
|
-
Project-URL: Issues, https://github.com/type-python/type-python/issues
|
|
11
|
-
Keywords: typepython,type-checking,compiler,python,static-typing
|
|
12
|
-
Classifier: Development Status :: 3 - Alpha
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Rust
|
|
20
|
-
Classifier: Topic :: Software Development :: Compilers
|
|
21
|
-
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
-
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Python: >=3.9
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
License-File: LICENSE
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
|
-
# TypePython
|
|
29
|
-
|
|
30
|
-
**A statically-typed authoring language that compiles to standard Python.**
|
|
31
|
-
|
|
32
|
-
TypePython lets you write `.tpy` source files with features such as `interface`, `data class`, `sealed class`, inline generics, and strict null safety. The compiler emits standard `.py` and `.pyi` files that run on ordinary Python interpreters and type-check with mypy or pyright.
|
|
33
|
-
|
|
34
|
-
## Install
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
pip install type-python
|
|
38
|
-
typepython --help
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Published wheels are platform-specific because they bundle the Rust CLI binary. Supported releases publish prebuilt wheels for Windows AMD64, macOS x86_64, macOS arm64, and Linux x86_64, so those platforms can install and run TypePython without Rust. Other platforms fall back to the source distribution and require a Rust toolchain with `cargo`.
|
|
42
|
-
|
|
43
|
-
The Python package bridge supports Python 3.9+. Generated TypePython projects currently target Python 3.10, 3.11, or 3.12.
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
46
|
-
|
|
47
|
-
Create a project:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
typepython init --dir my-project
|
|
51
|
-
cd my-project
|
|
52
|
-
typepython check --project .
|
|
53
|
-
typepython build --project .
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Example source:
|
|
57
|
-
|
|
58
|
-
```python
|
|
59
|
-
sealed class Expr:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
data class Num(Expr):
|
|
63
|
-
value: int
|
|
64
|
-
|
|
65
|
-
data class Add(Expr):
|
|
66
|
-
left: Expr
|
|
67
|
-
right: Expr
|
|
68
|
-
|
|
69
|
-
def evaluate(expr: Expr) -> int:
|
|
70
|
-
match expr:
|
|
71
|
-
case Num(value=v):
|
|
72
|
-
return v
|
|
73
|
-
case Add(left=l, right=r):
|
|
74
|
-
return evaluate(l) + evaluate(r)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## What You Get
|
|
78
|
-
|
|
79
|
-
- Standard `.py` and `.pyi` output with no custom runtime
|
|
80
|
-
- Type system features such as `unknown`, strict nulls, sealed exhaustiveness, `TypedDict` utilities, and generic defaults
|
|
81
|
-
- CLI commands for `init`, `check`, `build`, `watch`, `clean`, `verify`, `lsp`, and `migrate`
|
|
82
|
-
- Interoperability with standard Python typing tools and package publishing workflows
|
|
83
|
-
|
|
84
|
-
## Documentation
|
|
85
|
-
|
|
86
|
-
- Getting started: <https://github.com/type-python/type-python/blob/main/docs/getting-started.md>
|
|
87
|
-
- Syntax guide: <https://github.com/type-python/type-python/blob/main/docs/syntax-guide.md>
|
|
88
|
-
- Type system: <https://github.com/type-python/type-python/blob/main/docs/type-system.md>
|
|
89
|
-
- Configuration: <https://github.com/type-python/type-python/blob/main/docs/configuration.md>
|
|
90
|
-
- CLI reference: <https://github.com/type-python/type-python/blob/main/docs/cli-reference.md>
|
|
91
|
-
- Interoperability: <https://github.com/type-python/type-python/blob/main/docs/interop.md>
|
|
92
|
-
- Language spec: <https://github.com/type-python/type-python/blob/main/docs/spec/language-spec-v1.md>
|
|
93
|
-
|
|
94
|
-
## Project Links
|
|
95
|
-
|
|
96
|
-
- Repository: <https://github.com/type-python/type-python>
|
|
97
|
-
- Issues: <https://github.com/type-python/type-python/issues>
|
|
98
|
-
- License: <https://github.com/type-python/type-python/blob/main/LICENSE>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
type_python-0.2.0.dist-info/licenses/LICENSE,sha256=BMHUZ3id_LOrEdv_GmAbL1ttlKsqjZz14f1_M1tn73k,1089
|
|
2
|
-
typepython/__init__.py,sha256=kCK2IS4kVa6N1RsNikfR1sn_tnZ-v_RkAvNCmqMnYgA,308
|
|
3
|
-
typepython/__main__.py,sha256=m8cggQyLAOSPtb8r-uUv_g53whMoPXtF2NX9yLuPyLg,89
|
|
4
|
-
typepython/_runner.py,sha256=DySWPTud0FIAv2-0xhmwAyt_kk3w3kFJWK8FcYSe858,1777
|
|
5
|
-
typepython/annotation_compat.py,sha256=VKUJEAj9NdDWr1j5cB2OIWlupmDyWVYvQkpJ2Jer2m8,4223
|
|
6
|
-
typepython/bin/typepython.exe,sha256=nVmVjeYOX1bMA_nZbP5M82hlMpsmEaN-G-XnBtQRYKE,11372544
|
|
7
|
-
type_python-0.2.0.dist-info/METADATA,sha256=Qn5P8Hw8w6N-1sX8H__xcIip6rlFRG2DymiaLI-wDnI,3930
|
|
8
|
-
type_python-0.2.0.dist-info/WHEEL,sha256=GjDPPQwEcripVP6P2r3RxLa-h5Lb9ifGB7FYYtbLDT0,98
|
|
9
|
-
type_python-0.2.0.dist-info/entry_points.txt,sha256=B5Yjdi-RWaeRy7YUcni1lU_ya2lVZXM8llSFNSKEq0U,56
|
|
10
|
-
type_python-0.2.0.dist-info/top_level.txt,sha256=JFRFjt3AXvRQ99SKMxUVu9e3TOp2QpspvE-jk85uMjU,11
|
|
11
|
-
type_python-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|