77import re
88
99from django .urls import resolve
10- from django .urls .resolvers import get_resolver
1110import openapi_core
11+ from openapi_core .contrib .django import DjangoOpenAPIResponseFactory
12+ from openapi_core .contrib .django import DjangoOpenAPIRequestFactory
1213from openapi_core .schema .schemas .models import Format
13- from openapi_core .wrappers .base import BaseOpenAPIResponse
14- from openapi_core .wrappers .base import BaseOpenAPIRequest
1514from openapi_core .validation .request .validators import RequestValidator
1615from openapi_core .validation .response .validators import ResponseValidator
1716from openapi_core .schema .parameters .exceptions import OpenAPIParameterError
1817from openapi_core .schema .media_types .exceptions import OpenAPIMediaTypeError
18+ from openapi_core .templating import util
1919from rest_framework import status
2020import yaml
2121
2424 os .path .dirname (os .path .abspath (__file__ )), os .pardir , os .pardir ,
2525 os .pardir , 'docs' , 'api' , 'schemas' )
2626
27- HEADER_REGEXES = (
28- re .compile (r'^HTTP_.+$' ), re .compile (r'^CONTENT_TYPE$' ),
29- re .compile (r'^CONTENT_LENGTH$' ))
30-
3127_LOADED_SPECS = {}
3228
3329
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+
3444class RegexValidator (object ):
3545
3646 def __init__ (self , regex ):
@@ -61,113 +71,6 @@ def __call__(self, value):
6171}
6272
6373
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-
17174def _load_spec (version ):
17275 global _LOADED_SPECS
17376
@@ -186,72 +89,14 @@ def _load_spec(version):
18689 return _LOADED_SPECS [version ]
18790
18891
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-
24792def validate_data (path , request , response , validate_request ,
24893 validate_response ):
24994 if response .status_code == status .HTTP_405_METHOD_NOT_ALLOWED :
25095 return
25196
25297 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 )
255100
256101 # request
257102 if validate_request :
0 commit comments