nene2-python 1.0.0__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.
- nene2/__init__.py +1 -0
- nene2/auth/__init__.py +16 -0
- nene2/auth/api_key.py +39 -0
- nene2/auth/bearer_token.py +51 -0
- nene2/auth/exceptions.py +8 -0
- nene2/auth/interfaces.py +23 -0
- nene2/auth/local_verifier.py +17 -0
- nene2/config/__init__.py +5 -0
- nene2/config/settings.py +72 -0
- nene2/database/__init__.py +15 -0
- nene2/database/exceptions.py +5 -0
- nene2/database/health.py +24 -0
- nene2/database/interfaces.py +51 -0
- nene2/database/sqlalchemy_executor.py +128 -0
- nene2/http/__init__.py +14 -0
- nene2/http/health.py +20 -0
- nene2/http/pagination.py +93 -0
- nene2/http/problem_details.py +37 -0
- nene2/log/__init__.py +5 -0
- nene2/log/setup.py +49 -0
- nene2/mcp/__init__.py +11 -0
- nene2/mcp/http_client.py +97 -0
- nene2/mcp/server.py +25 -0
- nene2/middleware/__init__.py +20 -0
- nene2/middleware/domain_exception.py +18 -0
- nene2/middleware/error_handler.py +112 -0
- nene2/middleware/request_id.py +45 -0
- nene2/middleware/request_logging.py +34 -0
- nene2/middleware/request_size_limit.py +52 -0
- nene2/middleware/security_headers.py +37 -0
- nene2/middleware/throttle.py +72 -0
- nene2/py.typed +0 -0
- nene2/use_case/__init__.py +5 -0
- nene2/use_case/protocols.py +24 -0
- nene2/validation/__init__.py +5 -0
- nene2/validation/exceptions.py +34 -0
- nene2_python-1.0.0.dist-info/METADATA +211 -0
- nene2_python-1.0.0.dist-info/RECORD +40 -0
- nene2_python-1.0.0.dist-info/WHEEL +4 -0
- nene2_python-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nene2-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NENE2 Python — minimal API framework following NENE2's design philosophy
|
|
5
|
+
Project-URL: Homepage, https://github.com/hideyukiMORI/nene2-python
|
|
6
|
+
Project-URL: Repository, https://github.com/hideyukiMORI/nene2-python
|
|
7
|
+
Project-URL: Documentation, https://github.com/hideyukiMORI/nene2-python/tree/main/docs
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/hideyukiMORI/nene2-python/issues
|
|
9
|
+
Author-email: hideyukiMORI <info.xion.cc@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,clean-architecture,fastapi,framework,mcp,pydantic,sqlalchemy
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: FastAPI
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: alembic>=1.18.4
|
|
26
|
+
Requires-Dist: fastapi>=0.115
|
|
27
|
+
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: mcp>=1.0
|
|
29
|
+
Requires-Dist: pydantic-settings>=2.6
|
|
30
|
+
Requires-Dist: pydantic>=2.9
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.12
|
|
32
|
+
Requires-Dist: pyyaml>=6.0
|
|
33
|
+
Requires-Dist: sqlalchemy>=2.0.49
|
|
34
|
+
Requires-Dist: structlog>=24.0
|
|
35
|
+
Requires-Dist: uvicorn[standard]>=0.32
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
38
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
39
|
+
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
43
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# nene2-python
|
|
47
|
+
|
|
48
|
+
A Python reference framework implementing the [NENE2](https://github.com/hideyukiMORI/NENE2) design philosophy — clean architecture, security-first, and AI-readable code.
|
|
49
|
+
|
|
50
|
+
[](https://github.com/hideyukiMORI/nene2-python/actions/workflows/ci.yml)
|
|
51
|
+
[](https://www.python.org/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **FastAPI + Pydantic v2** — modern Python API stack with automatic OpenAPI docs
|
|
59
|
+
- **Clean Architecture** — UseCase / Domain layer fully decoupled from HTTP and DB
|
|
60
|
+
- **`mypy --strict`** — equivalent to PHPStan level 8 type safety
|
|
61
|
+
- **ruff** — lint and format in one tool (replaces flake8, isort, black, bandit)
|
|
62
|
+
- **RFC 9457 Problem Details** — uniform error responses across all endpoints
|
|
63
|
+
- **Bearer Token / API Key auth** — zero-config `LocalTokenVerifier`
|
|
64
|
+
- **MCP support** — expose UseCases as AI agent tools via `LocalMcpServer`
|
|
65
|
+
- **SQLAlchemy Core** — parameterised SQL without ORM overhead
|
|
66
|
+
- **Security middleware** — CSP, X-Frame-Options, rate limiting, request size limit, CORS
|
|
67
|
+
- **structlog** — structured JSON logging with request ID correlation
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install nene2-python
|
|
75
|
+
# or
|
|
76
|
+
uv add nene2-python
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Requires Python 3.12+.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from fastapi import FastAPI
|
|
87
|
+
from nene2.config import AppSettings
|
|
88
|
+
from nene2.middleware import (
|
|
89
|
+
ErrorHandlerMiddleware,
|
|
90
|
+
RequestIdMiddleware,
|
|
91
|
+
SecurityHeadersMiddleware,
|
|
92
|
+
ThrottleMiddleware,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
cfg = AppSettings()
|
|
96
|
+
app = FastAPI()
|
|
97
|
+
|
|
98
|
+
app.add_middleware(ErrorHandlerMiddleware, debug=cfg.app_debug)
|
|
99
|
+
app.add_middleware(SecurityHeadersMiddleware)
|
|
100
|
+
app.add_middleware(RequestIdMiddleware)
|
|
101
|
+
app.add_middleware(ThrottleMiddleware, limit=cfg.throttle_limit, window=cfg.throttle_window)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Define a domain
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from dataclasses import dataclass
|
|
108
|
+
from abc import ABC, abstractmethod
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True, slots=True)
|
|
111
|
+
class Note:
|
|
112
|
+
id: int
|
|
113
|
+
title: str
|
|
114
|
+
body: str
|
|
115
|
+
|
|
116
|
+
class NoteRepositoryInterface(ABC):
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def find_by_id(self, note_id: int) -> Note | None: ...
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True, slots=True)
|
|
121
|
+
class GetNoteInput:
|
|
122
|
+
note_id: int
|
|
123
|
+
|
|
124
|
+
class GetNoteUseCase:
|
|
125
|
+
def __init__(self, repository: NoteRepositoryInterface) -> None:
|
|
126
|
+
self._repository = repository
|
|
127
|
+
|
|
128
|
+
def execute(self, input_: GetNoteInput) -> Note:
|
|
129
|
+
note = self._repository.find_by_id(input_.note_id)
|
|
130
|
+
if note is None:
|
|
131
|
+
raise NoteNotFoundException(input_.note_id)
|
|
132
|
+
return note
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Wire to HTTP
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from fastapi import APIRouter
|
|
139
|
+
from fastapi.responses import JSONResponse
|
|
140
|
+
from nene2.http import problem_details_response
|
|
141
|
+
|
|
142
|
+
router = APIRouter(prefix="/notes", tags=["notes"])
|
|
143
|
+
|
|
144
|
+
@router.get("/{note_id}")
|
|
145
|
+
async def get_note(note_id: int) -> JSONResponse:
|
|
146
|
+
note = get_use_case.execute(GetNoteInput(note_id))
|
|
147
|
+
return JSONResponse({"id": note.id, "title": note.title, "body": note.body})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
See the full working example in [`src/example/`](src/example/).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Development Commands
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uv sync # install dependencies
|
|
158
|
+
uv run pytest # run tests (coverage enforced at 80%)
|
|
159
|
+
uv run mypy src/ # type check
|
|
160
|
+
uv run ruff check src/ tests/ # lint
|
|
161
|
+
uv run ruff format src/ tests/ # format
|
|
162
|
+
uv run uvicorn src.example.app:app --reload --port 8080 # dev server
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Full CI check (equivalent to GitHub Actions):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
uv run pytest && \
|
|
169
|
+
uv run mypy src/ && \
|
|
170
|
+
uv run ruff check src/ tests/ && \
|
|
171
|
+
uv run ruff format --check src/ tests/ && \
|
|
172
|
+
uv run pip-audit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Framework Modules
|
|
178
|
+
|
|
179
|
+
| Module | Purpose |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `nene2.http` | `PaginationQueryParser`, `PaginationResponse`, `problem_details_response()` |
|
|
182
|
+
| `nene2.middleware` | `ErrorHandlerMiddleware`, `SecurityHeadersMiddleware`, `RequestIdMiddleware`, `RequestLoggingMiddleware`, `RequestSizeLimitMiddleware`, `ThrottleMiddleware` |
|
|
183
|
+
| `nene2.auth` | `BearerTokenMiddleware`, `ApiKeyAuthMiddleware`, `LocalTokenVerifier`, `TokenVerifierProtocol` |
|
|
184
|
+
| `nene2.database` | `SqlAlchemyQueryExecutor`, `SqlAlchemyTransactionManager`, `DatabaseHealthCheck` |
|
|
185
|
+
| `nene2.config` | `AppSettings` (pydantic-settings, reads from env / `.env`) |
|
|
186
|
+
| `nene2.validation` | `ValidationException`, `ValidationError` |
|
|
187
|
+
| `nene2.mcp` | `LocalMcpServer`, `HttpxMcpClient` |
|
|
188
|
+
| `nene2.log` | `setup_logging()` (structlog, JSON in production) |
|
|
189
|
+
| `nene2.use_case` | `UseCaseProtocol[I, O]`, `AsyncUseCaseProtocol[I, O]` |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## PHP NENE2 Correspondence
|
|
194
|
+
|
|
195
|
+
| PHP | Python |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `readonly class` | `dataclass(frozen=True, slots=True)` |
|
|
198
|
+
| `PHPStan level 8` | `mypy --strict` |
|
|
199
|
+
| `PHP-CS-Fixer` | `ruff format` |
|
|
200
|
+
| `composer check` | `uv run pytest && mypy && ruff check && ruff format --check && pip-audit` |
|
|
201
|
+
| `ValidationException` | `nene2.validation.ValidationException` |
|
|
202
|
+
| `PaginationQueryParser` | `nene2.http.PaginationQueryParser` |
|
|
203
|
+
| `ErrorHandlerMiddleware` | `nene2.middleware.ErrorHandlerMiddleware` |
|
|
204
|
+
| `LocalMcpServer` | `nene2.mcp.LocalMcpServer` |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Related
|
|
209
|
+
|
|
210
|
+
- [NENE2 (PHP)](https://github.com/hideyukiMORI/NENE2) — PHP reference implementation
|
|
211
|
+
- [Documentation](https://hideyukimori.github.io/nene2-python/) — full docs (Diátaxis structure)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
nene2/__init__.py,sha256=BbkkyxUGlvKbcfXFS6ZMO4y1SDwR7J1Zi0tK5C0Fm6A,46
|
|
2
|
+
nene2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
nene2/auth/__init__.py,sha256=6CL-QYTo-QqqTTWdcICkavd7LOsx7LYKMWlU78sgcU0,478
|
|
4
|
+
nene2/auth/api_key.py,sha256=lTvpwcfRT8mVS-iZk7VGU3hUtGK718XNfYoSDdzIHbk,1379
|
|
5
|
+
nene2/auth/bearer_token.py,sha256=hUdgjmYPT1330q2kv0R4WPJmm0Fav5wDgvwT_5q8h7o,1822
|
|
6
|
+
nene2/auth/exceptions.py,sha256=ksgMVCZhcI6C7TKOMZkNe3-G3VmYrNL66PsCf_uLUZM,240
|
|
7
|
+
nene2/auth/interfaces.py,sha256=_wEZ7FmjMpqHVKNY_rS4__v61Kn4mZ2-dn_byP6jnU0,570
|
|
8
|
+
nene2/auth/local_verifier.py,sha256=d7xLM3elJwm-i60Yb5mUJlVWlJRjoHTB9LqnxUaCDYs,588
|
|
9
|
+
nene2/config/__init__.py,sha256=VAAqpJPW1bC1TtTLvkx5aFTC0d0cRPS71Z3dbcjk_Cg,139
|
|
10
|
+
nene2/config/settings.py,sha256=LR5HwXHUumW0UZmZRAgXidvw_m71pKVMEakgOpH9sNo,2386
|
|
11
|
+
nene2/database/__init__.py,sha256=9mwmvhG3sjlF9qDkfXrItZI1Sv_twgwfpmRdtRjKh2c,537
|
|
12
|
+
nene2/database/exceptions.py,sha256=75X5b1Go9JT59KGRwn08pygJajecmGzpewodbBqzPUQ,142
|
|
13
|
+
nene2/database/health.py,sha256=-WJ1OxOjll0mDPIAfoBtSxyiT-el76U8f7XT-Syl8xo,793
|
|
14
|
+
nene2/database/interfaces.py,sha256=1uSV19Bq6VAQzQbiY5c-16XpslegB7P3uklhIHvudd8,1501
|
|
15
|
+
nene2/database/sqlalchemy_executor.py,sha256=-s-NmRyDhEdQPYeYJvcf2BJWLpqDqcbH1QhsTDM3tzs,5078
|
|
16
|
+
nene2/http/__init__.py,sha256=zRF4mrCD6582VHqtP6BhIiwFQQBrXzZ9SDFT2QaeOv8,440
|
|
17
|
+
nene2/http/health.py,sha256=zYAeXkEWWCITFmza9u8yqcSkD2sJx7AGm650HhvU9Fs,496
|
|
18
|
+
nene2/http/pagination.py,sha256=BmKyTcNBplQtGADYjqlSvWSGEih9bb3DG-rT_CyvOg8,2663
|
|
19
|
+
nene2/http/problem_details.py,sha256=-a4E6OV0caigcETf9YBupPEhjlYvqLEwZdCux1ujhPI,874
|
|
20
|
+
nene2/log/__init__.py,sha256=umsIJ2QH0d-CpnqEQCR0oWCjpe7wVoGYAYP35f1Nmbc,113
|
|
21
|
+
nene2/log/setup.py,sha256=JZJ3EnUjN9XcC1BB03gbgk4R4cpOYgu7it487pLCIb0,1407
|
|
22
|
+
nene2/mcp/__init__.py,sha256=KBJAAa2ewj5aAeUWzl-2DU-qujAb-EIyeCHG0U8FBjc,289
|
|
23
|
+
nene2/mcp/http_client.py,sha256=bMG49qZJgZGZtucJuSzAA4WPmmynQ-tDYtCElBDRFoI,3092
|
|
24
|
+
nene2/mcp/server.py,sha256=NJF5FOV5YoSS2IVwUonM0h4pYh2fINU18I7GqLmP8Wc,927
|
|
25
|
+
nene2/middleware/__init__.py,sha256=_FLy_xggx6iHCeKxlrawX_Z0tiQqirwWIbTvi4ii4GE,672
|
|
26
|
+
nene2/middleware/domain_exception.py,sha256=hnJhMaa9_p-Ptlh6BwjpDuyxFkDHeZSc5zy6I_ZuMW4,572
|
|
27
|
+
nene2/middleware/error_handler.py,sha256=Ms4tEJED1-C2nIDoDG5Dm_bYk623oHa6D3zvS0d6kGQ,4166
|
|
28
|
+
nene2/middleware/request_id.py,sha256=vj3H1ULai8EgzoCfaM-YIf-vUbMhSiGmnfuUDjSgdY0,1540
|
|
29
|
+
nene2/middleware/request_logging.py,sha256=knmZV23HmmIJA73QIgpunfMi1bO8JttwwAGORju8UF0,1204
|
|
30
|
+
nene2/middleware/request_size_limit.py,sha256=r1nuV7TOyJ6tFGFgjd1mS7xU0KHmSQv6gMV8Qp417lQ,1793
|
|
31
|
+
nene2/middleware/security_headers.py,sha256=wZvQbqfD82ZmOgA3yBBRkAchR1YL3G0R8TnB-wbjP7A,1468
|
|
32
|
+
nene2/middleware/throttle.py,sha256=jI3XCUDfitmaF-ACznXMhaq8KPdmdhL5wT3jAPs5dns,2633
|
|
33
|
+
nene2/use_case/__init__.py,sha256=xwdFxHsjfPORSw6sq6dUN8JkEX6X2CHmO3stdpjUNOU,196
|
|
34
|
+
nene2/use_case/protocols.py,sha256=F-R99vLad7XX6fHncTVBo5XLsKFsVceWqnE4QlSn9kg,710
|
|
35
|
+
nene2/validation/__init__.py,sha256=Nj4dMx4Re3l57B0raFSUPQWeHHFMJbBQaQcuvDSbwiE,205
|
|
36
|
+
nene2/validation/exceptions.py,sha256=qFaK-LAk1sNs90TV0mrQLvwNlfUruSohRqpqOaaG-lg,1036
|
|
37
|
+
nene2_python-1.0.0.dist-info/METADATA,sha256=ZMDcaQqIqEj-pyekXLnIo47Yzxxbs3J_ybrUqHtV5bQ,7263
|
|
38
|
+
nene2_python-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
39
|
+
nene2_python-1.0.0.dist-info/licenses/LICENSE,sha256=fzFjofg1algLQJI6DBIhxBDQTpry6HNLUcjrXMPTwIk,1069
|
|
40
|
+
nene2_python-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hideyukiMORI
|
|
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.
|