Skip to content

Commit

Permalink
Support further query parameter serialization (#52)
Browse files Browse the repository at this point in the history
* Add support for serializing a subset of OpenAPI Parameter query parameter styles in combination with explode boolean: form, spaceDelimited, pipeDelimited
* Fix flake8 command in tox.ini (test code in almdrlib folder)
* Fix flake8 warnings in config.py
* Add object query parameter to test OpenAPI spec
* gitignore updates: ignore almdrlib/version.py, ignore IntelliJ .idea folder, clarify *.iml are related to IntelliJ
  • Loading branch information
rmpalomino authored Jul 15, 2020
1 parent 3304a66 commit d58f219
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 12 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ share/python-wheels/
.installed.cfg
*.egg
MANIFEST
almdrlib/version.py

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down Expand Up @@ -128,4 +129,6 @@ dmypy.json
# Pyre type checker
.pyre/

# IntelliJ
*.iml
.idea
88 changes: 82 additions & 6 deletions almdrlib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ class OpenAPIKeyWord:
DEFAULT = "default"
ENCODING = "encoding"
EXPLODE = "explode"
STYLE = "style"
PARAMETER_STYLE_MATRIX = "matrix"
PARAMETER_STYLE_LABEL = "label"
PARAMETER_STYLE_FORM = "form"
PARAMETER_STYLE_SIMPLE = "simple"
PARAMETER_STYLE_SPACE_DELIMITED = "spaceDelimited"
PARAMETER_STYLE_PIPE_DELIMITED = "pipeDelimited"
PARAMETER_STYLE_DEEP_OBJECT = "deepObject"
DATA = "data"
CONTENT_TYPE_PARAM = "content-type"
CONTENT_TYPE_JSON = "application/json"
Expand Down Expand Up @@ -405,10 +413,14 @@ def __init__(self, spec={}, session=None):
self._init_name(spec[OpenAPIKeyWord.NAME])
self._required = spec.get(OpenAPIKeyWord.REQUIRED, False)
self._description = spec.get(OpenAPIKeyWord.DESCRIPTION, "")
self._dataype = get_dict_value(
self._datatype = get_dict_value(
spec,
[OpenAPIKeyWord.SCHEMA, OpenAPIKeyWord.TYPE],
OpenAPIKeyWord.STRING)
self._style = spec.get(OpenAPIKeyWord.STYLE,
self.default_style(self._in))
self._explode = spec.get(OpenAPIKeyWord.EXPLODE,
self.default_explode(self._style))
self._spec = spec
self._session = session
self._default = None
Expand All @@ -435,7 +447,7 @@ def description(self):

@property
def datatype(self):
return self._dataype
return self._datatype

@property
def default(self):
Expand All @@ -462,21 +474,85 @@ def serialize(self, path_params, query_params, headers, cookies, kwargs):
raise ValueError(f"'{self._name}' is required")
return

raw_value = kwargs.pop(self._name, self.default)

value = serialize_value(
self._dataype,
kwargs.pop(self._name, self.default))
self._datatype,
raw_value)

if self._in == OpenAPIKeyWord.PATH:
path_params[self.schema_name] = value
elif self._in == OpenAPIKeyWord.QUERY:
query_params[self.schema_name] = value
new_query_params = self.serialize_query_parameter(self._style,
self._explode,
self._name,
self._datatype,
raw_value)
query_params.update(new_query_params)
elif self._in == OpenAPIKeyWord.HEADER:
headers[self.schema_name] = value
elif self._in == OpenAPIKeyWord.COOKIE:
cookies[self.schema_name] = value

return True

@classmethod
def default_style(cls, parameter_in):
if parameter_in == OpenAPIKeyWord.QUERY:
return OpenAPIKeyWord.PARAMETER_STYLE_FORM
elif parameter_in == OpenAPIKeyWord.PATH:
return OpenAPIKeyWord.PARAMETER_STYLE_SIMPLE
elif parameter_in == OpenAPIKeyWord.HEADER:
return OpenAPIKeyWord.PARAMETER_STYLE_SIMPLE
elif parameter_in == OpenAPIKeyWord.COOKIE:
return OpenAPIKeyWord.PARAMETER_STYLE_FORM
else:
return OpenAPIKeyWord.PARAMETER_STYLE_SIMPLE

@classmethod
def default_explode(cls, style):
if style == OpenAPIKeyWord.PARAMETER_STYLE_FORM:
return True
else:
return False

@classmethod
def serialize_query_parameter(cls, style, explode, name, datatype, value):
# Implements partial query parameter serialization using rules from:
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#style-examples
# https://swagger.io/docs/specification/serialization/#query
# TODO: Serialize deepObject style
valid_styles = [OpenAPIKeyWord.PARAMETER_STYLE_FORM,
OpenAPIKeyWord.PARAMETER_STYLE_SPACE_DELIMITED,
OpenAPIKeyWord.PARAMETER_STYLE_PIPE_DELIMITED]
if style not in valid_styles:
raise ValueError(f"{name} query parameter has invalid style: "
f"{style}")
return

if (datatype == OpenAPIKeyWord.OBJECT and
style == OpenAPIKeyWord.PARAMETER_STYLE_FORM):
if explode:
return value
else:
serialized_pairs = [f'{a},{b}' for (a, b) in value.items()]
return {name: ",".join(serialized_pairs)}
elif datatype == OpenAPIKeyWord.ARRAY:
if explode:
return {name: value}
else:
if style == OpenAPIKeyWord.PARAMETER_STYLE_SPACE_DELIMITED:
delimiter = " "
elif style == OpenAPIKeyWord.PARAMETER_STYLE_PIPE_DELIMITED:
delimiter = "|"
else:
# Implicitly 'form' style
delimiter = ","
return {name: delimiter.join(value)}
else:
return {name: value}
return None


class Operation(object):
_internal_param_prefix = "_"
Expand Down Expand Up @@ -704,7 +780,7 @@ def load_spec(self, spec, variables):
]
]
):
raise ValueError("Invaliad openapi document")
raise ValueError("Invalid OpenAPI document")

self._spec = spec.copy()
_spec = spec.copy()
Expand Down
4 changes: 2 additions & 2 deletions almdrlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(self,
self._config_file = almdrlib.constants.DEFAULT_CONFIG_FILE

logger.debug(
f"Initializing configuration using " +
"Initializing configuration using " +
f"'{self._config_file}' configuration file")
if access_key_id or secret_key:
self._access_key_id = access_key_id
Expand Down Expand Up @@ -86,7 +86,7 @@ def __init__(self,
else:
self._initialize_defaults()

logger.debug(f"Finished configuraiton initialization. " +
logger.debug("Finished configuraiton initialization. " +
f"access_key_id={self._access_key_id}, " +
f"account_id={self._account_id}, " +
f"global_endpoint={self._global_endpoint}")
Expand Down
9 changes: 7 additions & 2 deletions tests/apis/testapi/testapi.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ paths:
- schema:
type: array
in: query
name: query_param1
description: 'query parameter 1 description: optional; array'
name: query_param2
description: 'query parameter 2 description: optional; array'
- schema:
type: object
in: query
name: query_param3
description: 'query parameter 3 description: optional; object; exploded'
responses:
'200':
description: OK
Expand Down
13 changes: 12 additions & 1 deletion tests/data/test_open_api_support.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@
"description": "header parameter 2 description - integer"
},
"query_param1": {
"type": "string",
"in": "query",
"description": "query parameter 1 description: required; string",
"required": true
},
"query_param2": {
"type": "array",
"in": "query",
"description": "query parameter 1 description: optional; array"
"description": "query parameter 2 description: optional; array"
},
"query_param3": {
"type": "object",
"in": "query",
"description": "query parameter 3 description: optional; object; exploded"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ python =
[testenv:flake8]
basepython = python
deps = flake8
commands = flake8 alertlogic-sdk-python
commands = flake8 almdrlib

[testenv]
setenv =
Expand Down

0 comments on commit d58f219

Please sign in to comment.