fastapi-extra 0.2.0__cp312-cp312-win_amd64.whl → 0.2.2__cp312-cp312-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.
fastapi_extra/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.0"
1
+ __version__ = "0.2.2"
2
2
 
3
3
 
4
4
  from fastapi import FastAPI
Binary file
@@ -0,0 +1,8 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+
5
+ class Cursor:
6
+
7
+ def next_val(self) -> int:
8
+ ...
@@ -5,7 +5,7 @@ from contextvars import ContextVar
5
5
  from typing import Any, Generic, Self, TypeVar
6
6
 
7
7
  from fastapi_extra.database.model import SQLModel
8
- from fastapi_extra.database.session import DefaultSession
8
+ from fastapi_extra.database.session import AsyncSession, DefaultSession
9
9
  from fastapi_extra.dependency import AbstractService
10
10
 
11
11
  Model = TypeVar("Model", bound=SQLModel)
@@ -32,7 +32,7 @@ class ModelService(AbstractService, Generic[Model], abstract=True):
32
32
  self.__session_container__.set(session)
33
33
 
34
34
  @property
35
- def session(self):
35
+ def session(self) -> AsyncSession:
36
36
  _session = self.__session_container__.get()
37
37
  assert _session is not None, "Session is not initialized"
38
38
  return _session
@@ -37,8 +37,8 @@ cdef class Cursor:
37
37
  self.cursor = count
38
38
  return (point << (_sequence_length + 4)) + (self.seed << _sequence_length) + count
39
39
 
40
- def next_val(self) -> str:
40
+ def next_val(self) -> int:
41
41
  index = self.fetch()
42
42
  while index == 0:
43
43
  index = self.fetch()
44
- return str(index)
44
+ return index
@@ -3,80 +3,113 @@ __describe__ = ""
3
3
 
4
4
  cimport cython
5
5
 
6
- from typing import MutableMapping
7
-
8
6
  from starlette import _utils as starlette_utils
9
7
  from starlette.datastructures import URL
10
8
  from starlette.responses import RedirectResponse
11
9
 
12
10
 
11
+ cdef int find_params(unicode path):
12
+ for i, ch in enumerate(path):
13
+ if ch == "{":
14
+ return i
15
+ return -1
16
+
17
+
18
+ cdef int get_longest_common_prefix(unicode path, unicode node_path):
19
+ cdef int i
20
+ cdef int max_len = min(len(path), len(node_path))
21
+ for i in range(max_len):
22
+ if path[i] != node_path[i]:
23
+ return i
24
+ return max_len
25
+
26
+
13
27
  @cython.no_gc
14
28
  cdef class RouteNode:
29
+
15
30
  cdef readonly:
16
- list routes
17
- dict leaves
18
31
  unicode prefix
32
+ list params_routes
33
+ list static_routes
34
+ dict children
35
+
36
+ cdef public object parent
19
37
 
20
- def __cinit__(self, prefix):
38
+ def __cinit__(self, prefix: str):
21
39
  self.prefix = prefix
22
- self.routes = []
23
- self.leaves = {}
40
+ self.params_routes = []
41
+ self.static_routes = []
42
+ self.children = {}
43
+ self.parent = None
44
+
45
+ def add_route(self, fullpath: str, handler: object):
46
+ wild_child = False
47
+ if (index := find_params(fullpath)) >= 0:
48
+ wild_child = True
49
+ path = fullpath[:index]
50
+ else:
51
+ path = fullpath
52
+ insert_route(self, path, wild_child, handler)
24
53
 
25
- def add_route(self, route):
26
- self.routes.append(route)
27
54
 
28
- def add_leaf(self, node):
29
- if node.prefix in self.leaves:
30
- raise KeyError(node.prefix)
31
- else:
32
- self.leaves[node.prefix] = node
55
+ cdef void insert_route(RouteNode node, unicode path, bint wild_child, object handler):
56
+ if node.prefix == path:
57
+ add_node(node, wild_child, handler)
58
+ return
33
59
 
60
+ cdef Py_UCS4 key = path.removeprefix(node.prefix)[0]
61
+ if key not in node.children:
62
+ add_child_node(node, key, path, wild_child, handler)
63
+ return
34
64
 
35
- cdef list change_path_to_ranks(unicode path):
36
- ranks = path.lstrip('/').split('/')
37
- return ranks
65
+ child_node = node.children[key]
66
+ i = get_longest_common_prefix(child_node.prefix, path)
67
+ longest_prefix = child_node.prefix[0: i]
68
+ if i == len(child_node.prefix):
69
+ insert_route(node.children[key], path, wild_child, handler)
70
+ return
71
+ next_node = RouteNode.__new__(RouteNode, longest_prefix)
72
+ next_node.parent = node
73
+ node.children[key] = next_node
74
+ next_node.children[child_node.prefix[i]] = child_node
75
+ child_node.parent = next_node
76
+ insert_route(next_node, path, wild_child, handler)
38
77
 
39
78
 
40
- cdef void add_route(unicode path, RouteNode root, object route):
41
- current_node = root
42
- ranks = change_path_to_ranks(path)
43
- for r in ranks:
44
- if r.find('{') >= 0 and r.find('}') > 0:
45
- break
46
- if not r:
47
- continue
48
- if r in current_node.leaves:
49
- current_node = current_node.leaves[r]
50
- else:
51
- next_node = RouteNode.__new__(RouteNode, r)
52
- current_node.add_leaf(next_node)
53
- current_node = next_node
54
- current_node.add_route(route)
55
-
56
-
57
- cdef list find_routes(unicode path, RouteNode root):
58
- current_node = root
59
- ranks = change_path_to_ranks(path)
60
-
61
- routes = []
62
- if current_node.routes:
63
- routes += current_node.routes
64
- for r in ranks:
65
- if not r:
66
- continue
67
- if r in current_node.leaves:
68
- current_node = current_node.leaves[r]
69
- if current_node.routes:
70
- routes += current_node.routes
71
- continue
72
- break
73
- return routes
79
+ cdef inline void add_child_node(RouteNode node, Py_UCS4 key, unicode path, bint wild_child, object handler):
80
+ child = RouteNode.__new__(RouteNode, path)
81
+ child.parent = node
82
+ add_node(child, wild_child, handler)
83
+ node.children[key] = child
84
+
85
+
86
+ cdef inline void add_node(RouteNode node, bint wild_child, object handler):
87
+ if wild_child:
88
+ node.params_routes.append(handler)
89
+ else:
90
+ node.static_routes.append(handler)
74
91
 
75
92
 
76
93
  root_node = RouteNode.__new__(RouteNode, "")
77
94
 
78
95
 
79
- async def handle(router, scope, receive, send):
96
+ cdef RouteNode search_node(unicode url):
97
+ cdef RouteNode current_node = root_node
98
+ cdef int n = len(url)
99
+ cdef int i = get_longest_common_prefix(url, current_node.prefix)
100
+
101
+ while i < n:
102
+ key = url[i]
103
+ if key not in current_node.children:
104
+ break
105
+ current_node = current_node.children[key]
106
+ i = get_longest_common_prefix(url, current_node.prefix)
107
+
108
+ return current_node
109
+
110
+
111
+ async def handle(scope, receive, send):
112
+ router = scope["app"].router
80
113
  assert scope["type"] in ("http", "websocket", "lifespan")
81
114
 
82
115
  if "router" not in scope:
@@ -89,47 +122,70 @@ async def handle(router, scope, receive, send):
89
122
  partial = None
90
123
 
91
124
  scope["path"] = route_path = starlette_utils.get_route_path(scope)
92
- scope["root_path"] = ""
93
- matched_routes = find_routes(route_path, root_node)
94
- n = len(matched_routes)
95
-
96
- for i in range(n):
97
- route = matched_routes[n - i - 1]
98
- match, child_scope = route.matches(scope)
99
- if match.value == 2:
100
- scope.update(child_scope)
101
- await route.handle(scope, receive, send)
102
- return
103
- elif match.value == 1 and partial is None:
104
- partial = route
105
- partial_scope = child_scope
125
+ leaf_node = search_node(route_path)
126
+
127
+ if leaf_node.prefix == route_path:
128
+ for route in leaf_node.static_routes:
129
+ match, child_scope = route.matches(scope)
130
+ if match.value == 2:
131
+ scope.update(child_scope)
132
+ await route.handle(scope, receive, send)
133
+ return
134
+ elif match.value == 1 and partial is None:
135
+ partial = route
136
+ partial_scope = child_scope
137
+ else:
138
+ current_node = leaf_node
139
+ routes = current_node.params_routes
140
+ while current_node.parent:
141
+ for route in routes:
142
+ match, child_scope = route.matches(scope)
143
+ if match.value == 2:
144
+ scope.update(child_scope)
145
+ await route.handle(scope, receive, send)
146
+ return
147
+ elif match.value == 1 and partial is None:
148
+ partial = route
149
+ partial_scope = child_scope
150
+ current_node = current_node.parent
106
151
 
107
152
  if partial is not None:
108
153
  scope.update(partial_scope)
109
154
  await partial.handle(scope, receive, send)
110
155
  return
111
156
 
112
-
113
157
  if scope["type"] == "http" and router.redirect_slashes and route_path != "/":
114
158
  redirect_scope = dict(scope)
115
159
  if route_path.endswith("/"):
116
160
  redirect_scope["path"] = redirect_scope["path"].rstrip("/")
117
161
  else:
118
162
  redirect_scope["path"] = redirect_scope["path"] + "/"
119
-
120
- for i in range(n):
121
- route = matched_routes[n - i - 1]
122
- match, child_scope = route.matches(redirect_scope)
123
- if match.value != 0:
124
- redirect_url = URL(scope=redirect_scope)
125
- response = RedirectResponse(url=str(redirect_url))
126
- await response(scope, receive, send)
127
- return
163
+
164
+ if leaf_node.prefix == redirect_scope["path"]:
165
+ for route in leaf_node.static_routes:
166
+ match, child_scope = route.matches(redirect_scope)
167
+ if match.value != 0:
168
+ redirect_url = URL(scope=redirect_scope)
169
+ response = RedirectResponse(url=str(redirect_url))
170
+ await response(scope, receive, send)
171
+ return
172
+ else:
173
+ current_node = leaf_node
174
+ routes = current_node.params_routes
175
+ while current_node.parent:
176
+ for route in routes:
177
+ if match.value != 0:
178
+ redirect_url = URL(scope=redirect_scope)
179
+ response = RedirectResponse(url=str(redirect_url))
180
+ await response(scope, receive, send)
181
+ return
182
+ current_node = current_node.parent
128
183
 
129
184
  await router.default(scope, receive, send)
130
185
 
131
186
 
132
187
  def install(app):
133
188
  for route in app.routes:
134
- add_route(route.path, root_node, route)
135
- app.router.app = handle
189
+ root_node.add_route(route.path, route)
190
+
191
+ app.router.middleware_stack = handle
Binary file
@@ -0,0 +1,16 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+
5
+ from typing import Any
6
+ from fastapi import FastAPI
7
+
8
+
9
+ class RouteNode:
10
+
11
+ def add_route(self, fullpath: str, handler: Any):
12
+ ...
13
+
14
+
15
+ def install(app: FastAPI) -> None:
16
+ ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-extra
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -1,10 +1,12 @@
1
- fastapi_extra/__init__.py,sha256=MXf66njTRTUqUMbjSkWGF6HxU-1Dm8UVgfC5qh-uehM,286
2
- fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=0HTvLo9ZEY-GwvATHIVeaU5x-B1nxhdA8EvCZ6WhOu8,53760
1
+ fastapi_extra/__init__.py,sha256=15p7teY89i6qSlOfYqQjU6a7fRvBPC-tPXGQU6Oo0O8,286
2
+ fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=_Lkcm1Mi2PRMXa4M4QO5ydq-5N8Pk0pAjb8hiDLp13A,53760
3
+ fastapi_extra/cursor.pyi,sha256=t1_ub-dRhu3OcXB_dIw5MdBG9r1n7853Vtgzc6bk6dw,115
3
4
  fastapi_extra/dependency.py,sha256=LtYnOTMyhOQUFSbNEViw7lxJvAFpPWbyuY4e2goSG-8,2130
4
5
  fastapi_extra/form.py,sha256=Fs9uEDOQThjFroDVTrjWnIGJ107BgXCppIVTymwQLzg,1247
5
6
  fastapi_extra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
7
  fastapi_extra/response.py,sha256=DHvhOSgwot5eBNKuI_jPYxZ5rshZ55Xkg-FNBJlHD1E,9609
7
- fastapi_extra/routing.cp312-win_amd64.pyd,sha256=XlIyK5YoFeGcNGb_Mv27BYdjEEYJh2qlJagu7dZIJHk,87552
8
+ fastapi_extra/routing.cp312-win_amd64.pyd,sha256=e_t1yZsSSJm-z1ppnXVOch9TGQF2q9ipphYNsY_9Otk,91136
9
+ fastapi_extra/routing.pyi,sha256=QslcOUnVJqFCqt14VJQwjUYQ7Qd3bQBTo34Yx1xS7TA,243
8
10
  fastapi_extra/settings.py,sha256=cCcwaper5GiNNoT4gNKqf-iloSOTNnMsiUR0knJx4Mw,1461
9
11
  fastapi_extra/types.py,sha256=3z6gUnao6WZL76asZYmex20xfY9mvYA-RbnsxUcui30,819
10
12
  fastapi_extra/utils.py,sha256=tsPX3kpF_P5D9Bd3gnlG6rkVsLkv5gbxjml-s6ZL_6I,346
@@ -12,12 +14,12 @@ fastapi_extra/cache/__init__.py,sha256=kq4b_AYKCSJ0fEp4rqpeaoNJilko4XbtfC81xzUaY
12
14
  fastapi_extra/cache/redis.py,sha256=-hr2DRkmruzH4cIArdjsytqRiiWWtDJt9GIDy38wmtQ,1600
13
15
  fastapi_extra/database/__init__.py,sha256=pCYUoEylTponWqpR0AXJHcRo_cs4fnEXVVV9B5J9rt0,384
14
16
  fastapi_extra/database/model.py,sha256=2lDi9PQ5F0PSM7BGZozZf1wSefpXnTWqAVzEyGPaXRI,2453
15
- fastapi_extra/database/service.py,sha256=-tBC17jEbGZhnxdtWyftP2BUXfCwcOtxzNZ53qLp518,1798
17
+ fastapi_extra/database/service.py,sha256=V5ojRpiWs9_B3XVQz-PtyvBePfmByTn8WG2gXwuhpO0,1828
16
18
  fastapi_extra/database/session.py,sha256=PALArHhXNS2gCgeMkiKjyatvINV_VW1LjgEIwXDKyfQ,1903
17
- fastapi_extra/native/cursor.pyx,sha256=bESprFDgk9gGjyPQ4YCSg51dov2WB6s60XrOs3r5-r0,1146
18
- fastapi_extra/native/routing.pyx,sha256=GrdGAoBospwCpxMHBon5cuRYcz9ifAFSSYa2Ytf49lg,3841
19
- fastapi_extra-0.2.0.dist-info/licenses/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
20
- fastapi_extra-0.2.0.dist-info/METADATA,sha256=CRLVgSMLpfE9MQ-pj1aEAdC_c_7wCtGG9pCA2ARn-ts,1371
21
- fastapi_extra-0.2.0.dist-info/WHEEL,sha256=RYNUKzg4pggqpqERKe4OLbPF4ZPP-Ng-rmq_sekLDXg,101
22
- fastapi_extra-0.2.0.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
23
- fastapi_extra-0.2.0.dist-info/RECORD,,
19
+ fastapi_extra/native/cursor.pyx,sha256=GchJIPY6e5scuyl1BbA-Et0CdRcHOI3xgsTvDem0B70,1141
20
+ fastapi_extra/native/routing.pyx,sha256=NisJ9YaxzceDVWJnF3EeVXWT8rv_6WRDuIfLEK0eWbc,6233
21
+ fastapi_extra-0.2.2.dist-info/licenses/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
22
+ fastapi_extra-0.2.2.dist-info/METADATA,sha256=buJJyc4jA2ve_5jdhJ0Gm6HXiazZC2rwJm6oGEktH3A,1371
23
+ fastapi_extra-0.2.2.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
24
+ fastapi_extra-0.2.2.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
25
+ fastapi_extra-0.2.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-win_amd64
5
5