tachyon-api 0.5.5__py3-none-any.whl → 0.5.7__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.

Potentially problematic release.


This version of tachyon-api might be problematic. Click here for more details.

tachyon_api/cache.py ADDED
@@ -0,0 +1,261 @@
1
+ """
2
+ Cache utilities: decorator with TTL and pluggable backends.
3
+
4
+ This module provides:
5
+ - BaseCacheBackend protocol and an in-memory implementation
6
+ - CacheConfig dataclass and helpers to set global/app config
7
+ - cache decorator usable on any sync/async function (including routes)
8
+
9
+ Design notes:
10
+ - Key builder defaults to a stable representation of function + args/kwargs
11
+ - Unless predicate allows opt-out per-call
12
+ - TTL can be provided per-decorator or falls back to global default
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import time
17
+ import asyncio
18
+ import hashlib
19
+ import inspect
20
+ from dataclasses import dataclass
21
+ from functools import wraps
22
+ from typing import Any, Callable, Optional, Tuple
23
+
24
+
25
+ class BaseCacheBackend:
26
+ """Minimal cache backend interface."""
27
+
28
+ def get(self, key: str) -> Any: # pragma: no cover - interface
29
+ raise NotImplementedError
30
+
31
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None: # pragma: no cover - interface
32
+ raise NotImplementedError
33
+
34
+ def delete(self, key: str) -> None: # pragma: no cover - interface
35
+ raise NotImplementedError
36
+
37
+ def clear(self) -> None: # pragma: no cover - interface
38
+ raise NotImplementedError
39
+
40
+
41
+ class InMemoryCacheBackend(BaseCacheBackend):
42
+ """Simple in-memory cache with TTL using wall-clock time."""
43
+
44
+ def __init__(self) -> None:
45
+ self._store: dict[str, Tuple[float | None, Any]] = {}
46
+
47
+ def _is_expired(self, expires_at: float | None) -> bool:
48
+ return expires_at is not None and time.time() >= expires_at
49
+
50
+ def get(self, key: str) -> Any:
51
+ item = self._store.get(key)
52
+ if not item:
53
+ return None
54
+ expires_at, value = item
55
+ if self._is_expired(expires_at):
56
+ # Lazy expiration
57
+ try:
58
+ del self._store[key]
59
+ except KeyError:
60
+ pass
61
+ return None
62
+ return value
63
+
64
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
65
+ expires_at = time.time() + ttl if ttl and ttl > 0 else None
66
+ self._store[key] = (expires_at, value)
67
+
68
+ def delete(self, key: str) -> None:
69
+ self._store.pop(key, None)
70
+
71
+ def clear(self) -> None:
72
+ self._store.clear()
73
+
74
+
75
+ @dataclass
76
+ class CacheConfig:
77
+ backend: BaseCacheBackend
78
+ default_ttl: float = 60.0
79
+ key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None
80
+ enabled: bool = True
81
+
82
+
83
+ _cache_config: Optional[CacheConfig] = None
84
+
85
+
86
+ def _default_key_builder(func: Callable, args: tuple, kwargs: dict) -> str:
87
+ parts = [getattr(func, "__module__", ""), getattr(func, "__qualname__", ""), "|"]
88
+ # Stable kwargs order
89
+ items = [repr(a) for a in args] + [f"{k}={repr(v)}" for k, v in sorted(kwargs.items())]
90
+ raw_key = ":".join(parts + items)
91
+ return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
92
+
93
+
94
+ def create_cache_config(
95
+ backend: Optional[BaseCacheBackend] = None,
96
+ default_ttl: float = 60.0,
97
+ key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None,
98
+ enabled: bool = True,
99
+ ) -> CacheConfig:
100
+ """Create and set the global cache configuration.
101
+
102
+ Returns the created CacheConfig and sets it as the active global config.
103
+ """
104
+ global _cache_config
105
+ cfg = CacheConfig(
106
+ backend=backend or InMemoryCacheBackend(),
107
+ default_ttl=default_ttl,
108
+ key_builder=key_builder,
109
+ enabled=enabled,
110
+ )
111
+ _cache_config = cfg
112
+ return cfg
113
+
114
+
115
+ def set_cache_config(config: CacheConfig) -> None:
116
+ """Set the global cache configuration object."""
117
+ global _cache_config
118
+ _cache_config = config
119
+
120
+
121
+ def get_cache_config() -> CacheConfig:
122
+ """Get the current cache configuration, creating a default one if missing."""
123
+ global _cache_config
124
+ if _cache_config is None:
125
+ _cache_config = create_cache_config()
126
+ return _cache_config
127
+
128
+
129
+ def cache(
130
+ TTL: Optional[float] = None,
131
+ *,
132
+ key_builder: Optional[Callable[[Callable, tuple, dict], str]] = None,
133
+ unless: Optional[Callable[[tuple, dict], bool]] = None,
134
+ backend: Optional[BaseCacheBackend] = None,
135
+ ):
136
+ """Cache decorator with TTL and pluggable backend.
137
+
138
+ Args:
139
+ TTL: Time-to-live in seconds for this decorator instance. Falls back to config.default_ttl.
140
+ key_builder: Optional custom function to build cache keys.
141
+ unless: Predicate receiving (args, kwargs). If returns True, skip cache for that call.
142
+ backend: Optional backend override for this decorator.
143
+ """
144
+
145
+ def decorator(func: Callable):
146
+ cfg = get_cache_config()
147
+ be = backend or cfg.backend
148
+ ttl_value = cfg.default_ttl if TTL is None else TTL
149
+ kb = key_builder or cfg.key_builder or (lambda f, a, kw: _default_key_builder(f, a, kw))
150
+
151
+ if asyncio.iscoroutinefunction(func):
152
+ @wraps(func)
153
+ async def async_wrapper(*args, **kwargs):
154
+ if not cfg.enabled or (unless and unless(args, kwargs)):
155
+ return await func(*args, **kwargs)
156
+ key = kb(func, args, kwargs)
157
+ cached = be.get(key)
158
+ if cached is not None:
159
+ return cached
160
+ result = await func(*args, **kwargs)
161
+ try:
162
+ be.set(key, result, ttl_value)
163
+ except Exception:
164
+ # Backend errors should not break the app
165
+ pass
166
+ return result
167
+
168
+ return async_wrapper
169
+ else:
170
+ @wraps(func)
171
+ def wrapper(*args, **kwargs):
172
+ if not cfg.enabled or (unless and unless(args, kwargs)):
173
+ return func(*args, **kwargs)
174
+ key = kb(func, args, kwargs)
175
+ cached = be.get(key)
176
+ if cached is not None:
177
+ return cached
178
+ result = func(*args, **kwargs)
179
+ try:
180
+ be.set(key, result, ttl_value)
181
+ except Exception:
182
+ pass
183
+ return result
184
+
185
+ return wrapper
186
+
187
+ return decorator
188
+
189
+
190
+ class RedisCacheBackend(BaseCacheBackend):
191
+ """Adapter backend for Redis-like clients.
192
+
193
+ Expects a client with .get(key) -> bytes|str|None and .set(key, value, ex=ttl_seconds).
194
+ The stored values should be JSON-serializable or pickled externally by the user.
195
+ """
196
+
197
+ def __init__(self, client) -> None:
198
+ self.client = client
199
+
200
+ def get(self, key: str) -> Any:
201
+ value = self.client.get(key)
202
+ # Many clients return bytes; decode if possible
203
+ if isinstance(value, bytes):
204
+ try:
205
+ return value.decode("utf-8")
206
+ except Exception:
207
+ return value
208
+ return value
209
+
210
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
211
+ # Use ex (expire seconds) if available
212
+ kwargs = {}
213
+ if ttl and ttl > 0:
214
+ kwargs["ex"] = int(ttl)
215
+ # Best effort set
216
+ self.client.set(key, value, **kwargs)
217
+
218
+ def delete(self, key: str) -> None:
219
+ try:
220
+ self.client.delete(key)
221
+ except Exception:
222
+ pass
223
+
224
+ def clear(self) -> None:
225
+ # Not standardized; no-op by default
226
+ pass
227
+
228
+
229
+ class MemcachedCacheBackend(BaseCacheBackend):
230
+ """Adapter backend for Memcached-like clients.
231
+
232
+ Expects a client with .get(key) and .set(key, value, expire=ttl_seconds).
233
+ """
234
+
235
+ def __init__(self, client) -> None:
236
+ self.client = client
237
+
238
+ def get(self, key: str) -> Any:
239
+ return self.client.get(key)
240
+
241
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
242
+ expire = int(ttl) if ttl and ttl > 0 else 0
243
+ try:
244
+ # pymemcache: set(key, value, expire=...)
245
+ self.client.set(key, value, expire=expire)
246
+ except TypeError:
247
+ # python-binary-memcached: set(key, value, time=...)
248
+ self.client.set(key, value, time=expire)
249
+
250
+ def delete(self, key: str) -> None:
251
+ try:
252
+ self.client.delete(key)
253
+ except Exception:
254
+ pass
255
+
256
+ def clear(self) -> None:
257
+ try:
258
+ self.client.flush_all()
259
+ except Exception:
260
+ pass
261
+
tachyon_api/openapi.py CHANGED
@@ -1,5 +1,8 @@
1
- from typing import Dict, Any, Optional, List, Type
1
+ from typing import Dict, Any, Optional, List, Type, get_origin, get_args
2
2
  from dataclasses import dataclass, field
3
+ import datetime
4
+ import uuid
5
+ import typing
3
6
 
4
7
  from .models import Struct
5
8
 
@@ -7,30 +10,112 @@ from .models import Struct
7
10
  TYPE_MAP = {int: "integer", str: "string", bool: "boolean", float: "number"}
8
11
 
9
12
 
10
- def _generate_schema_for_struct(struct_class: Type[Struct]) -> Dict[str, Any]:
13
+ def _schema_for_python_type(
14
+ py_type: Type,
15
+ components: Dict[str, Dict[str, Any]],
16
+ visited: set[Type],
17
+ ) -> Dict[str, Any]:
18
+ """Return OpenAPI schema for a Python type, adding components for Structs if needed."""
19
+ origin = get_origin(py_type)
20
+ args = get_args(py_type)
21
+
22
+ # Optional[T] (Union[T, None])
23
+ if origin is typing.Union and args:
24
+ non_none = [a for a in args if a is not type(None)] # noqa: E721
25
+ if len(non_none) == 1:
26
+ inner = non_none[0]
27
+ schema = _schema_for_python_type(inner, components, visited)
28
+ schema["nullable"] = True
29
+ return schema
30
+
31
+ # List[T]
32
+ if origin in (list, List):
33
+ item = args[0] if args else str
34
+ item_schema = _schema_for_python_type(item, components, visited)
35
+ return {"type": "array", "items": item_schema}
36
+
37
+ # Struct subclass
38
+ if isinstance(py_type, type) and issubclass(py_type, Struct):
39
+ name = py_type.__name__
40
+ if py_type not in visited:
41
+ visited.add(py_type)
42
+ components[name] = _generate_struct_schema(py_type, components, visited)
43
+ return {"$ref": f"#/components/schemas/{name}"}
44
+
45
+ # Special formats
46
+ if py_type is uuid.UUID:
47
+ return {"type": "string", "format": "uuid"}
48
+ if py_type is datetime.datetime:
49
+ return {"type": "string", "format": "date-time"}
50
+ if py_type is datetime.date:
51
+ return {"type": "string", "format": "date"}
52
+
53
+ # Scalars
54
+ return {"type": TYPE_MAP.get(py_type, "string")}
55
+
56
+
57
+ def _unwrap_optional(py_type: Type) -> tuple[Type, bool]:
58
+ origin = get_origin(py_type)
59
+ args = get_args(py_type)
60
+ if origin is typing.Union and args:
61
+ non_none = [a for a in args if a is not type(None)] # noqa: E721
62
+ if len(non_none) == 1:
63
+ return non_none[0], True
64
+ return py_type, False
65
+
66
+
67
+ def _generate_struct_schema(
68
+ struct_class: Type[Struct],
69
+ components: Dict[str, Dict[str, Any]],
70
+ visited: set[Type],
71
+ ) -> Dict[str, Any]:
72
+ """
73
+ Generate a JSON Schema dictionary for a msgspec Struct, populating components for nested Structs.
11
74
  """
12
- Generate a JSON Schema dictionary from a tachyon_api.models.Struct.
75
+ properties: Dict[str, Any] = {}
76
+ required: List[str] = []
13
77
 
14
- Args:
15
- struct_class: The Struct class to generate schema for
78
+ annotations = getattr(struct_class, "__annotations__", {})
79
+ for field_name in getattr(struct_class, "__struct_fields__", annotations.keys()):
80
+ field_type = annotations.get(field_name, str)
81
+ base_type, is_opt = _unwrap_optional(field_type)
16
82
 
17
- Returns:
18
- Dictionary containing the OpenAPI schema for the struct
83
+ # Build property schema
84
+ prop_schema = _schema_for_python_type(base_type, components, visited)
85
+ if is_opt:
86
+ prop_schema["nullable"] = True
87
+
88
+ properties[field_name] = prop_schema
89
+
90
+ # Determine required: mark non-Optional fields as required
91
+ if not is_opt:
92
+ required.append(field_name)
93
+
94
+ schema: Dict[str, Any] = {"type": "object", "properties": properties}
95
+ if required:
96
+ schema["required"] = required
97
+ return schema
98
+
99
+
100
+ def build_components_for_struct(struct_class: Type[Struct]) -> Dict[str, Dict[str, Any]]:
19
101
  """
20
- properties = {}
21
- required = []
22
-
23
- # Use msgspec's introspection tools
24
- for field_name in struct_class.__struct_fields__:
25
- field_type = struct_class.__annotations__.get(field_name)
26
- properties[field_name] = {
27
- "type": TYPE_MAP.get(field_type, "string"),
28
- "title": field_name.replace("_", " ").title(),
29
- }
30
- # For now, assume all fields are required (can be enhanced later)
31
- required.append(field_name)
102
+ Build components schemas for the given Struct and all nested Structs.
103
+
104
+ Returns a dict mapping component name to schema, including the top-level struct.
105
+ """
106
+ components: Dict[str, Dict[str, Any]] = {}
107
+ visited: set[Type] = set()
108
+ name = struct_class.__name__
109
+ components[name] = _generate_struct_schema(struct_class, components, visited)
110
+ return components
111
+
32
112
 
33
- return {"type": "object", "properties": properties, "required": required}
113
+ def _generate_schema_for_struct(struct_class: Type[Struct]) -> Dict[str, Any]:
114
+ """
115
+ Backward-compatible API: generate only the schema for the provided Struct (no nested components registration).
116
+ """
117
+ comps = build_components_for_struct(struct_class)
118
+ return comps[struct_class.__name__]
34
119
 
35
120
 
36
121
  @dataclass
@@ -202,7 +287,7 @@ class OpenAPIGenerator:
202
287
  SwaggerUIBundle.presets.standalone
203
288
  ],
204
289
  layout: "BaseLayout",
205
- ...{params_json}
290
+ ...{{}}
206
291
  }})
207
292
  </script>
208
293
  </body>
@@ -268,7 +353,6 @@ class OpenAPIGenerator:
268
353
  """
269
354
  if self._openapi_schema is None:
270
355
  self._openapi_schema = self.config.to_openapi_dict()
271
-
272
356
  if path not in self._openapi_schema["paths"]:
273
357
  self._openapi_schema["paths"][path] = {}
274
358
 
tachyon_api/responses.py CHANGED
@@ -6,12 +6,24 @@ with Starlette responses.
6
6
  """
7
7
 
8
8
  from starlette.responses import JSONResponse, HTMLResponse # noqa
9
+ import orjson
10
+ from .models import encode_json
11
+
12
+
13
+ class TachyonJSONResponse(JSONResponse):
14
+ """High-performance JSON response using orjson for serialization."""
15
+
16
+ media_type = "application/json"
17
+
18
+ def render(self, content) -> bytes: # type: ignore[override]
19
+ # Use centralized encoder to support Struct, UUID, date, datetime, etc.
20
+ return encode_json(content)
9
21
 
10
22
 
11
23
  # Simple helper functions for common response patterns
12
24
  def success_response(data=None, message="Success", status_code=200):
13
25
  """Create a success response with consistent structure"""
14
- return JSONResponse(
26
+ return TachyonJSONResponse(
15
27
  {"success": True, "message": message, "data": data}, status_code=status_code
16
28
  )
17
29
 
@@ -22,7 +34,7 @@ def error_response(error, status_code=400, code=None):
22
34
  if code:
23
35
  response_data["code"] = code
24
36
 
25
- return JSONResponse(response_data, status_code=status_code)
37
+ return TachyonJSONResponse(response_data, status_code=status_code)
26
38
 
27
39
 
28
40
  def not_found_response(error="Resource not found"):
@@ -41,7 +53,19 @@ def validation_error_response(error="Validation failed", errors=None):
41
53
  if errors:
42
54
  response_data["errors"] = errors
43
55
 
44
- return JSONResponse(response_data, status_code=422)
56
+ return TachyonJSONResponse(response_data, status_code=422)
57
+
58
+
59
+ def response_validation_error_response(error="Response validation error"):
60
+ """Create a 500 response validation error response"""
61
+ # Normalize message with prefix and include 'detail' for backward compatibility
62
+ msg = str(error)
63
+ if not msg.lower().startswith("response validation error"):
64
+ msg = f"Response validation error: {msg}"
65
+ return TachyonJSONResponse(
66
+ {"success": False, "error": msg, "detail": msg, "code": "RESPONSE_VALIDATION_ERROR"},
67
+ status_code=500,
68
+ )
45
69
 
46
70
 
47
71
  # Re-export Starlette responses for convenience
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.3
2
+ Name: tachyon-api
3
+ Version: 0.5.7
4
+ Summary: A lightweight, FastAPI-inspired web framework
5
+ License: GPL-3.0-or-later
6
+ Author: Juan Manuel Panozzo Zénere
7
+ Author-email: jm.panozzozenere@gmail.com
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: msgspec (>=0.19.0,<0.20.0)
16
+ Requires-Dist: orjson (>=3.11.1,<4.0.0)
17
+ Requires-Dist: ruff (>=0.12.7,<0.13.0)
18
+ Requires-Dist: starlette (>=0.47.2,<0.48.0)
19
+ Requires-Dist: typer (>=0.16.0,<0.17.0)
20
+ Requires-Dist: uvicorn (>=0.35.0,<0.36.0)
21
+ Description-Content-Type: text/markdown
22
+
23
+ # 🚀 Tachyon API
24
+
25
+ ![Version](https://img.shields.io/badge/version-0.5.7-blue.svg)
26
+ ![Python](https://img.shields.io/badge/python-3.10+-brightgreen.svg)
27
+ ![License](https://img.shields.io/badge/license-GPL--3.0-orange.svg)
28
+ ![Status](https://img.shields.io/badge/status-stable-brightgreen.svg)
29
+
30
+ **A lightweight, high-performance API framework for Python with the elegance of FastAPI and the speed of light.**
31
+
32
+ Tachyon API combines the intuitive decorator-based syntax you love with minimal dependencies and maximal performance. Built with Test-Driven Development from the ground up, it offers a cleaner, faster alternative with full ASGI compatibility.
33
+
34
+ ```python
35
+ from tachyon_api import Tachyon
36
+ from tachyon_api.models import Struct
37
+
38
+ app = Tachyon()
39
+
40
+ class User(Struct):
41
+ name: str
42
+ age: int
43
+
44
+ @app.get("/")
45
+ def hello_world():
46
+ return {"message": "Tachyon is running at lightspeed!"}
47
+
48
+ @app.post("/users")
49
+ def create_user(user: User):
50
+ return {"created": user.name}
51
+ ```
52
+
53
+ ## ✨ Features
54
+
55
+ - 🔍 Intuitive API (decorators) and minimal core
56
+ - 🧩 Implicit & explicit DI
57
+ - 📚 OpenAPI with Scalar, Swagger, ReDoc
58
+ - 🛠️ Router system
59
+ - 🔄 Middlewares (class + decorator)
60
+ - 🧠 Cache decorator with TTL (in-memory, Redis, Memcached)
61
+ - 🚀 High-performance JSON (msgspec + orjson)
62
+ - 🧾 Unified error format (422/500)
63
+ - 🧰 Default JSON response (TachyonJSONResponse)
64
+ - 🔒 End-to-end safety: request Body validation + typed response_model
65
+ - 📘 Deep OpenAPI schemas: nested Structs, Optional/List (nullable/array), formats (uuid, date-time)
66
+
67
+ ## 🧪 Test-Driven Development
68
+
69
+ Tachyon API is built with TDD principles at its core. The test suite covers routing, DI, params, body validation, responses, OpenAPI generation, caching, and example flows.
70
+
71
+ ## 🔌 Core Dependencies
72
+
73
+ - Starlette (ASGI)
74
+ - msgspec (validation/serialization)
75
+ - orjson (fast JSON)
76
+ - uvicorn (server)
77
+
78
+ ## 💉 Dependency Injection System
79
+
80
+ - Implicit injection: annotate with registered types
81
+ - Explicit injection: Depends() for clarity and control
82
+
83
+ ## 🔄 Middleware Support
84
+
85
+ - Built-in: CORSMiddleware, LoggerMiddleware
86
+ - Use app.add_middleware(...) or @app.middleware()
87
+
88
+ ## ⚡ Cache with TTL
89
+
90
+ - @cache(TTL=...) on routes and functions
91
+ - Per-app config and pluggable backends (InMemory, Redis, Memcached)
92
+
93
+ ## 📚 Example Application
94
+
95
+ The example demonstrates clean architecture, routers, middlewares, caching, and now end-to-end safety with OpenAPI:
96
+
97
+ - /orjson-demo: default JSON powered by orjson
98
+ - /api/v1/users/e2e: Body + response_model, unified errors and deep OpenAPI schemas
99
+
100
+ Run the example:
101
+
102
+ ```
103
+ cd example
104
+ python app.py
105
+ ```
106
+
107
+ Docs at /docs (Scalar), /swagger, /redoc.
108
+
109
+ ## ✅ Response models, OpenAPI params, and deep schemas
110
+
111
+ - Response models: set response_model=YourStruct to validate/convert outputs via msgspec before serializing.
112
+ - Parameter schemas: Optional[T] → nullable: true; List[T] → type: array with items.
113
+ - Deep schemas: nested Struct components, Optional/List items, and formats (uuid, date-time) are generated and referenced in components.
114
+
115
+ ## 🧾 Default JSON response and unified error format
116
+
117
+ - Default response: TachyonJSONResponse serializes complex types (UUID/date/datetime, Struct) via orjson and centralized encoders.
118
+ - 422 Validation: { success: false, error, code: VALIDATION_ERROR, [errors] }.
119
+ - 500 Response model: { success: false, error: "Response validation error: ...", detail, code: RESPONSE_VALIDATION_ERROR }.
120
+
121
+ ## 📝 Contributing
122
+
123
+ Contributions are welcome! Please feel free to submit a Pull Request.
124
+
125
+ 1. Fork the repository
126
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
127
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
128
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
129
+ 5. Open a Pull Request
130
+
131
+ ## 📜 License
132
+
133
+ This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
134
+
135
+ ## 🔮 Roadmap
136
+
137
+ - Exception system and global handlers
138
+ - CLI, scaffolding, and code quality tooling
139
+ - Authentication middleware and benchmarks
140
+ - More examples and deployment guides
141
+
142
+ ---
143
+
144
+ *Built with 💜 by developers, for developers*
145
+
@@ -1,16 +1,17 @@
1
- tachyon_api/__init__.py,sha256=MrwA2s-dfpygkU3iY6SghCeuWuF_tNwdWPQ9Onp-pmM,776
2
- tachyon_api/app.py,sha256=Gt_ufNXdHAOR9lYJ8inLO_n-BuO_8VYmjTsuiab-r-k,24271
1
+ tachyon_api/__init__.py,sha256=CEg69nSVW9iCLYgqVNvSr_7EICb9nrLuaDVxKlpm430,1207
2
+ tachyon_api/app.py,sha256=R4EHr85hc_1k76YasseqZrus7b9by5rGij3IAh_UACg,36160
3
+ tachyon_api/cache.py,sha256=FAlWcVbXf47_OWVtP92D2_J7ytwLD2pUV3ROy95u0Uc,8249
3
4
  tachyon_api/di.py,sha256=FyFghfUjU0OaE_lW2BqItgsZWXFbLvwFsvVywqFjl6c,1505
4
5
  tachyon_api/middlewares/__init__.py,sha256=BR1kqTj2IKdwhvVZCVn9p_rOFS0aOYoaC0bNX0C_0q4,121
5
6
  tachyon_api/middlewares/core.py,sha256=-dJTrI6KJbQzatopCUC5lau_JIZpl063697qdE4W0SY,1440
6
7
  tachyon_api/middlewares/cors.py,sha256=jZMqfFSz-14dwNIOOMqKaQm8Dypnf0V_WVW_4Uuc4AE,5716
7
8
  tachyon_api/middlewares/logger.py,sha256=8W543kmfu7f5LpDQOox8Z4t3QT1knko7bUx7kWLCzWw,4832
8
9
  tachyon_api/models.py,sha256=9pA5_ddMAcWW2dErAgeyG926NFezfCU68uuhW0qGMs4,2224
9
- tachyon_api/openapi.py,sha256=MHrA7DnVW88gyh1fiiLx1-uE1mc-eU4yseD6dCf_wxs,11688
10
+ tachyon_api/openapi.py,sha256=7gob0J9uQ6-fjEFHbS-KT0oH7pOko18TEW_dcT5FEkI,14623
10
11
  tachyon_api/params.py,sha256=gLGIsFEGr33_G0kakpqOFHRrf86w3egFJAjquZp3Lo0,2810
11
- tachyon_api/responses.py,sha256=FaPv4xTXTw4jvyErsA7Gkp0l0POln-0_0bE6rbtsmcE,1574
12
+ tachyon_api/responses.py,sha256=f-oe4EFQb-JkYEhWwE63qf9V8GLZ0PxouwsHQQnTj7M,2488
12
13
  tachyon_api/router.py,sha256=TlzgJ-UAfxBAQKy4YNVQIBObIDNzlebQ4G9qxsswJQ0,4204
13
- tachyon_api-0.5.5.dist-info/LICENSE,sha256=UZXUTSuWBt8V377-2_YE4ug9t6rcBAKrfOVhIBQ8zhk,712
14
- tachyon_api-0.5.5.dist-info/METADATA,sha256=F2WE9mBCeW6YJaYTaygrRphBpjCWkRLeQ1DjQNGjl2k,8413
15
- tachyon_api-0.5.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
16
- tachyon_api-0.5.5.dist-info/RECORD,,
14
+ tachyon_api-0.5.7.dist-info/LICENSE,sha256=UZXUTSuWBt8V377-2_YE4ug9t6rcBAKrfOVhIBQ8zhk,712
15
+ tachyon_api-0.5.7.dist-info/METADATA,sha256=aIG9RflcTQ1deMOaBWfFt3s4cyUqVQ2MQfB9mUuXNoU,5042
16
+ tachyon_api-0.5.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ tachyon_api-0.5.7.dist-info/RECORD,,