diff --git a/.travis.yml b/.travis.yml index 2ad132c..32556f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ branches: python: - "2.7" - - "3.3" + - "3.4" - "3.5" - "3.6" install: diff --git a/CHANGES.md b/CHANGES.md index a21423d..012ee11 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ ## Changes +### 0.8.38 + +- Allow to access headers with case insensitive keys +- Drop support to py3.3 because of Tornado + ### 0.8.37 - Fix loading error on 'yaml' document diff --git a/pyswagger/__init__.py b/pyswagger/__init__.py index 0b87652..616c029 100644 --- a/pyswagger/__init__.py +++ b/pyswagger/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.8.37' +__version__ = '0.8.38' from .getter import Getter from .core import App, Security diff --git a/pyswagger/io.py b/pyswagger/io.py index 9852ff5..6fdb78d 100644 --- a/pyswagger/io.py +++ b/pyswagger/io.py @@ -1,6 +1,6 @@ from __future__ import absolute_import from .primitives.comm import PrimJSONEncoder -from .utils import final, deref +from .utils import final, deref, CaseInsensitiveDict from pyswagger import errs from uuid import uuid4 import six @@ -33,7 +33,7 @@ def __init__(self, op, params): self.__p = params self.__url = self.__op.url self.__path = self.__op.path - self.__header = {} + self.__header = CaseInsensitiveDict() self.__consume = None self.__produce = None @@ -42,7 +42,7 @@ def __init__(self, op, params): def reset(self): self.__url = self.__op.url self.__path = self.__op.path - self.__header = {} + self.__header = CaseInsensitiveDict() self.__data = None def consume(self, consume): @@ -350,7 +350,7 @@ def __init__(self, op): # init properties self.__status = None - self.__header = {} + self.__header = CaseInsensitiveDict() # options self.__raw_body_only = False diff --git a/pyswagger/primitives/__init__.py b/pyswagger/primitives/__init__.py index 6e2aff1..edac8cd 100644 --- a/pyswagger/primitives/__init__.py +++ b/pyswagger/primitives/__init__.py @@ -90,7 +90,11 @@ def __init__(self): def get(self, _type, _format=None): r = self._map.get(_type, None) - return (None, None) if r == None else r.get(_format, (None, None)) + if r is None: + return (None, None) + if _format in r: + return r.get(_format) + return r.get(None) def register(self, _type, _format, creater, _2nd_pass=None): """ register a type/format handler when producing primitives diff --git a/pyswagger/primitives/_model.py b/pyswagger/primitives/_model.py index 32afd41..087df19 100644 --- a/pyswagger/primitives/_model.py +++ b/pyswagger/primitives/_model.py @@ -15,7 +15,7 @@ def __init__(self): super(Model, self).__init__() def apply_with(self, obj, val, ctx): - """ recursivly apply Schema object + """ recursively apply Schema object :param obj.Model obj: model object to instruct how to create this model :param dict val: things used to construct this model diff --git a/pyswagger/primitives/_str.py b/pyswagger/primitives/_str.py index de9fc22..a60b51b 100644 --- a/pyswagger/primitives/_str.py +++ b/pyswagger/primitives/_str.py @@ -10,7 +10,7 @@ def validate_str(obj, ret, val, ctx): if obj.maxLength and len(ret) > obj.maxLength: raise ValidationError('[{0}] is longer than {1} characters'.format(ret, str(obj.maxLength))) if obj.minLength and len(ret) < obj.minLength: - raise ValidationError('[{0}] is shoter than {1} characters'.format(ret, str(obj.minLength))) + raise ValidationError('[{0}] is shorter than {1} characters'.format(ret, str(obj.minLength))) # TODO: handle pattern return val diff --git a/pyswagger/tests/test_utils.py b/pyswagger/tests/test_utils.py index 945a8e5..164beea 100644 --- a/pyswagger/tests/test_utils.py +++ b/pyswagger/tests/test_utils.py @@ -429,3 +429,28 @@ def test_multiple_cycles_2(self): [2, 3, 5, 4, 2], [2, 3 ,4, 2] ])) + + def test_case_insensitive_dict(self): + """ test utils.CaseInsensitiveDict + """ + normal = utils.CaseInsensitiveDict() + normal['Content-Type'] = 'application/json' + self.assertTrue('Content-Type' in normal) + self.assertTrue('content-type' in normal) + self.assertEqual(normal['content-type'], 'application/json') + + # test iteration + for k, v in normal.iteritems(): + self.assertEqual(k, 'Content-Type') + self.assertEqual(v, 'application/json') + break + else: + # should not reach here + self.assertTrue(False) + + for v in normal.itervalues(): + self.assertEqual(v, 'application/json') + break + else: + # should not reach here + self.assertTrue(False) diff --git a/pyswagger/utils.py b/pyswagger/utils.py index 76ce070..485b245 100644 --- a/pyswagger/utils.py +++ b/pyswagger/utils.py @@ -9,6 +9,7 @@ import os import operator import functools +import collections #TODO: accept varg def scope_compose(scope, name, sep=private.SCOPE_SEPARATOR): @@ -593,3 +594,45 @@ def patch_path(base_path, path): path = path[1:] return path + + +class CaseInsensitiveDict(collections.MutableMapping): + """ a case insensitive dict: + - allow to query with case insensitive keys (get, in) + - iteration would return original key + + A reference implementation could be found in + + https://github.com/requests/ + """ + + def __init__(self): + self._store = dict() + + def __setitem__(self, key, value): + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (original_key for original_key, _ in six.itervalues(self._store)) + + def iteritems(self): + return six.itervalues(self._store) + + def itervalues(self): + return (value for _, value in six.itervalues(self._store)) + + def __in__(self, key): + return key.lower() in self._store + + def __len__(self): + return len(self._store) + + def __repr__(self): + return str(dict(self.items())) +