bustapi 0.1.0__cp311-cp311-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.
Potentially problematic release.
This version of bustapi might be problematic. Click here for more details.
- bustapi/__init__.py +96 -0
- bustapi/app.py +552 -0
- bustapi/blueprints.py +500 -0
- bustapi/bustapi_core.cp311-win_amd64.pyd +0 -0
- bustapi/exceptions.py +438 -0
- bustapi/flask_compat.py +62 -0
- bustapi/helpers.py +370 -0
- bustapi/py.typed +0 -0
- bustapi/request.py +378 -0
- bustapi/response.py +406 -0
- bustapi/testing.py +375 -0
- bustapi-0.1.0.dist-info/METADATA +233 -0
- bustapi-0.1.0.dist-info/RECORD +16 -0
- bustapi-0.1.0.dist-info/WHEEL +4 -0
- bustapi-0.1.0.dist-info/entry_points.txt +2 -0
- bustapi-0.1.0.dist-info/licenses/LICENSE +21 -0
bustapi/helpers.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for BustAPI - Flask-compatible utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from .exceptions import HTTPException
|
|
9
|
+
from .response import Response
|
|
10
|
+
from .response import redirect as _redirect
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def abort(code: int, description: Optional[str] = None, **kwargs) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Abort request with HTTP error code (Flask-compatible).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
code: HTTP status code
|
|
19
|
+
description: Error description
|
|
20
|
+
**kwargs: Additional arguments
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
HTTPException: HTTP exception with specified code
|
|
24
|
+
"""
|
|
25
|
+
raise HTTPException(code, description=description)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def redirect(location: str, code: int = 302) -> Response:
|
|
29
|
+
"""
|
|
30
|
+
Create a redirect response (Flask-compatible).
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
location: Redirect URL
|
|
34
|
+
code: HTTP status code (301, 302, etc.)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Redirect response
|
|
38
|
+
"""
|
|
39
|
+
return _redirect(location, code)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def url_for(endpoint: str, **values) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Generate URL for endpoint (Flask-compatible placeholder).
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
endpoint: Endpoint name
|
|
48
|
+
**values: URL parameters
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Generated URL
|
|
52
|
+
|
|
53
|
+
Note:
|
|
54
|
+
This is a simplified implementation. Full URL generation
|
|
55
|
+
requires route reversal which should be implemented in
|
|
56
|
+
the Rust backend for performance.
|
|
57
|
+
"""
|
|
58
|
+
# Placeholder implementation
|
|
59
|
+
# TODO: Implement proper URL reversal with route mapping
|
|
60
|
+
|
|
61
|
+
# For now, just return the endpoint as-is
|
|
62
|
+
# In a full implementation, this would:
|
|
63
|
+
# 1. Look up the route pattern for the endpoint
|
|
64
|
+
# 2. Substitute parameters into the pattern
|
|
65
|
+
# 3. Generate the full URL
|
|
66
|
+
|
|
67
|
+
if values:
|
|
68
|
+
# Simple parameter substitution for basic cases
|
|
69
|
+
url = endpoint
|
|
70
|
+
for key, value in values.items():
|
|
71
|
+
url = url.replace(f"<{key}>", str(value))
|
|
72
|
+
url = url.replace(f"<int:{key}>", str(value))
|
|
73
|
+
url = url.replace(f"<string:{key}>", str(value))
|
|
74
|
+
return url
|
|
75
|
+
|
|
76
|
+
return endpoint
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def flash(message: str, category: str = "message") -> None:
|
|
80
|
+
"""
|
|
81
|
+
Flash a message to the user (Flask-compatible placeholder).
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
message: Message to flash
|
|
85
|
+
category: Message category
|
|
86
|
+
|
|
87
|
+
Note:
|
|
88
|
+
This is a placeholder implementation. Flash messaging
|
|
89
|
+
requires session support which should be implemented.
|
|
90
|
+
"""
|
|
91
|
+
# TODO: Implement flash messaging with session support
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_flashed_messages(
|
|
96
|
+
with_categories: bool = False, category_filter: Optional[list] = None
|
|
97
|
+
) -> list:
|
|
98
|
+
"""
|
|
99
|
+
Get flashed messages (Flask-compatible placeholder).
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
with_categories: Include categories in result
|
|
103
|
+
category_filter: Filter by categories
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of flashed messages
|
|
107
|
+
|
|
108
|
+
Note:
|
|
109
|
+
This is a placeholder implementation.
|
|
110
|
+
"""
|
|
111
|
+
# TODO: Implement flash message retrieval
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def send_file(
|
|
116
|
+
path_or_file,
|
|
117
|
+
mimetype: Optional[str] = None,
|
|
118
|
+
as_attachment: bool = False,
|
|
119
|
+
attachment_filename: Optional[str] = None,
|
|
120
|
+
add_etags: bool = True,
|
|
121
|
+
cache_timeout: Optional[int] = None,
|
|
122
|
+
conditional: bool = False,
|
|
123
|
+
last_modified: Optional[Any] = None,
|
|
124
|
+
) -> Response:
|
|
125
|
+
"""
|
|
126
|
+
Send a file to the user (Flask-compatible).
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
path_or_file: File path or file-like object
|
|
130
|
+
mimetype: MIME type
|
|
131
|
+
as_attachment: Send as attachment
|
|
132
|
+
attachment_filename: Attachment filename
|
|
133
|
+
add_etags: Add ETag header
|
|
134
|
+
cache_timeout: Cache timeout
|
|
135
|
+
conditional: Enable conditional responses
|
|
136
|
+
last_modified: Last modified time
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Response object with file content
|
|
140
|
+
"""
|
|
141
|
+
from .response import send_file as _send_file
|
|
142
|
+
|
|
143
|
+
return _send_file(path_or_file, mimetype, as_attachment, attachment_filename)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def send_from_directory(directory: str, path: str, **kwargs) -> Response:
|
|
147
|
+
"""
|
|
148
|
+
Send a file from a directory (Flask-compatible).
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
directory: Directory path
|
|
152
|
+
path: File path within directory
|
|
153
|
+
**kwargs: Additional arguments for send_file
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Response object with file content
|
|
157
|
+
"""
|
|
158
|
+
# Security: ensure path doesn't escape directory
|
|
159
|
+
safe_path = os.path.normpath(path).lstrip("/")
|
|
160
|
+
full_path = os.path.join(directory, safe_path)
|
|
161
|
+
|
|
162
|
+
# Check if path tries to escape directory
|
|
163
|
+
if not os.path.abspath(full_path).startswith(os.path.abspath(directory)):
|
|
164
|
+
abort(404, description="File not found")
|
|
165
|
+
|
|
166
|
+
return send_file(full_path, **kwargs)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def safe_join(directory: str, *pathnames: str) -> Optional[str]:
|
|
170
|
+
"""
|
|
171
|
+
Safely join directory and path components.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
directory: Base directory
|
|
175
|
+
*pathnames: Path components to join
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Joined path or None if unsafe
|
|
179
|
+
"""
|
|
180
|
+
if not pathnames:
|
|
181
|
+
return directory
|
|
182
|
+
|
|
183
|
+
path = os.path.join(directory, *pathnames)
|
|
184
|
+
path = os.path.normpath(path)
|
|
185
|
+
|
|
186
|
+
# Check if path tries to escape directory
|
|
187
|
+
if not path.startswith(directory + os.sep) and path != directory:
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
return path
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def escape(text: str) -> str:
|
|
194
|
+
"""
|
|
195
|
+
Escape HTML special characters.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
text: Text to escape
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Escaped text
|
|
202
|
+
"""
|
|
203
|
+
return (
|
|
204
|
+
text.replace("&", "&")
|
|
205
|
+
.replace("<", "<")
|
|
206
|
+
.replace(">", ">")
|
|
207
|
+
.replace('"', """)
|
|
208
|
+
.replace("'", "'")
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_debug_flag() -> bool:
|
|
213
|
+
"""
|
|
214
|
+
Get debug flag from environment or current app.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Debug flag value
|
|
218
|
+
"""
|
|
219
|
+
# Check environment variable first
|
|
220
|
+
debug_env = os.environ.get("FLASK_DEBUG", "").lower()
|
|
221
|
+
if debug_env in ("1", "true", "yes", "on"):
|
|
222
|
+
return True
|
|
223
|
+
elif debug_env in ("0", "false", "no", "off"):
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
# Try to get from current app
|
|
227
|
+
try:
|
|
228
|
+
current_app = _get_current_object()
|
|
229
|
+
return current_app.config.get("DEBUG", False)
|
|
230
|
+
except RuntimeError:
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _get_current_object():
|
|
235
|
+
"""
|
|
236
|
+
Get current application object (placeholder).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Current application
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
RuntimeError: If no application context
|
|
243
|
+
"""
|
|
244
|
+
# TODO: Implement application context
|
|
245
|
+
raise RuntimeError("Working outside of application context")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Template helpers (placeholders)
|
|
249
|
+
def render_template(template_name: str, **context) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Render template (Flask-compatible placeholder).
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
template_name: Template filename
|
|
255
|
+
**context: Template context variables
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Rendered template string
|
|
259
|
+
|
|
260
|
+
Note:
|
|
261
|
+
This is a placeholder. Template rendering should be
|
|
262
|
+
implemented with a proper template engine like Jinja2.
|
|
263
|
+
"""
|
|
264
|
+
# TODO: Implement template rendering
|
|
265
|
+
return f"<!-- Template: {template_name} -->"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def render_template_string(source: str, **context) -> str:
|
|
269
|
+
"""
|
|
270
|
+
Render template from string (Flask-compatible placeholder).
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
source: Template source string
|
|
274
|
+
**context: Template context variables
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Rendered template string
|
|
278
|
+
"""
|
|
279
|
+
# TODO: Implement template rendering from string
|
|
280
|
+
return source
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# JSON helpers
|
|
284
|
+
def jsonify(*args, **kwargs) -> Response:
|
|
285
|
+
"""
|
|
286
|
+
Create JSON response (re-export from response module).
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
*args: Positional arguments for JSON data
|
|
290
|
+
**kwargs: Keyword arguments for JSON data
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
JSON response
|
|
294
|
+
"""
|
|
295
|
+
from .response import jsonify as _jsonify
|
|
296
|
+
|
|
297
|
+
return _jsonify(*args, **kwargs)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# Request helpers
|
|
301
|
+
def get_json() -> Any:
|
|
302
|
+
"""
|
|
303
|
+
Get JSON data from current request.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
JSON data from request
|
|
307
|
+
"""
|
|
308
|
+
from .request import request
|
|
309
|
+
|
|
310
|
+
return request.get_json()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# URL helpers
|
|
314
|
+
def url_quote(string: str, charset: str = "utf-8", safe: str = "/:") -> str:
|
|
315
|
+
"""
|
|
316
|
+
URL quote a string.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
string: String to quote
|
|
320
|
+
charset: Character encoding
|
|
321
|
+
safe: Characters to not quote
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
URL quoted string
|
|
325
|
+
"""
|
|
326
|
+
from urllib.parse import quote
|
|
327
|
+
|
|
328
|
+
if isinstance(string, str):
|
|
329
|
+
string = string.encode(charset)
|
|
330
|
+
return quote(string, safe=safe)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def url_unquote(string: str, charset: str = "utf-8") -> str:
|
|
334
|
+
"""
|
|
335
|
+
URL unquote a string.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
string: String to unquote
|
|
339
|
+
charset: Character encoding
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
URL unquoted string
|
|
343
|
+
"""
|
|
344
|
+
from urllib.parse import unquote
|
|
345
|
+
|
|
346
|
+
return unquote(string, encoding=charset)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# Configuration helpers
|
|
350
|
+
def get_env() -> str:
|
|
351
|
+
"""
|
|
352
|
+
Get environment name.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Environment name (development, production, etc.)
|
|
356
|
+
"""
|
|
357
|
+
return os.environ.get("FLASK_ENV", "development")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def get_load_dotenv(default: bool = True) -> bool:
|
|
361
|
+
"""
|
|
362
|
+
Check if .env files should be loaded.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
default: Default value
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Whether to load .env files
|
|
369
|
+
"""
|
|
370
|
+
return os.environ.get("FLASK_SKIP_DOTENV", "").lower() not in ("1", "true", "yes")
|
bustapi/py.typed
ADDED
|
File without changes
|