Skip to content

Commit

Permalink
Merge branch 'release/0.8.37'
Browse files Browse the repository at this point in the history
  • Loading branch information
mission-liao committed Dec 23, 2017
2 parents b5feaff + 64fb487 commit ed0d508
Show file tree
Hide file tree
Showing 19 changed files with 244 additions and 85 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Changes

### 0.8.37

- Fix loading error on 'yaml' document
- Fix parameter renderer failed on int/number without 'format'
- Windows Support

### 0.8.33

- Support customized headers when making requests
Expand Down
41 changes: 15 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,25 @@ A python client for [Swagger](https://helloreverb.com/developers/swagger) enable
try Swagger REST API by [Swagger-UI](https://github.com/wordnik/swagger-ui). However, when it's time to **unittest**
your API, the first option you find would be [Swagger-codegen](https://github.com/wordnik/swagger-codegen), but the better option is us.

This project is developed after [swagger-py](https://github.com/digium/swagger-py), which is a nicely implemented one, and inspired many aspects of this project. Another project is [flex](https://github.com/pipermerriam/flex), which focuses on parameter validation, try it if you can handle other parts by yourselves.

For other projects related to Swagger tools in python, check [here](https://github.com/swagger-api/swagger-spec#python).
This project is developed after [swagger-py](https://github.com/digium/swagger-py), which is a nicely implemented one, and inspired many aspects of this project. Another project is [flex](https://github.com/pipermerriam/flex), which focuses on parameter validation, try it if you can handle other parts by yourselves. For other projects related to Swagger tools in python, check [here](https://github.com/swagger-api/swagger-spec#python).

**pyswagger** is much easier to use (compared to swagger-codegen, you don't need to prepare a scala environment) and tries hard to **fully supports** [Swagger Spec](https://helloreverb.com/developers/swagger) in all aspects.

Read the [Document](http://pyswagger.readthedocs.org/en/latest/), or just go through this README.

- [NEWs: upcoming support for OpenAPI 3.0](docs/md/news.md)
- [Features](README.md#features)
- [Tutorial](README.md#tutorial)
- [Quick Start](README.md#quick-start)
- [Installation](README.md#installation)
- [Reference](README.md#reference)
- [Contributors](README.md#contributors)
- [Contribution Guideline](README.md#contribution-guildeline)
- [FAQ](README.md#faq)
- [FAQ](docs/md/faq.md)
- [Changes](CHANGES.md)

---------

## Features
- **NEW** convert Swagger Document from older version to newer one. (ex. convert from 1.2 to 2.0)
- convert Swagger Document from older version to newer one. (ex. convert from 1.2 to 2.0)
- support Swagger **1.2**, **2.0** on python ~~2.6~~, **2.7**, **3.3**, **3.5**, **3.6**
- support YAML via [Pretty-YAML](https://github.com/mk-fg/pretty-yaml)
- support $ref to **External Document**, multiple swagger.json will be organized into a group of App. And external document with self-describing resource is also supported (refer to [issue](https://github.com/swagger-api/swagger-spec/issues/219)).
Expand Down Expand Up @@ -94,13 +91,18 @@ client.request(app.op['addPet'](body=pet_Tom))

# - access an Operation object via App.op when operationId is defined
# - a request to get the pet back
pet = client.request(app.op['getPetById'](petId=1)).data
req, resp = app.op['getPetById'](petId=1)
# prefer json as response
req.produce('application/json')
pet = client.request((req, resp)).data
assert pet.id == 1
assert pet.name == 'Tom'

# new ways to get Operation object corresponding to 'getPetById'.
# 'jp_compose' stands for JSON-Pointer composition
pet = client.request(app.resolve(jp_compose('/pet/{petId}', base='#/paths')).get(petId=1)).data
req, resp = app.resolve(jp_compose('/pet/{petId}', base='#/paths')).get(petId=1)
req.produce('application/json')
pet = client.request((req, resp)).data
assert pet.id == 1
```

Expand Down Expand Up @@ -148,7 +150,8 @@ All exported API are described in following sections. ![A diagram about relation
---------

## Contribution Guildeline
report an issue:

#### report an issue:
- issues can be reported [here](https://github.com/mission-liao/pyswagger/issues)
- include swagger.json if possible
- turn on logging and report with messages on console
Expand All @@ -171,9 +174,9 @@ logger.setLevel(logging.DEBUG)

- describe expected behavior, or more specific, the input/output

request a merge
- try not to decrease the coverage rate
#### submit a PR
- test included
- only PR to `develop` would be accepted

env preparation
```bash
Expand All @@ -185,17 +188,3 @@ unit testing
python -m pytest -s -v --cov=pyswagger --cov-config=.coveragerc pyswagger/tests
```

---------

## FAQ
- Format of byte?
- The way to encode/decode byte is [base64](https://github.com/wordnik/swagger-spec/issues/50).
- Format of datetime on the wire?
- should be an ISO8601 string, according to this [issue](https://github.com/wordnik/swagger-spec/issues/95).
- How **allowMultiple** is handled?
- Take type integer as example, you can pass ~~an integer or~~ an array/tuple of integer for this parameter. (a single value is no longer supported)
- What do we need to take care of when upgrading from Swagger 1.2 to 2.0?
- **allowMultiple** is no longer supported, always passing an array even with a single value.
- 'different host for different resource' is no longer supported in Swagger 2.0, only one host and one basePath is allowed in one swagger.json.
- refer to [Migration Guide](https://github.com/swagger-api/swagger-spec/wiki/Swagger-1.2-to-2.0-Migration-Guide) from Swagger team.
- The name of body parameters is no longer included in requests, refer to this [issue](https://github.com/mission-liao/pyswagger/issues/13) for details.
20 changes: 20 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
environment:
matrix:
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36"
- PYTHON: "C:\\Python36-x64"

install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
- "pip install --disable-pip-version-check --user --upgrade pip"
- "pip install -r requirements-dev.txt"

build: off

test_script:
- "python -m pytest -s -v pyswagger/tests"
21 changes: 21 additions & 0 deletions docs/md/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### FAQ

#### Format of byte?

The way to encode/decode byte is [base64](https://github.com/wordnik/swagger-spec/issues/50).

#### Format of datetime on the wire?

should be an ISO8601 string, according to this [issue](https://github.com/wordnik/swagger-spec/issues/95).


#### How **allowMultiple** is handled?

Take type integer as example, you can pass ~~an integer or~~ an array/tuple of integer for this parameter. (a single value is no longer supported)

#### What do we need to take care of when upgrading from Swagger 1.2 to 2.0?

- **allowMultiple** is no longer supported, always passing an array even with a single value.
- 'different host for different resource' is no longer supported in Swagger 2.0, only one host and one basePath is allowed in one swagger.json.
- refer to [Migration Guide](https://github.com/swagger-api/swagger-spec/wiki/Swagger-1.2-to-2.0-Migration-Guide) from Swagger team.
- The name of body parameters is no longer included in requests, refer to this [issue](https://github.com/mission-liao/pyswagger/issues/13) for details.
7 changes: 7 additions & 0 deletions docs/md/news.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### NEWs

Upcoming changes for OpenAPI 3.0 would be:

- version changes to: `1.0.0`, if you need a stabler version, please use `pyswagger<1.0.0` in pip's requirement file.
- most logic would be divided to this [repo](https://github.com/mission-liao/pyopenapi) and **pyswagger** would only contains code related to 'making reuqests' (just like what gophers did in [go-openapi](https://github.com/go-openapi))
- **$ref** would not be normalized anymore. Every field from API spec would be left unchanged and create another field for patched version.
2 changes: 1 addition & 1 deletion pyswagger/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.8.36'
__version__ = '0.8.37'

from .getter import Getter
from .core import App, Security
Expand Down
3 changes: 3 additions & 0 deletions pyswagger/consts/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@

FILE_EXT_JSON = 'json'
FILE_EXT_YAML = 'yaml'
FILE_EXT_YML = 'yml'

VALID_FILE_EXT = [
'.'+FILE_EXT_JSON,
'.'+FILE_EXT_YAML,
'.'+FILE_EXT_YML,
]
SWAGGER_FILE_NAMES = [
'resource_list' + '.' + FILE_EXT_JSON,
'swagger' + '.' + FILE_EXT_JSON,
'swagger' + '.' + FILE_EXT_YAML,
'swagger' + '.' + FILE_EXT_YML,
]

SCOPE_SEPARATOR = '!##!'
9 changes: 4 additions & 5 deletions pyswagger/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,11 @@ def prepare_obj(self, obj, jref):

s.scan(root=obj, route=[AssignParent()])

# fix for yaml that treat response code as number
s.scan(root=obj, route=[YamlFixer()], leaves=[Operation])
# normalize $ref
url, jp = utils.jr_split(jref)
s.scan(root=obj, route=[NormalizeRef(url)])
# fix for yaml that treat response code as number
s.scan(root=obj, route=[YamlFixer()], leaves=[Operation])

# cache this object
if url not in self.__objs:
if jp == '#':
Expand Down Expand Up @@ -287,7 +286,7 @@ def load(kls, url, getter=None, parser=None, url_load_hook=None, sep=consts.priv
if app.__version not in ['1.2', '2.0']:
raise NotImplementedError('Unsupported Version: {0}'.format(self.__version))

# update schem if any
# update scheme if any
p = six.moves.urllib.parse.urlparse(url)
if p.scheme:
app.schemes.append(p.scheme)
Expand Down Expand Up @@ -316,8 +315,8 @@ def prepare(self, strict=True):
:param bool strict: when in strict mode, exception would be raised if not valid.
"""

self.validate(strict=strict)
self.__root = self.prepare_obj(self.raw, self.__url)
self.validate(strict=strict)

if hasattr(self.__root, 'schemes') and self.__root.schemes:
if len(self.__root.schemes) > 0:
Expand Down
13 changes: 10 additions & 3 deletions pyswagger/getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import six
import os
import logging
import re


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -64,6 +65,12 @@ class LocalGetter(Getter):
def __init__(self, path):
super(LocalGetter, self).__init__(path)

if path.startswith('file://'):
parsed = six.moves.urllib.parse.urlparse(path)
path = parsed.path
if re.match('^/[A-Z]+:', path) is not None:
path = os.path.abspath(path[1:])

for n in private.SWAGGER_FILE_NAMES:
if self.base_path.endswith(n):
self.base_path = os.path.dirname(self.base_path)
Expand All @@ -82,13 +89,14 @@ def __init__(self, path):
# - when 'path' points to a specific file, and its
# extension is either 'json' or 'yaml'.
_, ext = os.path.splitext(path)
for e in [private.FILE_EXT_JSON, private.FILE_EXT_YAML]:
for e in [private.FILE_EXT_JSON, private.FILE_EXT_YAML, private.FILE_EXT_YML]:
if ext.endswith(e):
self.base_path = os.path.dirname(path)
self.urls = [path]
break
else:
for e in [private.FILE_EXT_JSON, private.FILE_EXT_YAML]:
for e in [private.FILE_EXT_JSON, private.FILE_EXT_YAML, private.FILE_EXT_YML]:
#print(path + '.' + e)
if os.path.isfile(path + '.' + e):
self.urls = [path + '.' + e]
break
Expand Down Expand Up @@ -163,4 +171,3 @@ def load(self, path):
logger.info('to load: [{0}]'.format(path))

return self._path2dict.get(path, {})

5 changes: 2 additions & 3 deletions pyswagger/primitives/_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def apply_with(self, obj, val, ctx):

not_found = set(obj.required) - set(six.iterkeys(self))
if len(not_found):
raise ValueError('requirement not meet: {0}'.format(not_found))
raise ValueError('Model missing required key(s): {0}'.format(', '.join(not_found)))

# remove assigned properties to avoid duplicated
# primitive creation
Expand Down Expand Up @@ -75,7 +75,7 @@ def cleanup(self, val, ctx):
return {}

def __eq__(self, other):
""" equality operater,
""" equality operater,
will skip checking when both value are None or no attribute.
:param other: another model
Expand All @@ -97,4 +97,3 @@ def __eq__(self, other):

def __ne__(self, other):
return not self.__eq__(other)

4 changes: 4 additions & 0 deletions pyswagger/primitives/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,14 @@ def __init__(self):
# init map of generators
self._map = {
'integer': {
'': _int_,
None: _int_,
'int32': _int_,
'int64': _int_,
},
'number': {
'': _float_,
None: _float_,
'float': _float_,
'double': _float_,
},
Expand Down
6 changes: 6 additions & 0 deletions pyswagger/tests/data/v2_0/render/other/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@
}
]
},
"float.2":{
"type":"number"
},
"integer.1":{
"type":"integer",
"format":"int32",
"maximum":50,
"minimum":10,
"multipleOf":5
},
"integer.2":{
"type":"integer"
},
"enum.string":{
"type":"string",
"enum":[
Expand Down
16 changes: 16 additions & 0 deletions pyswagger/tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ def test_integer(self):
self.assertTrue(i >= 10, 'should be greater than 10, not {0}'.format(i))
self.assertTrue((i % 5) == 0, 'should be moduleable by 5, not {0}'.format(i))

def test_integer_without_format(self):
for _ in six.moves.xrange(50):
i = self.rnd.render(
self.app.resolve('#/definitions/integer.2'),
opt=self.rnd.default()
)
self.assertTrue(isinstance(i, six.integer_types), 'should be integer, not {0}'.format(i))

def test_float(self):
for _ in six.moves.xrange(50):
f = self.rnd.render(
Expand All @@ -134,6 +142,14 @@ def test_float(self):
self.assertTrue(f >= 50, 'should be greater than 50, not {0}'.format(f))
self.assertTrue((f % 5) == 0, 'should be moduleable by 5, not {0}'.format(f))

def test_float_without_format(self):
for _ in six.moves.xrange(50):
f = self.rnd.render(
self.app.resolve('#/definitions/float.2'),
opt=self.rnd.default()
)
self.assertTrue(isinstance(f, float), 'should be float, not {0}'.format(f))

def test_bool(self):
b = self.rnd.render(
self.app.resolve('#/definitions/bool.1'),
Expand Down
Loading

0 comments on commit ed0d508

Please sign in to comment.