diff --git a/.gitignore b/.gitignore index fe24b660..62970b44 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ test/*.zip .p* *.pyc *.zip* +.settings/ *.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..eb5704f6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3-alpine + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ + +RUN pip3 install --no-cache-dir -r requirements.txt + +COPY . /usr/src/app + +EXPOSE 8080 + +ENTRYPOINT ["python3"] + +CMD ["-m", "swagger_server"] \ No newline at end of file diff --git a/README.md b/README.md index 444d0fa1..fca7a1e5 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ - Deploy the cluster! Example with Amazon EC2: ``` ./ec3 launch mycluster kubernetes_oscar ubuntu-ec2 -a auth.txt - ``` \ No newline at end of file + ``` diff --git a/examples/imagemagick/README.md b/examples/imagemagick/README.md new file mode 100644 index 00000000..e69de29b diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2264f67f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +connexion == 1.1.15 +python_dateutil == 2.6.0 +setuptools >= 21.0.0 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..81f8352e --- /dev/null +++ b/setup.py @@ -0,0 +1,35 @@ +# coding: utf-8 + +import sys +from setuptools import setup, find_packages + +NAME = "swagger_server" +VERSION = "1.0.0" + +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["connexion"] + +setup( + name=NAME, + version=VERSION, + description="On-premises Serverless Container-aware ARchitectures API Gateway", + author_email="", + url="", + keywords=["Swagger", "On-premises Serverless Container-aware ARchitectures API Gateway"], + install_requires=REQUIRES, + packages=find_packages(), + package_data={'': ['swagger/swagger.yaml']}, + include_package_data=True, + entry_points={ + 'console_scripts': ['swagger_server=__main__:main']}, + long_description="""\ + OSCAR API documentation + """ +) + diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..5e3dbbaa --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,18 @@ +# SCAR - Serverless Container-aware ARchitectures +# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +__all__ = ['cmdtemplate','request','utils'] \ No newline at end of file diff --git a/src/cmdtemplate.py b/src/cmdtemplate.py new file mode 100644 index 00000000..6e8c1dcd --- /dev/null +++ b/src/cmdtemplate.py @@ -0,0 +1,73 @@ +# SCAR - Serverless Container-aware ARchitectures +# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import abc +from enum import Enum + +class CallType(Enum): + INIT = "init" + INVOKE = "invoke" + RUN = "run" + UPDATE = "update" + LS = "ls" + RM = "rm" + LOG = "log" + PUT = "put" + GET = "get" + +class Commands(metaclass=abc.ABCMeta): + ''' All the different cloud provider controllers must inherit + from this class to ensure that the commands are defined consistently''' + + @abc.abstractmethod + def init(self): + pass + + @abc.abstractmethod + def invoke(self): + pass + + @abc.abstractmethod + def run(self): + pass + + @abc.abstractmethod + def update(self): + pass + + @abc.abstractmethod + def ls(self): + pass + + @abc.abstractmethod + def rm(self): + pass + + @abc.abstractmethod + def log(self): + pass + + @abc.abstractmethod + def put(self): + pass + + @abc.abstractmethod + def get(self): + pass + + @abc.abstractmethod + def parse_arguments(self, args): + pass diff --git a/src/providers/openfaas/__init__.py b/src/providers/openfaas/__init__.py new file mode 100644 index 00000000..aa10ae36 --- /dev/null +++ b/src/providers/openfaas/__init__.py @@ -0,0 +1,18 @@ +# SCAR - Serverless Container-aware ARchitectures +# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +__all__ = ['controller'] \ No newline at end of file diff --git a/src/providers/onpremises/openfaas/function/README.md b/src/providers/openfaas/cloud/openfaas/README.md similarity index 100% rename from src/providers/onpremises/openfaas/function/README.md rename to src/providers/openfaas/cloud/openfaas/README.md diff --git a/src/providers/onpremises/openfaas/function/supervisor.py b/src/providers/openfaas/cloud/openfaas/supervisor.py similarity index 99% rename from src/providers/onpremises/openfaas/function/supervisor.py rename to src/providers/openfaas/cloud/openfaas/supervisor.py index 023c58e4..ff62d135 100644 --- a/src/providers/onpremises/openfaas/function/supervisor.py +++ b/src/providers/openfaas/cloud/openfaas/supervisor.py @@ -93,5 +93,3 @@ def launch_user_script(): os.makedirs(output_folder, exist_ok=True) launch_user_script() upload_output() - - diff --git a/src/providers/openfaas/controller.py b/src/providers/openfaas/controller.py new file mode 100644 index 00000000..332d45a0 --- /dev/null +++ b/src/providers/openfaas/controller.py @@ -0,0 +1,86 @@ +# SCAR - Serverless Container-aware ARchitectures +# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from src.cmdtemplate import Commands +import src.utils as utils +import requests +from flask import Response + +def flask_response(func): + ''' + Decorator used to create a flask Response + ''' + def wrapper(*args, **kwargs): + r = func(*args, **kwargs) + kwargs = {'response' : r.content, 'status' : str(r.status_code), 'headers' : r.headers.items()} + return Response(**kwargs) + return wrapper + +class OpenFaas(Commands): + + functions_path = '/system/functions' + function_info = '/system/function/' + invoke_req_response_function = '/function/' + invoke_async_function = '/async-function/' + system_info = '/system/info' + + def __init__(self): + self.endpoint = utils.get_environment_variable("OPENFAAS_URL") + + @flask_response + def ls(self, function_name=None): + path = self.functions_path + if function_name: + path = self.function_info + function_name + return requests.get(self.endpoint + path) + + @flask_response + def init(self, **kwargs): + print(kwargs) + path = self.functions_path + r = requests.post(self.endpoint + path, json=kwargs) + return r + + @flask_response + def invoke(self, function_name, body, asynch=False): + path = self.invoke_req_response_function + if asynch: + path = self.invoke_async_function + return requests.post(self.endpoint + path + function_name, data=body) + + def run(self): + pass + + def update(self): + pass + + @flask_response + def rm(self, function_name): + payload = { 'functionName' : function_name } + return requests.delete(self.endpoint + self.functions_path, json=payload) + + def log(self): + pass + + def put(self): + pass + + def get(self): + pass + + def parse_arguments(self, args): + pass + diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 00000000..49a6e13b --- /dev/null +++ b/src/utils.py @@ -0,0 +1,165 @@ +# SCAR - Serverless Container-aware ARchitectures +# Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import base64 +import json +import os +import re +import subprocess +import tarfile +import tempfile +import uuid + +def join_paths(*paths): + return os.path.join(*paths) + +def get_temp_dir(): + return tempfile.gettempdir() + +def lazy_property(func): + ''' A decorator that makes a property lazy-evaluated.''' + attr_name = '_lazy_' + func.__name__ + + @property + def _lazy_property(self): + if not hasattr(self, attr_name): + setattr(self, attr_name, func(self)) + return getattr(self, attr_name) + return _lazy_property + +def find_expression(string_to_search, rgx_pattern): + '''Returns the first group that matches the rgx_pattern in the string_to_search''' + if string_to_search: + pattern = re.compile(rgx_pattern) + match = pattern.search(string_to_search) + if match : + return match.group() + +def base64_to_utf8_string(value): + return base64.b64decode(value).decode('utf-8') + +def utf8_to_base64_string(value): + return base64.b64encode(value).decode('utf-8') + +def dict_to_base64_string(value): + return base64.b64encode(json.dumps(value)).decode("utf-8") + +def divide_list_in_chunks(elements, chunk_size): + """Yield successive n-sized chunks from th elements list.""" + if len(elements) == 0: + yield [] + for i in range(0, len(elements), chunk_size): + yield elements[i:i + chunk_size] + +def get_random_uuid4_str(): + return str(uuid.uuid4()) + +def merge_dicts(d1, d2): + ''' + Merge 'd1' and 'd2' dicts into 'd1'. + 'd2' has precedence over 'd1' + ''' + for k,v in d2.items(): + if v: + if k not in d1: + d1[k] = v + elif type(v) is dict: + d1[k] = merge_dicts(d1[k], v) + elif type(v) is list: + d1[k] += v + return d1 + +def is_value_in_dict(dictionary, value): + return value in dictionary and dictionary[value] + +def get_tree_size(path): + """Return total size of files in given path and subdirs.""" + total = 0 + for entry in os.scandir(path): + if entry.is_dir(follow_symlinks=False): + total += get_tree_size(entry.path) + else: + total += entry.stat(follow_symlinks=False).st_size + return total + +def get_all_files_in_directory(dir_path): + files = [] + for dirname, _, filenames in os.walk(dir_path): + for filename in filenames: + files.append(os.path.join(dirname, filename)) + return files + +def get_file_size(file_path): + '''Return file size in bytes''' + return os.stat(file_path).st_size + +def create_folder(folder_name): + if not os.path.isdir(folder_name): + os.makedirs(folder_name, exist_ok=True) + +def create_file_with_content(path, content): + with open(path, "w") as f: + f.write(content) + +def read_file(file_path, mode="r"): + with open(file_path, mode) as content_file: + return content_file.read() + +def delete_file(path): + if os.path.isfile(path): + os.remove(path) + +def create_tar_gz(files_to_archive, destination_tar_path): + with tarfile.open(destination_tar_path, "w:gz") as tar: + for file_path in files_to_archive: + tar.add(file_path, arcname=os.path.basename(file_path)) + return destination_tar_path + +def extract_tar_gz(tar_path, destination_path): + with tarfile.open(tar_path, "r:gz") as tar: + tar.extractall(path=destination_path) + +def kill_process(self, process): + # Using SIGKILL instead of SIGTERM to ensure the process finalization + os.killpg(os.getpgid(process.pid), subprocess.signal.SIGKILL) + +def execute_command(command): + subprocess.call(command) + +def execute_command_and_return_output(command): + return subprocess.check_output(command).decode("utf-8") + +def is_variable_in_environment(variable): + return is_value_in_dict(os.environ, variable) + +def set_environment_variable(key, variable): + if key and variable: + os.environ[key] = variable + +def get_environment_variable(variable): + if is_variable_in_environment(variable): + return os.environ[variable] + +def parse_arg_list(arg_keys, cmd_args): + result = {} + for key in arg_keys: + if type(key) is tuple: + if key[0] in cmd_args and cmd_args[key[0]]: + result[key[1]] = cmd_args[key[0]] + else: + if key in cmd_args and cmd_args[key]: + result[key] = cmd_args[key] + return result diff --git a/swagger_server/__init__.py b/swagger_server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/swagger_server/__main__.py b/swagger_server/__main__.py new file mode 100644 index 00000000..b87cab87 --- /dev/null +++ b/swagger_server/__main__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import connexion + +from swagger_server import encoder + + +def main(): + app = connexion.App(__name__, specification_dir='./swagger/') + app.app.json_encoder = encoder.JSONEncoder + app.add_api('swagger.yaml', arguments={'title': 'On-premises Serverless Container-aware ARchitectures API Gateway'}) + app.run(port=8080) + + +if __name__ == '__main__': + main() diff --git a/swagger_server/controllers/__init__.py b/swagger_server/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/swagger_server/controllers/default_controller.py b/swagger_server/controllers/default_controller.py new file mode 100644 index 00000000..d929be8a --- /dev/null +++ b/swagger_server/controllers/default_controller.py @@ -0,0 +1,89 @@ +import connexion + +from swagger_server.models.delete_function_request import DeleteFunctionRequest +from swagger_server.models.function_definition import FunctionDefinition +from src.providers.openfaas.controller import OpenFaas + + +def function_async_function_name_post(functionName, input=None): + """Invoke a function asynchronously + + :param functionName: Function name + :type functionName: str + :param input: (Optional) data to pass to function + :type input: str + + :rtype: None + """ + return OpenFaas().invoke(functionName, input, asynch=True) + + +def function_function_name_get(functionName): + """Get a summary of an OpenFaaS function + + :param functionName: Function name + :type functionName: str + + :rtype: FunctionListEntry + """ + return OpenFaas().ls(functionName) + + +def function_function_name_post(functionName, input=None): + """Invoke a function defined in OpenFaaS + + :param functionName: Function name + :type functionName: str + :param body: (Optional) data to pass to function + :type body: str + + :rtype: None + """ + return OpenFaas().invoke(functionName, input) + + +def functions_delete(body): + """Remove a deployed function. + + :param body: Function to delete + :type body: dict | bytes + + :rtype: None + """ + if connexion.request.is_json: + body = DeleteFunctionRequest.from_dict(connexion.request.get_json()) + return OpenFaas().rm(body.function_name) + + +def functions_get(): + """Get a list of deployed functions with: stats and image digest + + :rtype: List[FunctionListEntry] + """ + return OpenFaas().ls() + + +def functions_post(body): + """Deploy a new function. + + :param body: Function to deploy + :type body: dict | bytes + + :rtype: None + """ + if connexion.request.is_json: + params = connexion.request.get_json() + return OpenFaas().init(**params) + + +def functions_put(body): + """Update a function. + + :param body: Function to update + :type body: dict | bytes + + :rtype: None + """ + if connexion.request.is_json: + body = FunctionDefinition.from_dict(connexion.request.get_json()) + return 'do some magic!' diff --git a/swagger_server/encoder.py b/swagger_server/encoder.py new file mode 100644 index 00000000..61ba4721 --- /dev/null +++ b/swagger_server/encoder.py @@ -0,0 +1,20 @@ +from connexion.apps.flask_app import FlaskJSONEncoder +import six + +from swagger_server.models.base_model_ import Model + + +class JSONEncoder(FlaskJSONEncoder): + include_nulls = False + + def default(self, o): + if isinstance(o, Model): + dikt = {} + for attr, _ in six.iteritems(o.swagger_types): + value = getattr(o, attr) + if value is None and not self.include_nulls: + continue + attr = o.attribute_map[attr] + dikt[attr] = value + return dikt + return FlaskJSONEncoder.default(self, o) diff --git a/swagger_server/models/__init__.py b/swagger_server/models/__init__.py new file mode 100644 index 00000000..55ca9d30 --- /dev/null +++ b/swagger_server/models/__init__.py @@ -0,0 +1,9 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +from swagger_server.models.delete_function_request import DeleteFunctionRequest +from swagger_server.models.function_definition import FunctionDefinition +from swagger_server.models.function_definition_limits import FunctionDefinitionLimits +from swagger_server.models.function_list_entry import FunctionListEntry diff --git a/swagger_server/models/base_model_.py b/swagger_server/models/base_model_.py new file mode 100644 index 00000000..97999c3d --- /dev/null +++ b/swagger_server/models/base_model_.py @@ -0,0 +1,69 @@ +import pprint + +import six +import typing + +from swagger_server import util + +T = typing.TypeVar('T') + + +class Model(object): + # swaggerTypes: The key is attribute name and the + # value is attribute type. + swagger_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls: typing.Type[T], dikt) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/swagger_server/models/delete_function_request.py b/swagger_server/models/delete_function_request.py new file mode 100644 index 00000000..e30fab51 --- /dev/null +++ b/swagger_server/models/delete_function_request.py @@ -0,0 +1,68 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class DeleteFunctionRequest(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self, function_name: str=None): # noqa: E501 + """DeleteFunctionRequest - a model defined in Swagger + + :param function_name: The function_name of this DeleteFunctionRequest. # noqa: E501 + :type function_name: str + """ + self.swagger_types = { + 'function_name': str + } + + self.attribute_map = { + 'function_name': 'functionName' + } + + self._function_name = function_name + + @classmethod + def from_dict(cls, dikt) -> 'DeleteFunctionRequest': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The DeleteFunctionRequest of this DeleteFunctionRequest. # noqa: E501 + :rtype: DeleteFunctionRequest + """ + return util.deserialize_model(dikt, cls) + + @property + def function_name(self) -> str: + """Gets the function_name of this DeleteFunctionRequest. + + Name of deployed function # noqa: E501 + + :return: The function_name of this DeleteFunctionRequest. + :rtype: str + """ + return self._function_name + + @function_name.setter + def function_name(self, function_name: str): + """Sets the function_name of this DeleteFunctionRequest. + + Name of deployed function # noqa: E501 + + :param function_name: The function_name of this DeleteFunctionRequest. + :type function_name: str + """ + if function_name is None: + raise ValueError("Invalid value for `function_name`, must not be `None`") # noqa: E501 + + self._function_name = function_name diff --git a/swagger_server/models/function_definition.py b/swagger_server/models/function_definition.py new file mode 100644 index 00000000..c1bb545c --- /dev/null +++ b/swagger_server/models/function_definition.py @@ -0,0 +1,373 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server.models.function_definition_limits import FunctionDefinitionLimits # noqa: F401,E501 +from swagger_server import util + + +class FunctionDefinition(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self, service: str=None, network: str=None, image: str=None, env_process: str=None, env_vars: Dict[str, str]=None, constraints: List[str]=None, labels: List[str]=None, annotations: List[str]=None, secrets: List[str]=None, registry_auth: str=None, limits: FunctionDefinitionLimits=None, requests: FunctionDefinitionLimits=None): # noqa: E501 + """FunctionDefinition - a model defined in Swagger + + :param service: The service of this FunctionDefinition. # noqa: E501 + :type service: str + :param network: The network of this FunctionDefinition. # noqa: E501 + :type network: str + :param image: The image of this FunctionDefinition. # noqa: E501 + :type image: str + :param env_process: The env_process of this FunctionDefinition. # noqa: E501 + :type env_process: str + :param env_vars: The env_vars of this FunctionDefinition. # noqa: E501 + :type env_vars: Dict[str, str] + :param constraints: The constraints of this FunctionDefinition. # noqa: E501 + :type constraints: List[str] + :param labels: The labels of this FunctionDefinition. # noqa: E501 + :type labels: List[str] + :param annotations: The annotations of this FunctionDefinition. # noqa: E501 + :type annotations: List[str] + :param secrets: The secrets of this FunctionDefinition. # noqa: E501 + :type secrets: List[str] + :param registry_auth: The registry_auth of this FunctionDefinition. # noqa: E501 + :type registry_auth: str + :param limits: The limits of this FunctionDefinition. # noqa: E501 + :type limits: FunctionDefinitionLimits + :param requests: The requests of this FunctionDefinition. # noqa: E501 + :type requests: FunctionDefinitionLimits + """ + self.swagger_types = { + 'service': str, + 'network': str, + 'image': str, + 'env_process': str, + 'env_vars': Dict[str, str], + 'constraints': List[str], + 'labels': List[str], + 'annotations': List[str], + 'secrets': List[str], + 'registry_auth': str, + 'limits': FunctionDefinitionLimits, + 'requests': FunctionDefinitionLimits + } + + self.attribute_map = { + 'service': 'service', + 'network': 'network', + 'image': 'image', + 'env_process': 'envProcess', + 'env_vars': 'envVars', + 'constraints': 'constraints', + 'labels': 'labels', + 'annotations': 'annotations', + 'secrets': 'secrets', + 'registry_auth': 'registryAuth', + 'limits': 'limits', + 'requests': 'requests' + } + + self._service = service + self._network = network + self._image = image + self._env_process = env_process + self._env_vars = env_vars + self._constraints = constraints + self._labels = labels + self._annotations = annotations + self._secrets = secrets + self._registry_auth = registry_auth + self._limits = limits + self._requests = requests + + @classmethod + def from_dict(cls, dikt) -> 'FunctionDefinition': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The FunctionDefinition of this FunctionDefinition. # noqa: E501 + :rtype: FunctionDefinition + """ + return util.deserialize_model(dikt, cls) + + @property + def service(self) -> str: + """Gets the service of this FunctionDefinition. + + Name of deployed function # noqa: E501 + + :return: The service of this FunctionDefinition. + :rtype: str + """ + return self._service + + @service.setter + def service(self, service: str): + """Sets the service of this FunctionDefinition. + + Name of deployed function # noqa: E501 + + :param service: The service of this FunctionDefinition. + :type service: str + """ + if service is None: + raise ValueError("Invalid value for `service`, must not be `None`") # noqa: E501 + + self._service = service + + @property + def network(self) -> str: + """Gets the network of this FunctionDefinition. + + Docker swarm network, usually func_functions # noqa: E501 + + :return: The network of this FunctionDefinition. + :rtype: str + """ + return self._network + + @network.setter + def network(self, network: str): + """Sets the network of this FunctionDefinition. + + Docker swarm network, usually func_functions # noqa: E501 + + :param network: The network of this FunctionDefinition. + :type network: str + """ + + self._network = network + + @property + def image(self) -> str: + """Gets the image of this FunctionDefinition. + + Docker image in accessible registry # noqa: E501 + + :return: The image of this FunctionDefinition. + :rtype: str + """ + return self._image + + @image.setter + def image(self, image: str): + """Sets the image of this FunctionDefinition. + + Docker image in accessible registry # noqa: E501 + + :param image: The image of this FunctionDefinition. + :type image: str + """ + if image is None: + raise ValueError("Invalid value for `image`, must not be `None`") # noqa: E501 + + self._image = image + + @property + def env_process(self) -> str: + """Gets the env_process of this FunctionDefinition. + + Process for watchdog to fork # noqa: E501 + + :return: The env_process of this FunctionDefinition. + :rtype: str + """ + return self._env_process + + @env_process.setter + def env_process(self, env_process: str): + """Sets the env_process of this FunctionDefinition. + + Process for watchdog to fork # noqa: E501 + + :param env_process: The env_process of this FunctionDefinition. + :type env_process: str + """ + if env_process is None: + raise ValueError("Invalid value for `env_process`, must not be `None`") # noqa: E501 + + self._env_process = env_process + + @property + def env_vars(self) -> Dict[str, str]: + """Gets the env_vars of this FunctionDefinition. + + Overrides to environmental variables # noqa: E501 + + :return: The env_vars of this FunctionDefinition. + :rtype: Dict[str, str] + """ + return self._env_vars + + @env_vars.setter + def env_vars(self, env_vars: Dict[str, str]): + """Sets the env_vars of this FunctionDefinition. + + Overrides to environmental variables # noqa: E501 + + :param env_vars: The env_vars of this FunctionDefinition. + :type env_vars: Dict[str, str] + """ + + self._env_vars = env_vars + + @property + def constraints(self) -> List[str]: + """Gets the constraints of this FunctionDefinition. + + + :return: The constraints of this FunctionDefinition. + :rtype: List[str] + """ + return self._constraints + + @constraints.setter + def constraints(self, constraints: List[str]): + """Sets the constraints of this FunctionDefinition. + + + :param constraints: The constraints of this FunctionDefinition. + :type constraints: List[str] + """ + + self._constraints = constraints + + @property + def labels(self) -> List[str]: + """Gets the labels of this FunctionDefinition. + + An array of labels used by the back-end for making scheduling or routing decisions # noqa: E501 + + :return: The labels of this FunctionDefinition. + :rtype: List[str] + """ + return self._labels + + @labels.setter + def labels(self, labels: List[str]): + """Sets the labels of this FunctionDefinition. + + An array of labels used by the back-end for making scheduling or routing decisions # noqa: E501 + + :param labels: The labels of this FunctionDefinition. + :type labels: List[str] + """ + + self._labels = labels + + @property + def annotations(self) -> List[str]: + """Gets the annotations of this FunctionDefinition. + + An array of annotations used by the back-end for management, orchestration, events and build tasks # noqa: E501 + + :return: The annotations of this FunctionDefinition. + :rtype: List[str] + """ + return self._annotations + + @annotations.setter + def annotations(self, annotations: List[str]): + """Sets the annotations of this FunctionDefinition. + + An array of annotations used by the back-end for management, orchestration, events and build tasks # noqa: E501 + + :param annotations: The annotations of this FunctionDefinition. + :type annotations: List[str] + """ + + self._annotations = annotations + + @property + def secrets(self) -> List[str]: + """Gets the secrets of this FunctionDefinition. + + + :return: The secrets of this FunctionDefinition. + :rtype: List[str] + """ + return self._secrets + + @secrets.setter + def secrets(self, secrets: List[str]): + """Sets the secrets of this FunctionDefinition. + + + :param secrets: The secrets of this FunctionDefinition. + :type secrets: List[str] + """ + + self._secrets = secrets + + @property + def registry_auth(self) -> str: + """Gets the registry_auth of this FunctionDefinition. + + Private registry base64-encoded basic auth (as present in ~/.docker/config.json) # noqa: E501 + + :return: The registry_auth of this FunctionDefinition. + :rtype: str + """ + return self._registry_auth + + @registry_auth.setter + def registry_auth(self, registry_auth: str): + """Sets the registry_auth of this FunctionDefinition. + + Private registry base64-encoded basic auth (as present in ~/.docker/config.json) # noqa: E501 + + :param registry_auth: The registry_auth of this FunctionDefinition. + :type registry_auth: str + """ + + self._registry_auth = registry_auth + + @property + def limits(self) -> FunctionDefinitionLimits: + """Gets the limits of this FunctionDefinition. + + + :return: The limits of this FunctionDefinition. + :rtype: FunctionDefinitionLimits + """ + return self._limits + + @limits.setter + def limits(self, limits: FunctionDefinitionLimits): + """Sets the limits of this FunctionDefinition. + + + :param limits: The limits of this FunctionDefinition. + :type limits: FunctionDefinitionLimits + """ + + self._limits = limits + + @property + def requests(self) -> FunctionDefinitionLimits: + """Gets the requests of this FunctionDefinition. + + + :return: The requests of this FunctionDefinition. + :rtype: FunctionDefinitionLimits + """ + return self._requests + + @requests.setter + def requests(self, requests: FunctionDefinitionLimits): + """Sets the requests of this FunctionDefinition. + + + :param requests: The requests of this FunctionDefinition. + :type requests: FunctionDefinitionLimits + """ + + self._requests = requests diff --git a/swagger_server/models/function_definition_limits.py b/swagger_server/models/function_definition_limits.py new file mode 100644 index 00000000..f60c5e25 --- /dev/null +++ b/swagger_server/models/function_definition_limits.py @@ -0,0 +1,90 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class FunctionDefinitionLimits(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self, memory: str=None, cpu: str=None): # noqa: E501 + """FunctionDefinitionLimits - a model defined in Swagger + + :param memory: The memory of this FunctionDefinitionLimits. # noqa: E501 + :type memory: str + :param cpu: The cpu of this FunctionDefinitionLimits. # noqa: E501 + :type cpu: str + """ + self.swagger_types = { + 'memory': str, + 'cpu': str + } + + self.attribute_map = { + 'memory': 'memory', + 'cpu': 'cpu' + } + + self._memory = memory + self._cpu = cpu + + @classmethod + def from_dict(cls, dikt) -> 'FunctionDefinitionLimits': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The FunctionDefinition_limits of this FunctionDefinitionLimits. # noqa: E501 + :rtype: FunctionDefinitionLimits + """ + return util.deserialize_model(dikt, cls) + + @property + def memory(self) -> str: + """Gets the memory of this FunctionDefinitionLimits. + + + :return: The memory of this FunctionDefinitionLimits. + :rtype: str + """ + return self._memory + + @memory.setter + def memory(self, memory: str): + """Sets the memory of this FunctionDefinitionLimits. + + + :param memory: The memory of this FunctionDefinitionLimits. + :type memory: str + """ + + self._memory = memory + + @property + def cpu(self) -> str: + """Gets the cpu of this FunctionDefinitionLimits. + + + :return: The cpu of this FunctionDefinitionLimits. + :rtype: str + """ + return self._cpu + + @cpu.setter + def cpu(self, cpu: str): + """Sets the cpu of this FunctionDefinitionLimits. + + + :param cpu: The cpu of this FunctionDefinitionLimits. + :type cpu: str + """ + + self._cpu = cpu diff --git a/swagger_server/models/function_list_entry.py b/swagger_server/models/function_list_entry.py new file mode 100644 index 00000000..75f5fb36 --- /dev/null +++ b/swagger_server/models/function_list_entry.py @@ -0,0 +1,272 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class FunctionListEntry(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + + def __init__(self, name: str=None, image: str=None, invocation_count: float=None, replicas: float=None, available_replicas: float=None, env_process: str=None, labels: Dict[str, str]=None, annotations: Dict[str, str]=None): # noqa: E501 + """FunctionListEntry - a model defined in Swagger + + :param name: The name of this FunctionListEntry. # noqa: E501 + :type name: str + :param image: The image of this FunctionListEntry. # noqa: E501 + :type image: str + :param invocation_count: The invocation_count of this FunctionListEntry. # noqa: E501 + :type invocation_count: float + :param replicas: The replicas of this FunctionListEntry. # noqa: E501 + :type replicas: float + :param available_replicas: The available_replicas of this FunctionListEntry. # noqa: E501 + :type available_replicas: float + :param env_process: The env_process of this FunctionListEntry. # noqa: E501 + :type env_process: str + :param labels: The labels of this FunctionListEntry. # noqa: E501 + :type labels: Dict[str, str] + :param annotations: The annotations of this FunctionListEntry. # noqa: E501 + :type annotations: Dict[str, str] + """ + self.swagger_types = { + 'name': str, + 'image': str, + 'invocation_count': float, + 'replicas': float, + 'available_replicas': float, + 'env_process': str, + 'labels': Dict[str, str], + 'annotations': Dict[str, str] + } + + self.attribute_map = { + 'name': 'name', + 'image': 'image', + 'invocation_count': 'invocationCount', + 'replicas': 'replicas', + 'available_replicas': 'availableReplicas', + 'env_process': 'envProcess', + 'labels': 'labels', + 'annotations': 'annotations' + } + + self._name = name + self._image = image + self._invocation_count = invocation_count + self._replicas = replicas + self._available_replicas = available_replicas + self._env_process = env_process + self._labels = labels + self._annotations = annotations + + @classmethod + def from_dict(cls, dikt) -> 'FunctionListEntry': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The FunctionListEntry of this FunctionListEntry. # noqa: E501 + :rtype: FunctionListEntry + """ + return util.deserialize_model(dikt, cls) + + @property + def name(self) -> str: + """Gets the name of this FunctionListEntry. + + The name of the function # noqa: E501 + + :return: The name of this FunctionListEntry. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this FunctionListEntry. + + The name of the function # noqa: E501 + + :param name: The name of this FunctionListEntry. + :type name: str + """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 + + self._name = name + + @property + def image(self) -> str: + """Gets the image of this FunctionListEntry. + + The fully qualified docker image name of the function # noqa: E501 + + :return: The image of this FunctionListEntry. + :rtype: str + """ + return self._image + + @image.setter + def image(self, image: str): + """Sets the image of this FunctionListEntry. + + The fully qualified docker image name of the function # noqa: E501 + + :param image: The image of this FunctionListEntry. + :type image: str + """ + if image is None: + raise ValueError("Invalid value for `image`, must not be `None`") # noqa: E501 + + self._image = image + + @property + def invocation_count(self) -> float: + """Gets the invocation_count of this FunctionListEntry. + + The amount of invocations for the specified function # noqa: E501 + + :return: The invocation_count of this FunctionListEntry. + :rtype: float + """ + return self._invocation_count + + @invocation_count.setter + def invocation_count(self, invocation_count: float): + """Sets the invocation_count of this FunctionListEntry. + + The amount of invocations for the specified function # noqa: E501 + + :param invocation_count: The invocation_count of this FunctionListEntry. + :type invocation_count: float + """ + if invocation_count is None: + raise ValueError("Invalid value for `invocation_count`, must not be `None`") # noqa: E501 + + self._invocation_count = invocation_count + + @property + def replicas(self) -> float: + """Gets the replicas of this FunctionListEntry. + + The current minimal ammount of replicas # noqa: E501 + + :return: The replicas of this FunctionListEntry. + :rtype: float + """ + return self._replicas + + @replicas.setter + def replicas(self, replicas: float): + """Sets the replicas of this FunctionListEntry. + + The current minimal ammount of replicas # noqa: E501 + + :param replicas: The replicas of this FunctionListEntry. + :type replicas: float + """ + if replicas is None: + raise ValueError("Invalid value for `replicas`, must not be `None`") # noqa: E501 + + self._replicas = replicas + + @property + def available_replicas(self) -> float: + """Gets the available_replicas of this FunctionListEntry. + + The current available amount of replicas # noqa: E501 + + :return: The available_replicas of this FunctionListEntry. + :rtype: float + """ + return self._available_replicas + + @available_replicas.setter + def available_replicas(self, available_replicas: float): + """Sets the available_replicas of this FunctionListEntry. + + The current available amount of replicas # noqa: E501 + + :param available_replicas: The available_replicas of this FunctionListEntry. + :type available_replicas: float + """ + if available_replicas is None: + raise ValueError("Invalid value for `available_replicas`, must not be `None`") # noqa: E501 + + self._available_replicas = available_replicas + + @property + def env_process(self) -> str: + """Gets the env_process of this FunctionListEntry. + + Process for watchdog to fork # noqa: E501 + + :return: The env_process of this FunctionListEntry. + :rtype: str + """ + return self._env_process + + @env_process.setter + def env_process(self, env_process: str): + """Sets the env_process of this FunctionListEntry. + + Process for watchdog to fork # noqa: E501 + + :param env_process: The env_process of this FunctionListEntry. + :type env_process: str + """ + if env_process is None: + raise ValueError("Invalid value for `env_process`, must not be `None`") # noqa: E501 + + self._env_process = env_process + + @property + def labels(self) -> Dict[str, str]: + """Gets the labels of this FunctionListEntry. + + + :return: The labels of this FunctionListEntry. + :rtype: Dict[str, str] + """ + return self._labels + + @labels.setter + def labels(self, labels: Dict[str, str]): + """Sets the labels of this FunctionListEntry. + + + :param labels: The labels of this FunctionListEntry. + :type labels: Dict[str, str] + """ + if labels is None: + raise ValueError("Invalid value for `labels`, must not be `None`") # noqa: E501 + + self._labels = labels + + @property + def annotations(self) -> Dict[str, str]: + """Gets the annotations of this FunctionListEntry. + + + :return: The annotations of this FunctionListEntry. + :rtype: Dict[str, str] + """ + return self._annotations + + @annotations.setter + def annotations(self, annotations: Dict[str, str]): + """Sets the annotations of this FunctionListEntry. + + + :param annotations: The annotations of this FunctionListEntry. + :type annotations: Dict[str, str] + """ + + self._annotations = annotations diff --git a/swagger_server/swagger/swagger.yaml b/swagger_server/swagger/swagger.yaml new file mode 100644 index 00000000..af0404c2 --- /dev/null +++ b/swagger_server/swagger/swagger.yaml @@ -0,0 +1,348 @@ +--- +swagger: "2.0" +info: + description: "OSCAR API documentation" + version: "0.0.1" + title: "On-premises Serverless Container-aware ARchitectures API Gateway" + license: + name: "Apache2" +basePath: "/" +schemes: +- "http" +paths: + /functions: + get: + summary: "Get a list of deployed functions with: stats and image digest" + operationId: "functions_get" + consumes: + - "application/json" + produces: + - "application/json" + parameters: [] + responses: + 200: + description: "List of deployed functions." + schema: + type: "array" + items: + $ref: "#/definitions/FunctionListEntry" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + post: + summary: "Deploy a new function." + description: "" + operationId: "functions_post" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "body" + description: "Function to deploy" + required: true + schema: + $ref: "#/definitions/FunctionDefinition" + responses: + 202: + description: "Accepted" + 400: + description: "Bad Request" + 500: + description: "Internal Server Error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + put: + summary: "Update a function." + description: "" + operationId: "functions_put" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "body" + description: "Function to update" + required: true + schema: + $ref: "#/definitions/FunctionDefinition" + responses: + 200: + description: "Accepted" + 400: + description: "Bad Request" + 404: + description: "Not Found" + 500: + description: "Internal Server Error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + delete: + summary: "Remove a deployed function." + description: "" + operationId: "functions_delete" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "body" + description: "Function to delete" + required: true + schema: + $ref: "#/definitions/DeleteFunctionRequest" + responses: + 200: + description: "OK" + 400: + description: "Bad Request" + 404: + description: "Not Found" + 500: + description: "Internal Server Error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + /function/async/{functionName}: + post: + summary: "Invoke a function asynchronously" + operationId: "function_async_function_name_post" + parameters: + - name: "functionName" + in: "path" + description: "Function name" + required: true + type: "string" + - in: "body" + name: "input" + description: "(Optional) data to pass to function" + required: false + schema: + type: "string" + format: "binary" + example: "{\"hello\": \"world\"}" + responses: + 202: + description: "Request accepted and queued" + 404: + description: "Not Found" + 500: + description: "Internal Server Error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + /function/{functionName}: + get: + summary: "Get a summary of an OpenFaaS function" + operationId: "function_function_name_get" + parameters: + - name: "functionName" + in: "path" + description: "Function name" + required: true + type: "string" + responses: + 200: + description: "Function Summary" + schema: + $ref: "#/definitions/FunctionListEntry" + 404: + description: "Not Found" + 500: + description: "Internal Server Error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" + post: + summary: "Invoke a defined function" + operationId: "function_function_name_post" + consumes: + - "application/json" + - "text/plain" + parameters: + - name: "functionName" + in: "path" + description: "Function name" + required: true + type: "string" + - in: "body" + name: "input" + description: "(Optional) data to pass to function" + required: false + schema: + type: "string" + format: "binary" + example: "{\"hello\": \"world\"}" + responses: + 200: + description: "Value returned from function" + 404: + description: "Not Found" + 500: + description: "Internal server error" + x-swagger-router-controller: "swagger_server.controllers.default_controller" +securityDefinitions: + basicAuth: + type: "basic" +definitions: + DeleteFunctionRequest: + type: "object" + required: + - "functionName" + properties: + functionName: + type: "string" + example: "nodeinfo" + description: "Name of deployed function" + example: + functionName: "nodeinfo" + FunctionDefinition: + type: "object" + required: + - "image" + - "service" + properties: + service: + type: "string" + example: "nodeinfo" + description: "Name of deployed function" + network: + type: "string" + example: "func_functions" + description: "Docker swarm network, usually func_functions" + image: + type: "string" + example: "functions/nodeinfo:latest" + description: "Docker image in accessible registry" + envProcess: + type: "string" + example: "node main.js" + description: "Process for watchdog to fork" + envVars: + type: "object" + description: "Overrides to environmental variables" + additionalProperties: + type: "string" + constraints: + type: "array" + items: + type: "string" + example: "node.platform.os == linux" + description: "Constraints are specific to OpenFaaS Provider" + labels: + type: "array" + description: "An array of labels used by the back-end for making scheduling\ + \ or routing decisions" + items: + type: "string" + annotations: + type: "array" + description: "An array of annotations used by the back-end for management,\ + \ orchestration, events and build tasks" + items: + type: "string" + secrets: + type: "array" + items: + type: "string" + example: "secret-name-1" + description: "An array of names of secrets that are required to be loaded\ + \ from the Docker Swarm." + registryAuth: + type: "string" + example: "dXNlcjpwYXNzd29yZA==" + description: "Private registry base64-encoded basic auth (as present in ~/.docker/config.json)" + limits: + $ref: "#/definitions/FunctionDefinition_limits" + requests: + $ref: "#/definitions/FunctionDefinition_limits" + example: + image: "functions/nodeinfo:latest" + envProcess: "node main.js" + service: "nodeinfo" + envVars: + key: "envVars" + annotations: + - "annotations" + - "annotations" + registryAuth: "dXNlcjpwYXNzd29yZA==" + requests: + memory: "128M" + cpu: "0.01" + constraints: + - "node.platform.os == linux" + - "node.platform.os == linux" + secrets: + - "secret-name-1" + - "secret-name-1" + limits: + memory: "128M" + cpu: "0.01" + network: "func_functions" + labels: + - "labels" + - "labels" + FunctionListEntry: + type: "object" + required: + - "availableReplicas" + - "envProcess" + - "image" + - "invocationCount" + - "labels" + - "name" + - "replicas" + properties: + name: + type: "string" + example: "nodeinfo" + description: "The name of the function" + image: + type: "string" + example: "functions/nodeinfo:latest" + description: "The fully qualified docker image name of the function" + invocationCount: + type: "number" + format: "integer" + example: 1337 + description: "The amount of invocations for the specified function" + replicas: + type: "number" + format: "integer" + example: 2 + description: "The current minimal ammount of replicas" + availableReplicas: + type: "number" + format: "integer" + example: 2 + description: "The current available amount of replicas" + envProcess: + type: "string" + example: "node main.js" + description: "Process for watchdog to fork" + labels: + type: "object" + additionalProperties: + type: "string" + annotations: + type: "object" + additionalProperties: + type: "string" + example: + image: "functions/nodeinfo:latest" + envProcess: "node main.js" + replicas: 2 + name: "nodeinfo" + invocationCount: 1337 + annotations: + key: "annotations" + availableReplicas: 2 + labels: + key: "labels" + FunctionDefinition_limits: + properties: + memory: + type: "string" + example: "128M" + cpu: + type: "string" + example: "0.01" + example: + memory: "128M" + cpu: "0.01" +externalDocs: + description: "More documentation available on Github" + url: "https://github.com/grycap/oscar" diff --git a/swagger_server/util.py b/swagger_server/util.py new file mode 100644 index 00000000..527d1424 --- /dev/null +++ b/swagger_server/util.py @@ -0,0 +1,141 @@ +import datetime + +import six +import typing + + +def _deserialize(data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in six.integer_types or klass in (float, str, bool): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif type(klass) == typing.GenericMeta: + if klass.__extra__ == list: + return _deserialize_list(data, klass.__args__[0]) + if klass.__extra__ == dict: + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return a original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.swagger_types: + return data + + for attr, attr_type in six.iteritems(instance.swagger_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)}