rest-canvas 0.8.1__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.
- rest_canvas-0.8.1/LICENSE +21 -0
- rest_canvas-0.8.1/PKG-INFO +382 -0
- rest_canvas-0.8.1/README.md +346 -0
- rest_canvas-0.8.1/pyproject.toml +124 -0
- rest_canvas-0.8.1/setup.cfg +4 -0
- rest_canvas-0.8.1/src/rest_canvas/__init__.py +140 -0
- rest_canvas-0.8.1/src/rest_canvas.egg-info/PKG-INFO +382 -0
- rest_canvas-0.8.1/src/rest_canvas.egg-info/SOURCES.txt +9 -0
- rest_canvas-0.8.1/src/rest_canvas.egg-info/dependency_links.txt +1 -0
- rest_canvas-0.8.1/src/rest_canvas.egg-info/requires.txt +18 -0
- rest_canvas-0.8.1/src/rest_canvas.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rklaus
|
|
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.
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rest-canvas
|
|
3
|
+
Version: 0.8.1
|
|
4
|
+
Summary: OpenAPI-driven decorator library for easy predictable and safe REST API implementation in python
|
|
5
|
+
Author: rklaus
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://gitlab.com/overflask/rest-canvas
|
|
8
|
+
Project-URL: Tracker, https://gitlab.com/overflask/rest-canvas/-/issues
|
|
9
|
+
Project-URL: Changelog, https://gitlab.com/overflask/rest-canvas/-/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: openapi,rest,api,decorator,flask,django,validation,schema
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
17
|
+
Requires-Python: >=3.14
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: PyYAML>=6.0
|
|
21
|
+
Requires-Dist: jsonschema>=4.0
|
|
22
|
+
Requires-Dist: prance>=25.4
|
|
23
|
+
Requires-Dist: openapi-spec-validator>=0.7
|
|
24
|
+
Provides-Extra: flask
|
|
25
|
+
Requires-Dist: Flask>=2.0; extra == "flask"
|
|
26
|
+
Provides-Extra: django
|
|
27
|
+
Requires-Dist: Django>=3.2; extra == "django"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: Flask>=2.0; extra == "dev"
|
|
34
|
+
Requires-Dist: Django>=3.2; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# REST Canvas
|
|
38
|
+
|
|
39
|
+
OpenAPI-driven decorator library for REST API endpoints in Django and Flask.
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
REST Canvas provides a decorator-based approach to building REST APIs that are automatically validated against OpenAPI specifications. It handles request validation, response formatting, pagination, sorting, filtering, and error handling based on your OpenAPI YAML files.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **OpenAPI-driven**: All validation and behavior driven by OpenAPI specifications
|
|
48
|
+
- **Framework-agnostic**: Works with Django, Flask, and standalone mode
|
|
49
|
+
- **Automatic validation**: Path parameters, query parameters, and request body validation at startup
|
|
50
|
+
- **Request-centric design**: All features accessible via unified Request object
|
|
51
|
+
- **Feature declaration**: Declare endpoint capabilities with simple markers (Fields, Sort, Filter)
|
|
52
|
+
- **Pagination support**: Both cursor-based and page-based pagination
|
|
53
|
+
- **RCQL filtering**: REST Canvas Query Language for powerful filtering (e.g., `status==active&age>18`)
|
|
54
|
+
- **Sort support**: Multi-field sorting with ascending/descending (e.g., `name,-createdAt`)
|
|
55
|
+
- **Detail level selection**: Control response verbosity with field groups (e.g., `minimal`, `full`)
|
|
56
|
+
- **Type safety**: Generic type hints for pagination: `Request[PagePagination]`
|
|
57
|
+
- **Testing utilities**: Built-in MockRequest for testing view functions in isolation
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Install from Git (once published)
|
|
63
|
+
pip install git+https://github.com/yourusername/rest-canvas.git
|
|
64
|
+
|
|
65
|
+
# Or install in editable mode for development
|
|
66
|
+
pip install -e .
|
|
67
|
+
|
|
68
|
+
# With development dependencies
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
|
|
71
|
+
# With Django
|
|
72
|
+
pip install -e ".[django]"
|
|
73
|
+
|
|
74
|
+
# With Flask
|
|
75
|
+
pip install -e ".[flask]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
REST Canvas is configured via the `Config` class, which allows you to customize behavior:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from rest_canvas import RestCanvas
|
|
84
|
+
from rest_canvas.core.config import Config
|
|
85
|
+
from rest_canvas.core.log import LoggerConfig, LogToConsole
|
|
86
|
+
from rest_canvas.core.log.logger_types import LogLevel
|
|
87
|
+
|
|
88
|
+
# Basic usage - starts with Standalone adapter
|
|
89
|
+
api = RestCanvas('openapi.yaml')
|
|
90
|
+
|
|
91
|
+
# Custom configuration
|
|
92
|
+
config = Config(
|
|
93
|
+
debug=True, # Enable debug mode
|
|
94
|
+
logger_config=LoggerConfig(
|
|
95
|
+
min_log_level=LogLevel.INFO, # Set logging level
|
|
96
|
+
enabled=True, # Enable/disable logging
|
|
97
|
+
processors=[LogToConsole()] # Custom log processors
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
api = RestCanvas('openapi.yaml', config=config)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Configuration Options
|
|
104
|
+
|
|
105
|
+
- **debug**: Enable debug mode for additional logging and validation (default: `False`)
|
|
106
|
+
- **logger_config**: LoggerConfig instance for customizing logging behavior
|
|
107
|
+
|
|
108
|
+
### Framework Adapter Selection
|
|
109
|
+
|
|
110
|
+
REST Canvas always starts with the **Standalone adapter**. The adapter automatically switches when you register routes:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Flask - adapter switches when you call register_flask_routes()
|
|
114
|
+
api = RestCanvas('openapi.yaml')
|
|
115
|
+
api.register_flask_routes(app) # Now using FlaskAdapter
|
|
116
|
+
|
|
117
|
+
# Django - adapter switches when you call register_django_urls()
|
|
118
|
+
api = RestCanvas('openapi.yaml')
|
|
119
|
+
urlpatterns = api.register_django_urls() # Now using DjangoAdapter
|
|
120
|
+
|
|
121
|
+
# Standalone - no registration needed (default)
|
|
122
|
+
api = RestCanvas('openapi.yaml')
|
|
123
|
+
# Call endpoints directly - stays with StandaloneAdapter
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Note:** You cannot switch adapters once routes are registered. Calling `register_flask_routes()` or `register_django_urls()` more than once will raise a `RuntimeError`.
|
|
127
|
+
|
|
128
|
+
## Quick Start
|
|
129
|
+
|
|
130
|
+
### Flask Example
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from flask import Flask
|
|
134
|
+
from rest_canvas import RestCanvas, Request, PagePagination, Sort, Filter
|
|
135
|
+
|
|
136
|
+
app = Flask(__name__)
|
|
137
|
+
|
|
138
|
+
# Initialize REST API with OpenAPI spec
|
|
139
|
+
api = RestCanvas('openapi.yaml', mount_point='/api/v1')
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Declare features in decorator, access via request object
|
|
143
|
+
@api.endpoint('GET /users', Sort, Filter)
|
|
144
|
+
def list_users(request: Request[PagePagination]):
|
|
145
|
+
"""List users with pagination, sorting, and filtering."""
|
|
146
|
+
users = User.query.all()
|
|
147
|
+
|
|
148
|
+
# Filter with RCQL: ?filter=status==active&age>18
|
|
149
|
+
if request.filter:
|
|
150
|
+
users = apply_filter(users, request.filter)
|
|
151
|
+
|
|
152
|
+
# Sort: ?sort=name,-createdAt (ascending name, descending createdAt)
|
|
153
|
+
if request.sort:
|
|
154
|
+
users = apply_sort(users, request.sort)
|
|
155
|
+
|
|
156
|
+
# Pagination automatically populated from query params
|
|
157
|
+
users_page = users[request.pagination.offset:request.pagination.limit]
|
|
158
|
+
request.pagination.total = len(users)
|
|
159
|
+
|
|
160
|
+
return [user.to_dict() for user in users_page]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Auto-register all routes with Flask
|
|
164
|
+
api.register_flask_routes(app)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Django Example
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from rest_canvas import RestCanvas, Request, CursorPagination, DetailLevel
|
|
171
|
+
|
|
172
|
+
# Initialize REST API
|
|
173
|
+
api = RestCanvas('openapi.yaml', mount_point='/api/v1')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@api.endpoint('GET /users/{userId}', DetailLevel)
|
|
177
|
+
def get_user_by_id(request: Request, user_id: int):
|
|
178
|
+
"""Get user by ID with detail level selection."""
|
|
179
|
+
user = User.objects.get(id=user_id)
|
|
180
|
+
|
|
181
|
+
# Return different fields based on request.detail_level
|
|
182
|
+
# Query: ?detailLevel=minimal
|
|
183
|
+
if request.detail_level == "minimal":
|
|
184
|
+
return {"id": user.id, "name": user.name}
|
|
185
|
+
else:
|
|
186
|
+
return user.to_dict()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# In urls.py
|
|
190
|
+
urlpatterns = api.register_django_urls()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Key Features
|
|
194
|
+
|
|
195
|
+
- **Request-centric design**: All features (pagination, sort, filter, detail_level) accessible via `request` object
|
|
196
|
+
- **Feature declaration**: Declare features in decorator: `@api.endpoint('GET /path', Sort, Filter, DetailLevel)`
|
|
197
|
+
- **Type-safe**: Generic type hints for pagination: `Request[PagePagination]`
|
|
198
|
+
- **Automatic parsing**: Sort, filter, pagination, and detail level parameters parsed automatically
|
|
199
|
+
- **OpenAPI validation**: Function signatures and features validated against OpenAPI spec at startup
|
|
200
|
+
- **RCQL filtering**: Powerful query language with operators: `==`, `!=`, `<`, `<=`, `>`, `>=`, `=like=`
|
|
201
|
+
- **Multi-field sorting**: Sort by multiple fields with direction: `?sort=name,-createdAt`
|
|
202
|
+
- **Framework adapters**: Seamless adaptation for Flask, Django, and standalone modes
|
|
203
|
+
|
|
204
|
+
## Development Status
|
|
205
|
+
|
|
206
|
+
**Current Version**: 0.1.0 (Alpha)
|
|
207
|
+
|
|
208
|
+
This library is in active development. Recent refactoring has established a modular, request-centric architecture.
|
|
209
|
+
|
|
210
|
+
### Completed Features
|
|
211
|
+
- ✅ OpenAPI-driven endpoint validation
|
|
212
|
+
- ✅ Request-centric design with unified Request object
|
|
213
|
+
- ✅ Framework adapters (Django, Flask, Standalone)
|
|
214
|
+
- ✅ Feature declaration system (Sort, Filter, DetailLevel)
|
|
215
|
+
- ✅ Pagination (Page-based and Cursor-based)
|
|
216
|
+
- ✅ RCQL filtering with comparison and logical operators
|
|
217
|
+
- ✅ Multi-field sorting with ascending/descending
|
|
218
|
+
- ✅ Detail level (field group) selection
|
|
219
|
+
- ✅ Automatic type coercion for query parameters
|
|
220
|
+
- ✅ CamelCase to snake_case conversion
|
|
221
|
+
- ✅ Testing utilities (MockRequest)
|
|
222
|
+
|
|
223
|
+
### In Progress
|
|
224
|
+
- 🔄 Additional ORM appliers for filtering and sorting
|
|
225
|
+
- 🔄 Enhanced error messages and validation
|
|
226
|
+
- 🔄 Documentation improvements
|
|
227
|
+
|
|
228
|
+
## Requirements
|
|
229
|
+
|
|
230
|
+
- Python >= 3.8
|
|
231
|
+
- PyYAML >= 6.0
|
|
232
|
+
- jsonschema >= 4.0
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Install development dependencies
|
|
238
|
+
pip install -r requirements-dev.txt
|
|
239
|
+
|
|
240
|
+
# Run tests
|
|
241
|
+
pytest
|
|
242
|
+
|
|
243
|
+
# Run tests with coverage
|
|
244
|
+
pytest --cov=rest_api_decorator --cov-report=html
|
|
245
|
+
|
|
246
|
+
# Format code
|
|
247
|
+
black .
|
|
248
|
+
|
|
249
|
+
# Lint code
|
|
250
|
+
flake8 rest_api_decorator tests
|
|
251
|
+
|
|
252
|
+
# Type check
|
|
253
|
+
mypy rest_api_decorator
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Testing Your Views
|
|
257
|
+
|
|
258
|
+
The library provides `MockRequest` for testing your view functions in isolation:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from rest_canvas.testing import MockRequest
|
|
262
|
+
|
|
263
|
+
def test_list_users():
|
|
264
|
+
# Create mock request with pagination, sort, and filter
|
|
265
|
+
request = (MockRequest()
|
|
266
|
+
.with_page_pagination(page=1, size=10)
|
|
267
|
+
.with_sort('name,-createdAt')
|
|
268
|
+
.with_filter('status==active'))
|
|
269
|
+
|
|
270
|
+
# Call view function directly
|
|
271
|
+
result = list_users(request)
|
|
272
|
+
|
|
273
|
+
# Assert results
|
|
274
|
+
assert len(result) <= 10
|
|
275
|
+
assert request.pagination.total > 0
|
|
276
|
+
|
|
277
|
+
def test_get_user_by_id():
|
|
278
|
+
# Create mock request for single resource with detail level
|
|
279
|
+
request = MockRequest().with_detail_level('minimal')
|
|
280
|
+
|
|
281
|
+
# Call view with path parameter
|
|
282
|
+
result = get_user_by_id(request, user_id=42)
|
|
283
|
+
|
|
284
|
+
# Assert minimal fields returned
|
|
285
|
+
assert 'id' in result
|
|
286
|
+
assert 'name' in result
|
|
287
|
+
assert 'email' not in result # Not included in minimal
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Project Structure
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
rest_canvas/
|
|
294
|
+
├── public/ # Public API surface
|
|
295
|
+
│ ├── rest_canvas.py # RestCanvas class (main entry point)
|
|
296
|
+
│ ├── features.py # Feature markers (DetailLevel, Sort, Filter)
|
|
297
|
+
│ └── exceptions.py # API exceptions
|
|
298
|
+
├── core/ # Core implementation
|
|
299
|
+
│ ├── endpoint/ # Endpoint decorator and validation
|
|
300
|
+
│ ├── request/ # Request wrapper and validation
|
|
301
|
+
│ ├── open_api/ # OpenAPI spec loading and parsing
|
|
302
|
+
│ ├── pagination/ # Page and cursor pagination
|
|
303
|
+
│ ├── query/ # Query parameter parsing (sort, filter)
|
|
304
|
+
│ ├── view/ # View function introspection
|
|
305
|
+
│ ├── envelope/ # Response envelope formatting
|
|
306
|
+
│ └── serializer.py # Datetime serialization
|
|
307
|
+
├── adapters/ # Framework adapters
|
|
308
|
+
│ ├── django_adapter.py
|
|
309
|
+
│ ├── flask_adapter.py
|
|
310
|
+
│ └── standalone_adapter.py
|
|
311
|
+
├── utils/ # Utilities
|
|
312
|
+
└── testing/ # Testing utilities (MockRequest)
|
|
313
|
+
|
|
314
|
+
tests/
|
|
315
|
+
├── unit/ # Unit tests
|
|
316
|
+
├── integration/ # Integration tests
|
|
317
|
+
└── fixtures/ # Test fixtures and OpenAPI specs
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Key Concepts
|
|
321
|
+
|
|
322
|
+
### RCQL (REST Canvas Query Language)
|
|
323
|
+
|
|
324
|
+
RCQL provides a powerful filtering syntax using URL-friendly operators:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
# Comparison operators
|
|
328
|
+
?filter=status==active # Equal
|
|
329
|
+
?filter=age>18 # Greater than
|
|
330
|
+
?filter=age<=65 # Less than or equal
|
|
331
|
+
?filter=name=like=%john% # Pattern matching
|
|
332
|
+
|
|
333
|
+
# Logical operators (left-to-right evaluation)
|
|
334
|
+
?filter=status==active&age>18 # AND
|
|
335
|
+
?filter=role==admin|role==mod # OR
|
|
336
|
+
|
|
337
|
+
# Complex expressions
|
|
338
|
+
?filter=status==active&age>18|premium==true
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Sort Syntax
|
|
342
|
+
|
|
343
|
+
Multi-field sorting with direction control:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
?sort=name # Ascending by name
|
|
347
|
+
?sort=-createdAt # Descending by createdAt
|
|
348
|
+
?sort=name,-createdAt # Multiple fields
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Detail Levels
|
|
352
|
+
|
|
353
|
+
Control response verbosity with field groups defined in your OpenAPI spec:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
?detailLevel=minimal # Only essential fields
|
|
357
|
+
?detailLevel=full # All fields including relations
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Architecture
|
|
361
|
+
|
|
362
|
+
REST Canvas uses a **request-centric** design where all query features flow through a unified `Request` object:
|
|
363
|
+
|
|
364
|
+
1. **Decorator declaration**: Features declared in `@api.endpoint()` decorator
|
|
365
|
+
2. **Automatic parsing**: Query parameters parsed and validated at request time
|
|
366
|
+
3. **Request population**: Parsed values populated in `request.sort`, `request.filter`, etc.
|
|
367
|
+
4. **Type safety**: Generic type hints for pagination: `Request[PagePagination]`
|
|
368
|
+
5. **Framework agnostic**: Same code works with Django, Flask, or standalone
|
|
369
|
+
|
|
370
|
+
This design ensures your view functions remain clean, testable, and framework-independent.
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
MIT License
|
|
375
|
+
|
|
376
|
+
## Contributing
|
|
377
|
+
|
|
378
|
+
This is currently an internal project. Contribution guidelines will be added once the library reaches beta status.
|
|
379
|
+
|
|
380
|
+
## Support
|
|
381
|
+
|
|
382
|
+
For issues and questions, please refer to the issue tracker (to be set up).
|