Skip to content

Commit

Permalink
Merge pull request #21 from Klebert-Engineering/feature/0-6-0-put-del…
Browse files Browse the repository at this point in the history
…ete-x-zserio-request-part

v0.6.0 / First batch of OpenApi alignment improvements.
  • Loading branch information
josephbirkner authored Jul 3, 2020
2 parents d511462 + 2bb1d84 commit b760521
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 52 deletions.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Alternatively, clone this repository, and run
pip3 install -e .
```

## Running the remote calculator test example

```bash
PYTHONPATH=$PWD/test
python3 -m calc server &
python3 -m calc client
```

## Creating a Swagger service from zserio

`ZserioSwaggerApp` gives you the power to marry a user-written app controller
Expand Down Expand Up @@ -102,27 +110,26 @@ OpenAPI YAML file:

#### Option: HTTP method

To change the **HTTP method**, simply place either `get` or `post`
To change the **HTTP method**, simply place the desired method
as the key under the method path, such as in the following example:
```yaml
paths:
/methodName:
{get|post}:
{get|post|put|patch|delete}:
...
```

#### Option: Base64 URL Parameter Format

To use the __Base64 URL parameter format__, use the snippet below in you method spec.
Zswag will use this format if a parameter named `requestData` exists.
```yaml
parameters:
- description: ''
in: query
name: requestData
required: true
x-zserio-request-part: "*" # The parameter represents the whole zserio request object
schema:
default: Base64-encoded bytes
format: byte
type: string
```
Expand All @@ -133,11 +140,10 @@ To use the Binary Body Parameter Format, use the snippet below in your method sp
```yaml
requestBody:
content:
application/x-binary:
application/x-zserio-object:
schema:
type: string
```
Note: Binary parameter passing is only allowed with `POST` http requests.

#### Option: Server URL Base Path

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

setuptools.setup(
name="zswag",
version="0.5.0",
version="0.6.0",
url="https://github.com/klebert-engineering/zswag",
author="Klebert Engineering",
author_email="[email protected]",
Expand Down
41 changes: 22 additions & 19 deletions src/zswag/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Type

from .doc import get_doc_str, IdentType, md_filter_definition
from zswag_client.spec import ZserioSwaggerSpec, ParamFormat
from zswag_client.spec import ZserioSwaggerSpec, ParamFormat, ParamLocation, ZSERIO_REQUEST_PART, ZSERIO_OBJECT_CONTENT_TYPE, ZSERIO_REQUEST_PART_WHOLE


# Name of variable that is added to controller
Expand Down Expand Up @@ -125,13 +125,17 @@ def myApi(request):
continue

method_spec = self.spec.method_spec(method_name)
if method_spec.param_format == ParamFormat.QUERY_PARAM_BASE64:
def wsgi_method(request_data, fun=zserio_modem_function):
request_data = base64.urlsafe_b64decode(request_data)
return bytes(fun(request_data, None))
else:
def wsgi_method(body, fun=zserio_modem_function):
return bytes(fun(body, None))
param_spec = method_spec.params[0]

def wsgi_method(fun=zserio_modem_function, param=param_spec, **kwargs):
param_name = param.name if param.location != ParamLocation.BODY else "body"
assert param_name in kwargs
param_value = kwargs[param_name]
if param.format == ParamFormat.BYTE:
param_value = base64.b64decode(param_value)
else:
assert param.format == ParamFormat.BINARY
return bytes(fun(param_value, None))
setattr(self.service_instance, method_name, wsgi_method)

def method_impl(request, ctx=None, fun=user_function):
Expand All @@ -147,7 +151,7 @@ def method_impl(request, ctx=None, fun=user_function):
self.add_api(
yaml_basename,
arguments={"title": f"REST API for {service_type.__name__}"},
pythonic_params=True)
pythonic_params=False)

def verify_openapi_schema(self):
for method_name in self.service_instance._methodMap:
Expand Down Expand Up @@ -177,21 +181,20 @@ def generate_openapi_schema(self):
"servers": [],
"paths": {
f"/{method_info.name}": {
"get": {
"post": {
"summary": method_info.docstring,
"description": method_info.docstring,
"operationId": method_info.name,
"parameters": [{
"name": "requestData",
"in": "query",
"requestBody": {
"description": method_info.argdoc,
"required": True,
"schema": {
"type": "string",
"default": "Base64-encoded bytes",
"format": "byte"
"content": {
ZSERIO_OBJECT_CONTENT_TYPE: {
"schema": {
"type": "string"
}
}
}
}],
},
"responses": {
"200": {
"description": method_info.returndoc,
Expand Down
21 changes: 15 additions & 6 deletions src/zswag_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import zserio
import base64

from .spec import ZserioSwaggerSpec, HttpMethod, ParamFormat
from .spec import ZserioSwaggerSpec, HttpMethod, ParamFormat, ParamLocation, ParamSpec
from urllib.parse import urlparse
import os

Expand Down Expand Up @@ -70,14 +70,23 @@ def callMethod(self, method_name, request_data, context=None):
try:
method_spec = self.spec.method_spec(method_name)
kwargs = {}
if method_spec.param_format == ParamFormat.QUERY_PARAM_BASE64:
kwargs["params"] = {"requestData": base64.urlsafe_b64encode(request_data)}
else:
kwargs["data"] = request_data
for param in method_spec.params:
if param.location == ParamLocation.QUERY:
kwargs["params"] = {"requestData": base64.b64encode(request_data)}
elif param.location == ParamLocation.BODY:
kwargs["data"] = request_data
if method_spec.http_method == HttpMethod.GET:
response = requests.get(self.path + method_name, **kwargs)
else:
elif method_spec.http_method == HttpMethod.POST:
response = requests.post(self.path + method_name, **kwargs)
elif method_spec.http_method == HttpMethod.DELETE:
response = requests.delete(self.path + method_name, **kwargs)
elif method_spec.http_method == HttpMethod.PUT:
response = requests.put(self.path + method_name, **kwargs)
elif method_spec.http_method == HttpMethod.PATCH:
response = requests.patch(self.path + method_name, **kwargs)
else:
raise zserio.ServiceException("Unsupported HTTP method!")
if response.status_code != requests.codes.ok:
raise zserio.ServiceException(str(response.status_code))
return response.content
Expand Down
79 changes: 59 additions & 20 deletions src/zswag_client/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,58 @@
from urllib.parse import urlparse


ZSERIO_OBJECT_CONTENT_TYPE = "application/x-zserio-object"
ZSERIO_REQUEST_PART = "x-zserio-request-part"
ZSERIO_REQUEST_PART_WHOLE = "*"


class HttpMethod(Enum):
GET = 0
POST = 1
PUT = 2
DELETE = 3
PATCH = 4


class ParamLocation(Enum):
QUERY = 0
BODY = 1
PATH = 2


class ParamFormat(Enum):
QUERY_PARAM_BASE64 = 0
BODY_BINARY = 1
STRING = 0
BYTE = 1
HEX = 2
BINARY = 3


class ParamSpec:

def __init__(self, *, name: str = "", format: ParamFormat, location: ParamLocation, zserio_request_part: str):
# https://github.com/Klebert-Engineering/zswag/issues/15
assert location in (ParamLocation.QUERY, ParamLocation.BODY)
# https://github.com/Klebert-Engineering/zswag/issues/19
assert zserio_request_part == ZSERIO_REQUEST_PART_WHOLE
# https://github.com/Klebert-Engineering/zswag/issues/20
assert \
(format == ParamFormat.BINARY and location == ParamLocation.BODY) or \
(format == ParamFormat.BYTE and location == ParamLocation.QUERY)
self.name = name
self.format = format
self.location = location
self.zserio_request_part = zserio_request_part


class MethodSpec:

def __init__(self, name: str, path: str, http_method: HttpMethod, param_format: ParamFormat, param_name: str):
def __init__(self, name: str, path: str, http_method: HttpMethod, params: List[ParamSpec]):
# https://github.com/Klebert-Engineering/zswag/issues/19
assert len(params) == 1
self.name = name
self.path = path
self.http_method = http_method
self.param_format = param_format
self.param_name = param_name
assert \
self.http_method == HttpMethod.GET and param_format == ParamFormat.QUERY_PARAM_BASE64 or \
self.http_method == HttpMethod.POST
assert \
self.param_format == ParamFormat.QUERY_PARAM_BASE64 and param_name or \
self.param_format == ParamFormat.BODY_BINARY
self.params = params


class ZserioSwaggerSpec:
Expand All @@ -49,19 +77,30 @@ def __init__(self, spec_url_or_path: str):
for method, method_spec in path_spec.items():
http_method = HttpMethod[method.upper()]
name = method_spec["operationId"]
param_format = ParamFormat.BODY_BINARY
expected_param_name = "requestData"
if "parameters" in method_spec and \
any(param for param in method_spec["parameters"] if param["name"] == expected_param_name):
param_format = ParamFormat.QUERY_PARAM_BASE64
else:
expected_param_name = ""
params: List[ParamSpec] = []
if "requestBody" in method_spec:
assert "content" in method_spec["requestBody"]
for content_type in method_spec["requestBody"]["content"]:
if content_type == ZSERIO_OBJECT_CONTENT_TYPE:
params.append(ParamSpec(
format=ParamFormat.BINARY,
location=ParamLocation.BODY,
zserio_request_part=ZSERIO_REQUEST_PART_WHOLE))
if "parameters" in method_spec:
for param in method_spec["parameters"]:
if ZSERIO_REQUEST_PART not in param:
continue
assert "schema" in param and "format" in param["schema"]
params.append(ParamSpec(
name=param["name"],
format=ParamFormat[param["schema"]["format"].upper()],
location=ParamLocation[param["in"].upper()],
zserio_request_part=param[ZSERIO_REQUEST_PART]))
method_spec_object = MethodSpec(
name=name,
path=path,
http_method=http_method,
param_format=param_format,
param_name=expected_param_name)
params=params)
assert name not in self.methods
self.methods[name] = method_spec_object

Expand Down

0 comments on commit b760521

Please sign in to comment.