1
1
from __future__ import absolute_import
2
2
from .getter import UrlGetter , LocalGetter
3
+ from .resolve import SwaggerResolver
3
4
from .primitives import SwaggerPrimitive
4
5
from .spec .v1_2 .parser import ResourceListContext
5
6
from .spec .v2_0 .parser import SwaggerContext
6
7
from .spec .v2_0 .objects import Operation
8
+ from .spec .base import BaseObj
7
9
from .scan import Scanner
8
10
from .scanner import TypeReduce , CycleDetector
9
11
from .scanner .v1_2 import Upgrade
10
- from .scanner .v2_0 import AssignParent , Resolve , PatchObject , YamlFixer , Aggregate
12
+ from .scanner .v2_0 import AssignParent , Merge , Resolve , PatchObject , YamlFixer , Aggregate , NormalizeRef
11
13
from pyswagger import utils , errs , consts
12
- import inspect
13
14
import base64
14
15
import six
15
16
import weakref
16
- import os
17
17
import logging
18
18
19
19
@@ -33,11 +33,10 @@ class SwaggerApp(object):
33
33
sc_path : ('/' , '#/paths' )
34
34
}
35
35
36
- def __init__ (self , url = None , app_cache = None , url_load_hook = None , sep = consts .private .SCOPE_SEPARATOR , prim = None ):
36
+ def __init__ (self , url = None , url_load_hook = None , sep = consts .private .SCOPE_SEPARATOR , prim = None ):
37
37
""" constructor
38
38
39
39
:param url str: url of swagger.json
40
- :param dict app_cache: a url map shared by SwaggerApp(s), mapping from url to SwaggerApp
41
40
:param func url_load_hook: a way to redirect url to a accessible place. for self testing.
42
41
:param sep str: separator used by pyswager.utils.ScopeDict
43
42
:param prim pyswagger.primitives.SwaggerPrimitive: factory for primitives in Swagger.
@@ -54,15 +53,14 @@ def __init__(self, url=None, app_cache=None, url_load_hook=None, sep=consts.priv
54
53
self .__schemes = []
55
54
self .__url = url
56
55
57
- # a map from url to SwaggerApp
58
- self .__app_cache = {} if app_cache == None else app_cache
56
+ # a map from json-reference to
57
+ # - spec.BaseObj
58
+ # - a map from json-pointer to spec.BaseObj
59
+ self .__objs = {}
60
+ self .__resolver = SwaggerResolver (url_load_hook )
59
61
# keep a string reference to SwaggerApp when resolve
60
62
self .__strong_refs = []
61
63
62
- # things to make unittest easier,
63
- # all urls to load json would go through this hook
64
- self .__url_load_hook = url_load_hook
65
-
66
64
# allow init App-wised SCOPE_SEPARATOR
67
65
self .__sep = sep
68
66
@@ -139,12 +137,6 @@ def url(self):
139
137
"""
140
138
return self .__url
141
139
142
- @property
143
- def _app_cache (self ):
144
- """ internal usage
145
- """
146
- return self .__app_cache
147
-
148
140
@property
149
141
def prim_factory (self ):
150
142
""" primitive factory used by this app
@@ -153,41 +145,18 @@ def prim_factory(self):
153
145
"""
154
146
return self .__prim
155
147
156
- def _load_obj (self , url , getter = None , parser = None ):
157
- """
148
+ def load_obj (self , jref , getter = None , parser = None ):
149
+ """ load a object(those in spec._version_.objects) from a JSON reference.
158
150
"""
159
- if url in self .__app_cache :
160
- logger .info ('{0} hit cache' .format (url ))
161
-
162
- # look into cache first
163
- return
164
-
165
- # apply hook when use this url to load
166
- # note that we didn't cache SwaggerApp with this local_url
167
- local_url = url if not self .__url_load_hook else self .__url_load_hook (url )
168
-
169
- logger .info ('{0} patch to {1}' .format (url , local_url ))
170
-
171
- if not getter :
172
- getter = UrlGetter
173
- p = six .moves .urllib .parse .urlparse (local_url )
174
- if p .scheme == 'file' and p .path :
175
- getter = LocalGetter (os .path .join (p .netloc , p .path ))
176
-
177
- if inspect .isclass (getter ):
178
- # default initialization is passing the url
179
- # you can override this behavior by passing an
180
- # initialized getter object.
181
- getter = getter (local_url )
151
+ obj = self .__resolver .resolve (jref , getter )
182
152
183
153
# get root document to check its swagger version.
184
- obj , _ = six .advance_iterator (getter )
185
154
tmp = {'_tmp_' : {}}
186
155
version = utils .get_swagger_version (obj )
187
156
if version == '1.2' :
188
157
# swagger 1.2
189
158
with ResourceListContext (tmp , '_tmp_' ) as ctx :
190
- ctx .parse (getter , obj )
159
+ ctx .parse (obj , jref , self . __resolver , getter )
191
160
elif version == '2.0' :
192
161
# swagger 2.0
193
162
with SwaggerContext (tmp , '_tmp_' ) as ctx :
@@ -198,13 +167,55 @@ def _load_obj(self, url, getter=None, parser=None):
198
167
199
168
version = tmp ['_tmp_' ].__swagger_version__ if hasattr (tmp ['_tmp_' ], '__swagger_version__' ) else version
200
169
else :
201
- raise NotImplementedError ('Unsupported Swagger Version: {0} from {1}' .format (version , url ))
170
+ raise NotImplementedError ('Unsupported Swagger Version: {0} from {1}' .format (version , jref ))
171
+
172
+ if not tmp ['_tmp_' ]:
173
+ raise Exception ('Unable to parse object from {0}' .format (jref ))
202
174
203
175
logger .info ('version: {0}' .format (version ))
204
176
205
- self .__app_cache [url ] = weakref .proxy (self ) # avoid circular reference
206
- self .__version = version
207
- self .__raw = tmp ['_tmp_' ]
177
+ return tmp ['_tmp_' ], version
178
+
179
+ def prepare_obj (self , obj , jref ):
180
+ """ basic preparation of an object(those in sepc._version_.objects),
181
+ and cache the 'prepared' object.
182
+ """
183
+ if not obj :
184
+ raise Exception ('unexpected, passing {0}:{1} to prepare' .format (obj , jref ))
185
+
186
+ s = Scanner (self )
187
+ if self .version == '1.2' :
188
+ # upgrade from 1.2 to 2.0
189
+ converter = Upgrade (self .__sep )
190
+ s .scan (root = obj , route = [converter ])
191
+ obj = converter .swagger
192
+
193
+ if not obj :
194
+ raise Exception ('unable to upgrade from 1.2: {0}' .format (jref ))
195
+
196
+ s .scan (root = obj , route = [AssignParent ()])
197
+
198
+ # normalize $ref
199
+ url , jp = utils .jr_split (jref )
200
+ s .scan (root = obj , route = [NormalizeRef (url )])
201
+ # fix for yaml that treat response code as number
202
+ s .scan (root = obj , route = [YamlFixer ()], leaves = [Operation ])
203
+
204
+ # cache this object
205
+ if url not in self .__objs :
206
+ if jp == '#' :
207
+ self .__objs [url ] = obj
208
+ else :
209
+ self .__objs [url ] = {jp : obj }
210
+ else :
211
+ if not isinstance (self .__objs [url ], dict ):
212
+ raise Exception ('it should be able to resolve with BaseObj' )
213
+ self .__objs [url ].update ({jp : obj })
214
+
215
+ # pre resolve Schema Object
216
+ # note: make sure this object is cached before using 'Resolve' scanner
217
+ s .scan (root = obj , route = [Resolve ()])
218
+ return obj
208
219
209
220
def _validate (self ):
210
221
""" check if this Swagger API valid or not.
@@ -232,44 +243,8 @@ def _validate(self):
232
243
s .scan (route = [v ], root = self .__raw )
233
244
return v .errs
234
245
235
- def _prepare_obj (self , strict = True ):
236
- """
237
- """
238
- if self .__root :
239
- return
240
-
241
- s = Scanner (self )
242
- self .validate (strict = strict )
243
-
244
- if self .version == '1.2' :
245
- converter = Upgrade (self .__sep )
246
- s .scan (root = self .raw , route = [converter ])
247
- obj = converter .swagger
248
-
249
- # We only have to run this scanner when upgrading from 1.2.
250
- # Mainly because we initial BaseObj via NullContext
251
- s .scan (root = obj , route = [AssignParent ()])
252
-
253
- self .__root = obj
254
- elif self .version == '2.0' :
255
- s .scan (root = self .raw , route = [YamlFixer ()], leaves = [Operation ])
256
- self .__root = self .raw
257
- else :
258
- raise NotImplementedError ('Unsupported Version: {0}' .format (self .__version ))
259
-
260
- if hasattr (self .__root , 'schemes' ) and self .__root .schemes :
261
- if len (self .__root .schemes ) > 0 :
262
- self .__schemes = self .__root .schemes
263
- else :
264
- # extract schemes from the url to load spec
265
- self .__schemes = [six .moves .urlparse (self .__url ).schemes ]
266
-
267
- s .scan (root = self .__root , route = [Resolve ()])
268
- s .scan (root = self .__root , route = [PatchObject ()])
269
- s .scan (root = self .__root , route = [Aggregate ()])
270
-
271
246
@classmethod
272
- def load (kls , url , getter = None , parser = None , app_cache = None , url_load_hook = None , sep = consts .private .SCOPE_SEPARATOR , prim = None ):
247
+ def load (kls , url , getter = None , parser = None , url_load_hook = None , sep = consts .private .SCOPE_SEPARATOR , prim = None ):
273
248
""" load json as a raw SwaggerApp
274
249
275
250
:param str url: url of path of Swagger API definition
@@ -290,9 +265,10 @@ def load(kls, url, getter=None, parser=None, app_cache=None, url_load_hook=None,
290
265
logger .info ('load with [{0}]' .format (url ))
291
266
292
267
url = utils .normalize_url (url )
293
- app = kls (url , app_cache = app_cache , url_load_hook = url_load_hook , sep = sep , prim = prim )
294
-
295
- app ._load_obj (url , getter , parser )
268
+ app = kls (url , url_load_hook = url_load_hook , sep = sep , prim = prim )
269
+ app .__raw , app .__version = app .load_obj (url , getter = getter , parser = parser )
270
+ if app .__version not in ['1.2' , '2.0' ]:
271
+ raise NotImplementedError ('Unsupported Version: {0}' .format (self .__version ))
296
272
297
273
# update schem if any
298
274
p = six .moves .urllib .parse .urlparse (url )
@@ -321,10 +297,22 @@ def prepare(self, strict=True):
321
297
:param bool strict: when in strict mode, exception would be raised if not valid.
322
298
"""
323
299
324
- self ._prepare_obj (strict = strict )
300
+ self .validate (strict = strict )
301
+ self .__root = self .prepare_obj (self .raw , self .__url )
302
+
303
+ if hasattr (self .__root , 'schemes' ) and self .__root .schemes :
304
+ if len (self .__root .schemes ) > 0 :
305
+ self .__schemes = self .__root .schemes
306
+ else :
307
+ # extract schemes from the url to load spec
308
+ self .__schemes = [six .moves .urlparse (self .__url ).schemes ]
325
309
326
- # reducer for Operation
327
310
s = Scanner (self )
311
+ s .scan (root = self .__root , route = [Merge ()])
312
+ s .scan (root = self .__root , route = [PatchObject ()])
313
+ s .scan (root = self .__root , route = [Aggregate ()])
314
+
315
+ # reducer for Operation
328
316
tr = TypeReduce (self .__sep )
329
317
cy = CycleDetector ()
330
318
s .scan (root = self .__root , route = [tr , cy ])
@@ -381,28 +369,40 @@ def resolve(self, jref, parser=None):
381
369
if jref == None or len (jref ) == 0 :
382
370
raise ValueError ('Empty Path is not allowed' )
383
371
372
+ obj = None
384
373
url , jp = utils .jr_split (jref )
385
374
if url :
386
- if url not in self .__app_cache :
387
- # This loaded SwaggerApp would be kept in app_cache.
388
- app = SwaggerApp .load (url , parser = parser , app_cache = self .__app_cache , url_load_hook = self .__url_load_hook )
389
- app .prepare ()
390
-
391
- # nothing but only keeping a strong reference of
392
- # loaded SwaggerApp.
393
- self .__strong_refs .append (app )
394
-
395
- return self .__app_cache [url ].resolve (jp )
375
+ # check cacahed object against json reference by
376
+ # comparing url first, and find those object prefixed with
377
+ # the JSON pointer.
378
+ o = self .__objs .get (url , None )
379
+ if o :
380
+ if isinstance (o , BaseObj ):
381
+ obj = o .resolve (utils .jp_split (jp )[1 :])
382
+ elif isinstance (o , dict ):
383
+ for k , v in six .iteritems (o ):
384
+ if jp .startswith (k ):
385
+ obj = v .resolve (utils .jp_split (jp [len (k ):])[1 :])
386
+ break
387
+ else :
388
+ raise Exception ('Unknown Cached Object: {0}' .format (str (type (o ))))
396
389
397
- if not jp .startswith ('#' ):
398
- raise ValueError ('Invalid Path, root element should be \' #\' , but [{0}]' .format (jref ))
390
+ # this object is not loaded yet, load it
391
+ if obj == None :
392
+ obj , _ = self .load_obj (jref , parser = parser )
393
+ if obj :
394
+ obj = self .prepare_obj (obj , jref )
395
+ else :
396
+ # a local reference, 'jref' is just a json-pointer
397
+ if not jp .startswith ('#' ):
398
+ raise ValueError ('Invalid Path, root element should be \' #\' , but [{0}]' .format (jref ))
399
399
400
- obj = self .root .resolve (utils .jp_split (jp )[1 :]) # heading element is #, mapping to self.root
400
+ obj = self .root .resolve (utils .jp_split (jp )[1 :]) # heading element is #, mapping to self.root
401
401
402
402
if obj == None :
403
403
raise ValueError ('Unable to resolve path, [{0}]' .format (jref ))
404
404
405
- if isinstance (obj , (six .string_types , int , list , dict )):
405
+ if isinstance (obj , (six .string_types , six . integer_types , list , dict )):
406
406
return obj
407
407
return weakref .proxy (obj )
408
408
0 commit comments