7
7
import re
8
8
9
9
from django .urls import resolve
10
- from django .urls .resolvers import get_resolver
11
10
import openapi_core
11
+ from openapi_core .contrib .django import DjangoOpenAPIResponseFactory
12
+ from openapi_core .contrib .django import DjangoOpenAPIRequestFactory
12
13
from openapi_core .schema .schemas .models import Format
13
- from openapi_core .wrappers .base import BaseOpenAPIResponse
14
- from openapi_core .wrappers .base import BaseOpenAPIRequest
15
14
from openapi_core .validation .request .validators import RequestValidator
16
15
from openapi_core .validation .response .validators import ResponseValidator
17
16
from openapi_core .schema .parameters .exceptions import OpenAPIParameterError
18
17
from openapi_core .schema .media_types .exceptions import OpenAPIMediaTypeError
18
+ from openapi_core .templating import util
19
19
from rest_framework import status
20
20
import yaml
21
21
24
24
os .path .dirname (os .path .abspath (__file__ )), os .pardir , os .pardir ,
25
25
os .pardir , 'docs' , 'api' , 'schemas' )
26
26
27
- HEADER_REGEXES = (
28
- re .compile (r'^HTTP_.+$' ), re .compile (r'^CONTENT_TYPE$' ),
29
- re .compile (r'^CONTENT_LENGTH$' ))
30
-
31
27
_LOADED_SPECS = {}
32
28
33
29
30
+ # HACK! Workaround for https://github.com/p1c2u/openapi-core/issues/226
31
+ def search (path_pattern , full_url_pattern ):
32
+ p = util .Parser (path_pattern )
33
+ p ._expression = p ._expression + '$'
34
+ result = p .search (full_url_pattern )
35
+ if not result or any ('/' in arg for arg in result .named .values ()):
36
+ return None
37
+
38
+ return result
39
+
40
+
41
+ util .search = search
42
+
43
+
34
44
class RegexValidator (object ):
35
45
36
46
def __init__ (self , regex ):
@@ -61,113 +71,6 @@ def __call__(self, value):
61
71
}
62
72
63
73
64
- def _extract_headers (request ):
65
- request_headers = {}
66
- for header in request .META :
67
- for regex in HEADER_REGEXES :
68
- if regex .match (header ):
69
- request_headers [header ] = request .META [header ]
70
-
71
- return request_headers
72
-
73
-
74
- def _resolve (path , resolver = None ):
75
- """Resolve a given path to its matching regex (Django 2.x).
76
-
77
- This is essentially a re-implementation of ``URLResolver.resolve`` that
78
- builds and returns the matched regex instead of the view itself.
79
-
80
- >>> _resolve('/api/1.0/patches/1/checks/')
81
- "^api/(?:(?P<version>(1.0|1.1))/)patches/(?P<patch_id>[^/]+)/checks/$"
82
- """
83
- from django .urls .resolvers import URLResolver # noqa
84
- from django .urls .resolvers import RegexPattern # noqa
85
-
86
- resolver = resolver or get_resolver ()
87
- match = resolver .pattern .match (path )
88
-
89
- # we dont handle any other type of pattern at the moment
90
- assert isinstance (resolver .pattern , RegexPattern )
91
-
92
- if not match :
93
- return
94
-
95
- if isinstance (resolver , URLResolver ):
96
- sub_path , args , kwargs = match
97
- for sub_resolver in resolver .url_patterns :
98
- sub_match = _resolve (sub_path , sub_resolver )
99
- if not sub_match :
100
- continue
101
-
102
- kwargs .update (sub_match [2 ])
103
- args += sub_match [1 ]
104
-
105
- regex = resolver .pattern ._regex + sub_match [0 ].lstrip ('^' )
106
-
107
- return regex , args , kwargs
108
- else :
109
- _ , args , kwargs = match
110
- return resolver .pattern ._regex , args , kwargs
111
-
112
-
113
- def _resolve_path_to_kwargs (path ):
114
- """Convert a path to the kwargs used to resolve it.
115
-
116
- >>> resolve_path_to_kwargs('/api/1.0/patches/1/checks/')
117
- {"patch_id": 1}
118
- """
119
- # TODO(stephenfin): Handle definition by args
120
- _ , _ , kwargs = _resolve (path )
121
-
122
- results = {}
123
- for key , value in kwargs .items ():
124
- if key == 'version' :
125
- continue
126
-
127
- if key == 'pk' :
128
- key = 'id'
129
-
130
- results [key ] = value
131
-
132
- return results
133
-
134
-
135
- def _resolve_path_to_template (path ):
136
- """Convert a path to a template string.
137
-
138
- >>> resolve_path_to_template('/api/1.0/patches/1/checks/')
139
- "/api/{version}/patches/{patch_id}/checks/"
140
- """
141
- regex , _ , _ = _resolve (path )
142
- regex = re .match (regex , path )
143
-
144
- result = ''
145
- prev_index = 0
146
- for index , group in enumerate (regex .groups (), 1 ):
147
- if not group : # group didn't match anything
148
- continue
149
-
150
- result += path [prev_index :regex .start (index )]
151
- prev_index = regex .end (index )
152
- # groupindex keys by name, not index. Switch that.
153
- for name , index_ in regex .re .groupindex .items ():
154
- if index_ == (index ):
155
- # special-case version group
156
- if name == 'version' :
157
- result += group
158
- break
159
-
160
- if name == 'pk' :
161
- name = 'id'
162
-
163
- result += '{%s}' % name
164
- break
165
-
166
- result += path [prev_index :]
167
-
168
- return result
169
-
170
-
171
74
def _load_spec (version ):
172
75
global _LOADED_SPECS
173
76
@@ -186,72 +89,14 @@ def _load_spec(version):
186
89
return _LOADED_SPECS [version ]
187
90
188
91
189
- class DRFOpenAPIRequest (BaseOpenAPIRequest ):
190
-
191
- def __init__ (self , request ):
192
- self .request = request
193
-
194
- @property
195
- def host_url (self ):
196
- return self .request .get_host ()
197
-
198
- @property
199
- def path (self ):
200
- return self .request .path
201
-
202
- @property
203
- def method (self ):
204
- return self .request .method .lower ()
205
-
206
- @property
207
- def path_pattern (self ):
208
- return _resolve_path_to_template (self .request .path_info )
209
-
210
- @property
211
- def parameters (self ):
212
- return {
213
- 'path' : _resolve_path_to_kwargs (self .request .path_info ),
214
- 'query' : self .request .GET ,
215
- 'header' : _extract_headers (self .request ),
216
- 'cookie' : self .request .COOKIES ,
217
- }
218
-
219
- @property
220
- def body (self ):
221
- return self .request .body .decode ('utf-8' )
222
-
223
- @property
224
- def mimetype (self ):
225
- return self .request .content_type
226
-
227
-
228
- class DRFOpenAPIResponse (BaseOpenAPIResponse ):
229
-
230
- def __init__ (self , response ):
231
- self .response = response
232
-
233
- @property
234
- def data (self ):
235
- return self .response .content .decode ('utf-8' )
236
-
237
- @property
238
- def status_code (self ):
239
- return self .response .status_code
240
-
241
- @property
242
- def mimetype (self ):
243
- # TODO(stephenfin): Why isn't this populated?
244
- return 'application/json'
245
-
246
-
247
92
def validate_data (path , request , response , validate_request ,
248
93
validate_response ):
249
94
if response .status_code == status .HTTP_405_METHOD_NOT_ALLOWED :
250
95
return
251
96
252
97
spec = _load_spec (resolve (path ).kwargs .get ('version' ))
253
- request = DRFOpenAPIRequest (request )
254
- response = DRFOpenAPIResponse (response )
98
+ request = DjangoOpenAPIRequestFactory . create (request )
99
+ response = DjangoOpenAPIResponseFactory . create (response )
255
100
256
101
# request
257
102
if validate_request :
0 commit comments