Skip to content

Commit 8725c53

Browse files
committed
fix
- deprecate implicit dereference - support relative file reference - #53
1 parent 391b03e commit 8725c53

18 files changed

+197
-63
lines changed

CHANGES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
##Changes
2+
3+
###0.8.17
4+
5+
- (deprecated) implicit dereferencing
6+
```json
7+
"definitions":{
8+
"User":{
9+
},
10+
"AuthorizedUser":{
11+
"$ref": "User" --> deferenced to "#/definitions/User"
12+
}
13+
}
14+
```
15+
- __NEW__ relative file reference
16+
```
17+
"definitions":{
18+
"User": {
19+
"$ref": "other_folder/User.json"
20+
}
21+
}
22+
```

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Read the [Document](http://pyswagger.readthedocs.org/en/latest/), or just go thr
2424
- [Contributors](README.md#contributors)
2525
- [Contribution Guideline](README.md#contribution-guildeline)
2626
- [FAQ](README.md#faq)
27+
- [Changes](CHANGES.md)
2728

2829
---------
2930

pyswagger/scanner/cycle_detector.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
import functools
1111
import six
1212

13-
def _out(app, prefix, path):
14-
obj = app.resolve(normalize_jr(path, prefix, app.url))
13+
def _out(app, path):
14+
obj = app.resolve(normalize_jr(path, app.url))
1515
r = getattr(obj, 'norm_ref')
1616
return [r] if r else []
1717

@@ -37,7 +37,7 @@ def _schema_out_obj(obj, out=None):
3737
return out
3838

3939
def _schema_out(app, path):
40-
obj = app.resolve(normalize_jr(path, '#/definitions'))
40+
obj = app.resolve(normalize_jr(path, app.url))
4141
return [] if obj == None else _schema_out_obj(obj)
4242

4343

@@ -66,23 +66,23 @@ def _schema(self, path, _, app):
6666
def _parameter(self, path, _, app):
6767
self.cycles['parameter'] = walk(
6868
path,
69-
functools.partial(_out, app, '#/parameters'),
69+
functools.partial(_out, app),
7070
self.cycles['parameter']
7171
)
7272

7373
@Disp.register([Response])
7474
def _response(self, path, _, app):
7575
self.cycles['response'] = walk(
7676
path,
77-
functools.partial(_out, app, '#/responses'),
77+
functools.partial(_out, app),
7878
self.cycles['response']
7979
)
8080

8181
@Disp.register([PathItem])
8282
def _path_item(self, path, _, app):
8383
self.cycles['path_item'] = walk(
8484
path,
85-
functools.partial(_out, app, '#/paths'),
85+
functools.partial(_out, app),
8686
self.cycles['path_item']
8787
)
8888

pyswagger/scanner/v2_0/resolve.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,30 @@
1818
def is_resolved(obj):
1919
return getattr(obj, '$ref') == None or obj.ref_obj != None
2020

21-
def _resolve(obj, app, prefix, parser):
21+
def _resolve(obj, app, parser):
2222
if is_resolved(obj):
2323
return
2424

2525
r = getattr(obj, '$ref')
26-
ro = app.resolve(normalize_jr(r, prefix, app.url), parser)
26+
ro = app.resolve(normalize_jr(r, app.url), parser)
2727

2828
if not ro:
2929
raise ReferenceError('Unable to resolve: {0}'.format(r))
3030
if ro.__class__ != obj.__class__:
3131
raise TypeError('Referenced Type mismatch: {0}'.format(r))
3232

3333
obj.update_field('ref_obj', ro)
34-
obj.update_field('norm_ref', normalize_jr(r, prefix, app.url))
34+
obj.update_field('norm_ref', normalize_jr(r, app.url))
3535

36-
def _merge(obj, app, prefix, ctx):
36+
def _merge(obj, app, ctx):
3737
""" resolve $ref as ref_obj, and merge ref_obj to self.
3838
This operation should be carried in a cascade manner.
3939
"""
4040

4141
cur = obj
4242
to_resolve = []
4343
while not is_resolved(cur):
44-
_resolve(cur, app, prefix, ctx)
44+
_resolve(cur, app, ctx)
4545

4646
to_resolve.append(cur)
4747
cur = cur.ref_obj if cur.ref_obj else cur
@@ -59,21 +59,21 @@ class Disp(Dispatcher): pass
5959

6060
@Disp.register([Schema])
6161
def _schema(self, _, obj, app):
62-
_resolve(obj, app, '#/definitions', SchemaContext)
62+
_resolve(obj, app, SchemaContext)
6363

6464
@Disp.register([Parameter])
6565
def _parameter(self, _, obj, app):
66-
_resolve(obj, app, '#/parameters', ParameterContext)
66+
_resolve(obj, app, ParameterContext)
6767

6868
@Disp.register([Response])
6969
def _response(self, _, obj, app):
70-
_resolve(obj, app, '#/responses', ResponseContext)
70+
_resolve(obj, app, ResponseContext)
7171

7272
@Disp.register([PathItem])
7373
def _path_item(self, _, obj, app):
7474

7575
# $ref in PathItem is 'merge', not 'replace'
7676
# we need to merge properties of others if missing
7777
# in current object.
78-
_merge(obj, app, '#/paths', PathItemContext)
78+
_merge(obj, app, PathItemContext)
7979

pyswagger/spec/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,10 @@ def _produce_new_obj(x, ct, v):
304304
# since everything under SwaggerApp should be
305305
# readonly.
306306
if isinstance(v, list):
307-
self.update_field(name, list(set((getattr(self, name) or []) + v)))
307+
try:
308+
self.update_field(name, list(set((getattr(self, name) or []) + v)))
309+
except TypeError:
310+
self.update_field(name, list((getattr(self, name) or []) + v))
308311
elif isinstance(v, dict):
309312
d = copy.copy(v)
310313
d.update(getattr(self, name) or {})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: test
2+
type: object
3+
properties:
4+
id:
5+
type: integer
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: test
2+
type: object
3+
properties:
4+
id:
5+
type: integer
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: test
2+
type: object
3+
properties:
4+
id:
5+
type: integer
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: test
2+
type: object
3+
properties:
4+
id:
5+
type: integer
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
swagger: '2.0'
2+
info:
3+
title: Acme Internal API
4+
description: Docs for working with secret stuff
5+
version: 0.0.1
6+
host: api.acme.com
7+
basePath: /v1
8+
schemes:
9+
- https
10+
produces:
11+
- application/json
12+
paths:
13+
'/login':
14+
$ref: 'login.yaml'
15+
'/secret_stuff':
16+
$ref: 'secret_stuff.yaml'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
post:
2+
operationId: post_login
3+
summary: Log a user in
4+
description: Insert username and password, receive token
5+
parameters:
6+
- name: login_form
7+
in: body
8+
required: true
9+
schema:
10+
$ref: 'definitions/Login.yaml'
11+
responses:
12+
'200':
13+
description: Login result containing a session token
14+
schema:
15+
$ref: 'definitions/LoginResults.yaml'
16+
'403':
17+
description: Returned when credentials are incorrect
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
swagger: '2.0'
2+
info:
3+
title: Acme Public API
4+
description: Docs for working with normal stuff
5+
version: 0.0.1
6+
host: api.acme.com
7+
basePath: /v1
8+
schemes:
9+
- https
10+
produces:
11+
- application/json
12+
paths:
13+
'/login':
14+
$ref: 'login.yaml'
15+
'/stuff':
16+
$ref: 'stuff.yaml'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
post:
2+
operationId: post_secret_stuff
3+
security:
4+
- session_token: []
5+
description: does some secret stuff
6+
parameters:
7+
- name: secret_stuff_form
8+
in: body
9+
required: true
10+
schema:
11+
$ref: 'definitions/Stuff.yaml'
12+
responses:
13+
'200':
14+
description: Results of doing the stuff
15+
schema:
16+
$ref: 'definitions/StuffResults.yaml'
17+
'403':
18+
description: Returned when token is incorrect
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
post:
2+
operationId: post_stuff
3+
security:
4+
- session_token: []
5+
description: does some stuff
6+
parameters:
7+
- name: stuff_form
8+
in: body
9+
required: true
10+
schema:
11+
$ref: 'definitions/Stuff.yaml'
12+
responses:
13+
'200':
14+
description: Results of doing the stuff
15+
schema:
16+
$ref: 'definitions/StuffResults.yaml'
17+
'403':
18+
description: Returned when token is incorrect

pyswagger/tests/data/v2_0/schema/model/swagger.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
"xml":{
128128
"name":"category"
129129
},
130-
"$ref":"Category"
130+
"$ref":"#/definitions/Category"
131131
},
132132
"name":{
133133
"type":"string",
@@ -153,7 +153,7 @@
153153
"wrapped":true
154154
},
155155
"items":{
156-
"$ref":"Tag"
156+
"$ref":"#/definitions/Tag"
157157
}
158158
},
159159
"status":{
@@ -217,10 +217,10 @@
217217
"Employee":{
218218
"allOf":[
219219
{
220-
"$ref":"User"
220+
"$ref":"#/definitions/User"
221221
},
222222
{
223-
"$ref":"Skill"
223+
"$ref":"#/definitions/Skill"
224224
}
225225
]
226226
},
@@ -232,10 +232,10 @@
232232
},
233233
"allOf":[
234234
{
235-
"$ref":"User"
235+
"$ref":"#/definitions/User"
236236
},
237237
{
238-
"$ref":"Skill"
238+
"$ref":"#/definitions/Skill"
239239
}
240240
]
241241
},

pyswagger/tests/test_utils.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,12 @@ def test_normalize_url(self):
140140
self.assertEqual(utils.normalize_url('/tmp/local/test in space.txt'), 'file:///tmp/local/test%20in%20space.txt')
141141

142142
def test_normalize_jr(self):
143-
self.assertEqual(utils.normalize_jr(None, ''), None)
144-
self.assertEqual(utils.normalize_jr('User', '#/definitions'), '#/definitions/User')
145-
self.assertEqual(utils.normalize_jr('User', '#/definitions', 'http://test.com/api'), 'http://test.com/api#/definitions/User')
146-
self.assertEqual(utils.normalize_jr('#/definitions/User', '#/definitions', 'http://test.com/api'), 'http://test.com/api#/definitions/User')
147-
self.assertEqual(utils.normalize_jr(
148-
'http://test.com/api#/definitions/User', ''),
149-
'http://test.com/api#/definitions/User'
150-
)
143+
self.assertEqual(utils.normalize_jr(None), None)
144+
self.assertEqual(utils.normalize_jr(None, 'http://test.com/api/swagger.json'), None)
145+
self.assertEqual(utils.normalize_jr('User.json', 'http://test.com/api/swagger.json'), 'http://test.com/api/User.json')
146+
self.assertEqual(utils.normalize_jr('definitions/User.json', 'http://test.com/api/swagger.json'), 'http://test.com/api/definitions/User.json')
147+
self.assertEqual(utils.normalize_jr('#/definitions/User', 'http://test.com/api/swagger.json'), 'http://test.com/api/swagger.json#/definitions/User')
148+
self.assertEqual(utils.normalize_jr('#/definitions/User'), '#/definitions/User')
151149

152150
def test_get_swagger_version(self):
153151
self.assertEqual(utils.get_swagger_version({'swaggerVersion': '1.2'}), '1.2')

pyswagger/tests/v2_0/test_ex.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,26 @@
55
import six
66

77

8-
folder = get_test_data_folder(version='2.0', which='ex')
8+
def _gen_hook(folder):
9+
def _hook(url):
10+
p = six.moves.urllib.parse.urlparse(url)
11+
if p.scheme != 'file':
12+
return url
913

14+
path = os.path.join(folder, p.path if not p.path.startswith('/') else p.path[1:])
15+
return six.moves.urllib.parse.urlunparse(p[:2]+(path,)+p[3:])
1016

11-
def _hook(url):
12-
global folder
13-
14-
p = six.moves.urllib.parse.urlparse(url)
15-
if p.scheme != 'file':
16-
return url
17-
18-
path = os.path.join(folder, p.path if not p.path.startswith('/') else p.path[1:])
19-
return six.moves.urllib.parse.urlunparse(p[:2]+(path,)+p[3:])
17+
return _hook
2018

2119

2220
class ExternalDocumentTestCase(unittest.TestCase):
2321
""" test case for external document """
2422

2523
@classmethod
2624
def setUpClass(kls):
27-
global folder
28-
2925
kls.app = SwaggerApp.load(
3026
url='file:///root/swagger.json',
31-
url_load_hook=_hook
27+
url_load_hook=_gen_hook(get_test_data_folder(version='2.0', which='ex'))
3228
)
3329
kls.app.prepare()
3430

@@ -101,3 +97,13 @@ def chk(obj):
10197
chk(self.app.s('relative'))
10298
chk(self.app.resolve('file:///root/path_item.json'))
10399

100+
def test_relative_schema(self):
101+
""" test case for issue#53,
102+
relative file, which root is a Schema Object
103+
"""
104+
app = SwaggerApp.load(
105+
url='file:///relative/internal.yaml',
106+
url_load_hook=_gen_hook(get_test_data_folder(version='2.0', which='ex'))
107+
)
108+
app.prepare()
109+

0 commit comments

Comments
 (0)