diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..1b097e279931e088c564c80dd5de395d8411f9d2
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 6d7b9c17f7d3c0a133b2328a1c52005d3e39f75d..4a23ab3b2dfdfeee476d2a6c3d0f63bed253e8c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,5 @@ docs/_build/
# PyBuilder
target/
+/examples/industrial-ai-apiserver/test.json
+/examples/industrial-ai-apiserver/test.yaml
diff --git a/pecan_swagger/__init__.py b/.idea/.gitignore
similarity index 100%
rename from pecan_swagger/__init__.py
rename to .idea/.gitignore
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d85f807005f0c32ffe93c3c6b32844890f9498e
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c5b50f1e38dd7da465c1f810acaf0d148d078f1a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/pecan-swagger.iml b/.idea/pecan-swagger.iml
new file mode 100644
index 0000000000000000000000000000000000000000..c03885c89ad187df967cb71b3d05bdecb2e33b05
--- /dev/null
+++ b/.idea/pecan-swagger.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch
new file mode 100644
index 0000000000000000000000000000000000000000..fdc7d9e1f5a6536389d2995c74dae583c5a86b33
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24,_14_46_[Changes]/shelved.patch
@@ -0,0 +1,1136 @@
+Index: pecan_swagger/utils.py
+===================================================================
+diff --git a/pecan_swagger/utils.py b/pecan_swagger/utils.py
+deleted file mode 100644
+--- a/pecan_swagger/utils.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
+@@ -1,64 +0,0 @@
+-from pecan_swagger import g as g
+-
+-
+-"""
+-utility function module
+-
+-this module contains the utility functions to assemble the swagger
+-dictionary object. they can be consumed by end-user applications to
+-build up swagger objects for applications.
+-
+-functions:
+-swagger_build -- build a full swagger dictionary
+-"""
+-
+-
+-def swagger_build(title, version):
+- swag = dict()
+- swag['swagger'] = '2.0'
+- swag['info'] = dict(title=title, version=version)
+- swag['consumes'] = []
+- swag['produces'] = []
+- swag['paths'] = {}
+- for p in g.get_paths():
+- if p[0] not in swag['paths']:
+- swag['paths'][p[0]] = _tuple_to_dict(p[1])
+- elif len(p[1]) > 0:
+- swag['paths'][p[0]].update(_tuple_to_dict(p[1]))
+- swag['definitions'] = g._definitions
+- return swag
+-
+-
+-def _tuple_to_dict(tpl):
+- """Convert tuple to dictionary
+-
+- each tuple must have key and value.
+- This function arrows taple including lists.
+-
+- ex.) acceptable taple
+- OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name']))))
+- NG: ('/', ('get',('desc','res',('params',['id','name']))))
+- """
+- if isinstance(tpl, list):
+- d = []
+- for e in tpl:
+- d.append(_tuple_to_dict(e))
+- elif isinstance(tpl, tuple):
+- d = {}
+- if isinstance(tpl[0], tuple):
+- # tuple has some child tuple
+- for e in tpl:
+- d[e[0]] = _tuple_to_dict(e[1])
+- elif isinstance(tpl[0], list):
+- # list member should be processed recursively
+- d = _tuple_to_dict(tpl[0])
+- else:
+- if len(tpl) == 2:
+- # single tuple node
+- d[tpl[0]] = _tuple_to_dict(tpl[1])
+- else:
+- raise Exception(tpl)
+- else:
+- # value or dict
+- d = tpl
+- return d
+Index: pecan_swagger/decorators.py
+===================================================================
+diff --git a/pecan_swagger/decorators.py b/pecan_swagger/decorators.py
+deleted file mode 100644
+--- a/pecan_swagger/decorators.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
+@@ -1,38 +0,0 @@
+-import pecan_swagger.g as g
+-
+-"""
+-decorators module
+-
+-these decorators are meant to be used by developers who wish to markup
+-their pecan applications to produce swagger.
+-"""
+-
+-
+-def path(endpoint, name, parent=None):
+- """
+- path decorator
+-
+- this decorator should be used on pecan controllers to instruct how
+- they map to routes.
+-
+- :param endpoint: the root uri for this controller.
+- :param name: the name of this path.
+- :param parent: an optional path name to indicate a parent/child
+- relationship between this path and another.
+- """
+- def decorator(c):
+- if hasattr(c, '__swag'):
+- raise Exception('{} already has swag'.format(c.__name__))
+- c.__swag = dict(endpoint=endpoint, name=name, parent=parent)
+- g.add_path(c)
+- return c
+- return decorator
+-
+-
+-def method(method):
+- def decorator(m):
+- if hasattr(m, '__swag'):
+- raise Exception('{} already has swag'.format(m.__name__))
+- m.__swag = dict(method=method)
+- return m
+- return decorator
+Index: pecan_swagger/g.py
+===================================================================
+diff --git a/pecan_swagger/g.py b/pecan_swagger/g.py
+deleted file mode 100644
+--- a/pecan_swagger/g.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ /dev/null (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
+@@ -1,487 +0,0 @@
+-import copy
+-import inspect
+-
+-from pecan import util as p_u
+-import wsme.types as wtypes
+-import six
+-import weakref
+-
+-
+-"""
+-global hierarchy module
+-
+-this module is at the heart of the swagger conversion utility. it
+-contains a global "hierarchy" object which will provide a single point
+-to assemble an application's swagger output.
+-
+-there are also several helper functions to assist in building the
+-hierarchy of controllers, routes, and methods.
+-"""
+-
+-_hierarchy = {}
+-
+-_http_methods = {'get': '', 'post': '', 'put': '',
+- 'patch': '', 'delete': '',
+- 'head': '', 'trace': ''}
+-
+-_all_wsme_types = wtypes.native_types + (
+- wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType,
+- wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType,
+- wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType)
+-
+-_definitions = {}
+-
+-
+-def add_path(c):
+- """adds a named controller to the hierarchy."""
+- if _hierarchy.get(c.__swag['name']):
+- raise Exception(
+- 'name {} already exists in hierarchy'.format(c.__swag['name']))
+- _hierarchy[c.__swag['name']] = c
+-
+-
+-def build_path(swaginfo):
+- """return the route path for a swag metadata item."""
+- if swaginfo.get('parent') is not None:
+- return path_join(build_path(get_swag(swaginfo.get('parent'))),
+- swaginfo.get('endpoint'))
+- return swaginfo.get('endpoint')
+-
+-
+-def get_controller_paths(controllers, wsme_defs):
+- """
+- get a list of paths with methods
+-
+- returns a list of tuples (controller path, methods).
+- """
+- def get_methods_for_generic(name):
+- methods = []
+- generic_handlers = lc.get(name).get('generic_handlers', {})
+- for method, func in generic_handlers.items():
+- if method == 'DEFAULT':
+- methods.append('get')
+- else:
+- methods.append(method.lower())
+- # TODO drill down through decorators to get true function name
+- truename = getrealname(func)
+- del lc[truename]
+- return methods
+-
+- def append_methods_for_specific(name, tpl):
+- paths.append(tpl)
+- del lc[name]
+-
+- lc = copy.deepcopy(controllers)
+- paths = []
+- # TODO incorporate method decorator, removing functions marked
+- if lc.get('index'):
+- for method in get_methods_for_generic('index'):
+- paths.append(('', (method, {})))
+-
+- # for REST controller
+- for method, path in _http_methods.items():
+- if lc.get(method):
+- spec = wsme_defs.get(method, {}) # add wsme definitions
+- append_methods_for_specific(method, (path, (method, spec)))
+-
+- if lc.get('get_all'):
+- # add wsme definitions
+- spec = wsme_defs.get('get_all') # add wsme definitions
+- append_methods_for_specific('get_all', ('', ('get', spec)))
+- if lc.get('get_one'):
+- # add wsme definitions
+- spec = wsme_defs.get('get_one') # add wsme definitions
+- append_methods_for_specific('get_one', ('', ('get', spec)))
+-
+- if lc.get('_default'):
+- append_methods_for_specific('_default', ('', ('*', {})))
+- if lc.get('_route'):
+- append_methods_for_specific('_route', ('', ('*', {})))
+- if lc.get('_lookup'):
+- del lc['_lookup']
+-
+- if lc.get('_custom_actions'):
+- for ca, ca_method in lc['_custom_actions'].items():
+- spec = wsme_defs.get(ca) # add wsme definitions
+- for m in ca_method:
+- paths.append((ca, (m.lower(), spec)))
+- del lc['_custom_actions']
+-
+- generic_controllers = [c for c in lc if lc[c].get('generic')]
+- for controller in generic_controllers:
+- for method in get_methods_for_generic(controller):
+- paths.append((controller, (method, {})))
+- for controller in lc:
+- if controller not in [path[0] for path in paths]:
+- paths.append((controller, ('get', {})))
+- return paths
+-
+-
+-def get_wsme_defs(name):
+-
+- def datatype_to_type_and_format(datatype):
+- tf = {}
+- if datatype == 'uuid' or datatype == 'ipv4address' \
+- or datatype == 'ipv6address' or datatype == 'date' \
+- or datatype == 'time':
+- tf['type'] = 'string'
+- tf['format'] = datatype
+- elif datatype == 'datetime':
+- tf['type'] = 'string'
+- tf['format'] = 'date-time'
+- elif datatype == 'binary' or datatype == 'bytes':
+- # base64 encoded characters
+- tf['type'] = 'string'
+- tf['format'] = 'byte'
+- elif datatype == 'array' or datatype == 'boolean' \
+- or datatype == 'integer' or datatype == 'string':
+- # no format
+- tf['type'] = datatype
+- elif datatype == 'float':
+- # number
+- tf['type'] = 'number'
+- tf['format'] = datatype
+- elif datatype == 'unicode' or datatype == 'str' \
+- or datatype == 'text':
+- # primitive, no format
+- tf['type'] = 'string'
+- elif datatype == 'int' or datatype == 'decimal':
+- # primitive, no format
+- tf['type'] = 'integer'
+- elif datatype == 'bool':
+- # primitive, no format
+- tf['type'] = 'boolean'
+- elif datatype == 'enum':
+- tf['type'] = 'enum'
+- elif datatype == 'unset' or datatype == 'none':
+- tf['type'] = None
+- elif datatype == 'dict':
+- tf['type'] = 'object'
+- else:
+- tf['type'] = 'object'
+- return tf
+-
+- def class_to_name_str(c):
+- return (('%s' % c).replace('<', '').replace('>', '')
+- .replace('class ', '').replace('\'', '')
+- .split(' ', 1)[0].rsplit('.', 1)[-1])
+-
+- def conv_class_name_to_type_str(cn):
+- type_str = ''
+- if cn.endswith('Type'):
+- type_str = cn[:-4]
+- elif cn == 'str':
+- type_str = 'string'
+- else:
+- type_str = cn
+- type_str = type_str.lower()
+- return type_str
+-
+- def get_type_str(obj, src_dict=None):
+- type_str = ''
+- if hasattr(obj, '__name__'):
+- type_str = obj.__name__
+- else:
+- type_str = obj.__class__.__name__
+- type_str = conv_class_name_to_type_str(type_str)
+-
+- tf = datatype_to_type_and_format(type_str)
+-
+- if hasattr(obj, 'basetype') and \
+- (obj.__class__ not in _all_wsme_types or type_str == 'enum'):
+- # UserType or Enum
+- tf['type'] = get_type_str(obj.basetype)
+-
+- if isinstance(src_dict, dict):
+- # if dict is in args, set 'type' and 'format' into the dict and
+- # return
+- src_dict.update({'type': tf['type']})
+- if 'format' in tf:
+- src_dict.update({'format': tf['format']})
+-
+- # get datatype options. ex.) min_length, minimum, ..
+- for k, v in inspect.getmembers(obj):
+- if ((k == 'minimum' or k == 'maxmum'
+- or k == 'min_length' or k == 'max_length')
+- and v is not None):
+- src_dict[to_swag_key(k)] = v
+- elif k == 'pattern' and v is not None:
+- src_dict[to_swag_key(k)] = v.pattern
+- # TODO(shu-mutou): this should be removed for production.
+- # else:
+- # src_dict[to_swag_key(k)] = v
+-
+- if type_str == 'enum':
+- # EnumType use 'set' that doesn't have sequence.
+- # So use 'sorted' for keeping static output.
+- src_dict['enum'] = sorted([v for v in obj.values])
+-
+- # return 'type' only
+- return tf['type']
+-
+- def to_swag_key(key):
+- keys = {
+- 'doc': 'description',
+- 'arguments': 'parameters',
+- 'return_type': 'schema',
+- 'datatype': 'type',
+- 'mandatory': 'required',
+- 'sample': 'examples',
+- 'readonly': 'readOnly',
+- 'min_length': 'minLength',
+- 'max_length': 'maxLength',
+- }
+- if key in keys:
+- return keys[key]
+- else:
+- return key
+-
+- def get_wm_item_prop(item, isparams=False):
+- # Add prop into 'properties' and 'required' in 'Items Object'
+- # 'Items Object' can be part of 'Schema Object' or 'Items Object',
+- # and can use recursively
+- prop_dict = {}
+- # TODO(shu-mutou): this should be removed for production.
+- # prop_dict['obj'] = inspect.getmembers(item)
+-
+- _item = item
+- if wtypes.iscomplex(item):
+- _item = weakref.ref(item)
+-
+- for a, i in inspect.getmembers(_item):
+- if a == 'datatype':
+- datatype = get_type_str(i, prop_dict)
+- if datatype == 'array':
+- # if array, do recursively
+- prop_dict['items'] = inspect_wm_schema(i.item_type)
+- elif datatype == 'object':
+- # if obuject, do recursively
+- prop_dict['items'] = inspect_wm_schema(i)
+- elif a == 'default' and i:
+- prop_dict[to_swag_key(a)] = i
+- elif a == 'name' and isparams:
+- prop_dict[to_swag_key(a)] = i
+- elif a == 'mandatory' and i:
+- prop_dict[to_swag_key(a)] = i
+- elif a == 'readonly' and i:
+- prop_dict[to_swag_key(a)] = i
+- elif a == 'doc' and i is not None:
+- prop_dict[to_swag_key(a)] = i
+-
+- if isparams and prop_dict['type'] in ['object', 'array']:
+- prop_dict['schema'] = {'items': prop_dict['items'],
+- 'type': prop_dict['type']}
+- del prop_dict['type']
+- del prop_dict['items']
+- return prop_dict
+-
+- def get_wsattr_and_wsproperty(obj):
+- ws_dict = {}
+- for key, value in inspect.getmembers(obj):
+- if (key[0] != '_' and
+- (value.__class__.__name__ == 'wsattr'
+- or value.__class__.__name__ == 'wsproperty')):
+- ws_dict[key] = value
+- return ws_dict
+-
+- def inspect_wm_schema(schema_obj, isparams=False):
+- schema_dict = {}
+- # TODO(shu-mutou): this should be removed for production.
+- # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj)
+- ws_len = len(get_wsattr_and_wsproperty(schema_obj))
+- model_name = class_to_name_str(schema_obj)
+-
+- for key, obj in inspect.getmembers(schema_obj):
+- if (key[0] != '_' and
+- (obj.__class__.__name__ == 'wsattr'
+- or obj.__class__.__name__ == 'wsproperty')):
+- # TODO(shu-mutou): this should be removed for production.
+- # _definitions[model_name][to_swag_key(key)] = \
+- # {'obj': inspect.getmembers(obj)}
+-
+- if ws_len == 1:
+- # single schema
+- schema_dict = get_wm_item_prop(obj, isparams)
+- else:
+- # multi property schema
+- schema_dict.update({'type': 'object'})
+- # properties
+- if 'items' not in schema_dict:
+- schema_dict['items'] = {'properties': {}}
+- prop = {key: get_wm_item_prop(obj, isparams)}
+- # required as array of string
+- if 'required' in prop[key] and prop[key]['required'] \
+- and isinstance(prop[key]['required'], bool):
+- if 'required' not in schema_dict:
+- schema_dict['required'] = []
+- schema_dict['required'].append(key)
+- del prop[key]['required']
+- schema_dict['items']['properties'].update(prop)
+-
+- if schema_obj not in _all_wsme_types:
+- if model_name not in _definitions:
+- _definitions[model_name] = schema_dict
+- schema_dict = {'$ref': "#/definitions/%s" % model_name}
+-
+- return schema_dict
+-
+- def get_wm_def(o):
+- wsme = {'description': ''}
+- for w, d in inspect.getmembers(o):
+- if w == 'arguments':
+- wsme[to_swag_key(w)] = []
+- for arg in d:
+- # set each 'Parameter Object', it can include
+- # 'Items Object' recursively
+- item_dict = get_wm_item_prop(arg, True)
+- # TODO: MUST be set one of
+- # 'body|query|path|header|formData'
+- item_dict['in'] = 'query'
+- wsme[to_swag_key(w)].append(item_dict)
+- # 'body' should be set due to existing 'body' option
+- # in WSME expose
+- if o.body_type is not None:
+- # if body is set, last arg is it.
+- wsme[to_swag_key(w)][-1]['in'] = 'body'
+- elif w == 'doc' and d:
+- wsme[to_swag_key(w)] = d
+- elif w == 'return_type':
+- wsme['responses'] = {'status': {'description': ''}}
+- if d:
+- if d in _all_wsme_types:
+- # d is set single WSME type class or implicit type
+- wsme['responses']['status'][to_swag_key(w)] = (
+- datatype_to_type_and_format(
+- conv_class_name_to_type_str(
+- class_to_name_str(d))))
+- else:
+- # d is model class
+- wsme['responses']['status'][to_swag_key(w)] = (
+- inspect_wm_schema(d, False))
+- doc = inspect.getdoc(d)
+- if doc is not None:
+- wsme['responses']['status']['description'] = doc
+- elif w == 'status_code':
+- wsme['responses'][d] = wsme['responses']['status']
+- del wsme['responses']['status']
+- # TODO(shu-mutou): this should be removed for production.
+- # elif w == 'body_type':
+- # wsme[to_swag_key(w)] = get_type_str(d)
+- # elif w == 'extra_options' or w == 'ignore_extra_args' \
+- # or w == 'name' or w == 'pass_request':
+- # wsme[to_swag_key(w)] = d
+- # else:
+- # wsme[to_swag_key(w)] = d
+- return wsme
+-
+- c = _hierarchy[name]
+- wsme_defs = {}
+- for k, v in inspect.getmembers(c):
+- if p_u.iscontroller(v):
+- for m, o in inspect.getmembers(v):
+- if m == "_wsme_definition":
+- wsme_defs[k] = get_wm_def(o)
+-
+- # TODO(shu-mutou): this should be removed for production.
+- # output wsme info into files by each controller for dev
+- # import pprint
+- # with open(name + '.txt', 'w') as fout:
+- # pprint.pprint(wsme_defs, stream=fout, indent=2)
+-
+- return wsme_defs
+-
+-
+-def get_controllers(name):
+- """
+- get all the controllers associated with a path
+-
+- returns a dictionary of controllers indexed by their names.
+- """
+- c = _hierarchy[name]
+- controllers = {k: p_u._cfg(v)
+- for k, v in c.__dict__.items() if p_u.iscontroller(v)}
+- cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'}
+- if len(cacts):
+- controllers.update(cacts)
+- return controllers
+-
+-
+-def get_paths():
+- """
+- return all the registered paths
+-
+- loops through the hierarchy and retuns a list of tuples containing the
+- paths and their methods.
+-
+- :returns: [(path, methods), ...]
+- """
+- pathlist = []
+- for name in _hierarchy:
+- fullpath = build_path(get_swag(name))
+- controllers = get_controllers(name)
+- wsme_defs = get_wsme_defs(name)
+- paths = get_controller_paths(controllers, wsme_defs)
+- for path in paths:
+- ptuple = (path_join(fullpath, path[0]), path[1])
+- pathlist.append(ptuple)
+- return pathlist
+-
+-
+-def get_swag(name):
+- """return the swag metadata from an named controller."""
+- return _hierarchy.get(name).__swag
+-
+-
+-def getrealname(method):
+- """attempt to get a method's real name."""
+- argspec = inspect.getargspec(method)
+- args = argspec[0]
+- if args and args[0] == 'self':
+- return method.__name__
+- if hasattr(method, '__func__'):
+- method = method.__func__
+-
+- func_closure = six.get_function_closure(method)
+-
+- # NOTE(sileht): if the closure is None we cannot look deeper,
+- # so return actual argspec, this occurs when the method
+- # is static for example.
+- if func_closure is None:
+- return method.__name__
+-
+- closure = next(
+- (
+- c for c in func_closure if six.callable(c.cell_contents)
+- ),
+- None
+- )
+- method = closure.cell_contents
+- return getrealname(method)
+-
+-
+-def methods_get(name):
+- """get all the methods for a named controller."""
+- c = _hierarchy[name]
+- mlist = []
+- if hasattr(c, 'index') and p_u.iscontroller(c.index):
+- cfg = p_u._cfg(c.index)
+- if cfg.get('generic_handlers').get('DEFAULT'):
+- mlist.append('get')
+- mlist += cfg.get('allowed_methods')
+- for i in c.__dict__:
+- ii = getattr(c, i)
+- if hasattr(ii, '__swag'):
+- m = ii.__swag.get('method')
+- if m is not None:
+- mlist.append(m)
+- return mlist
+-
+-
+-def path_join(part1, part2):
+- """join two url paths."""
+- if len(part2) == 0:
+- return part1
+- sep = '/'
+- if part1[-1] == sep:
+- sep = ''
+- return part1 + sep + part2
+Index: examples/industrial-ai-apiserver/README.md
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+># industrial-ai-apiserver\n\n数据字典 & API:\n\n## 本地调试\n\n```bash\ncd industrial-ai-apiserver\n\n# 创建 python 虚拟环境\npython -m virtualenv .venv\n\n# 进入 python 虚拟环境\n.venv\\Scripts\\activate \n\n# 安装依赖\npip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt\n\n# 清理原数据库\nrm -rf /tmp/industrialai.db\n\n# 初始化数据库\npython datainit.py\n\n# 启动 web 服务\npython run.py\n# python run.py 0.0.0.0 8080\n\n# 尝试访问\ncurl http://localhost:8080/users/\n```\n\n## 测试\n\n```bash\npython setup.py test -q\n\nstestr run\n\ntox\n```\n\n## 容器镜像制作和部署\n\n```bash\ndocker build -t industrialai .\n\ndocker stop industrialai; docker rm industrialai\n\ndocker run -d --name industrialai -p 8888:8080 industrialai\n\ncurl http://localhost:8888/users/\n```
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/README.md b/examples/industrial-ai-apiserver/README.md
+--- a/examples/industrial-ai-apiserver/README.md (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/README.md (date 1651652456000)
+@@ -17,7 +17,7 @@
+ pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
+
+ # 清理原数据库
+-rm -rf /tmp/industrialai.db
++#rm -rf /tmp/industrialai.db
+
+ # 初始化数据库
+ python datainit.py
+@@ -27,7 +27,7 @@
+ # python run.py 0.0.0.0 8080
+
+ # 尝试访问
+-curl http://localhost:8080/users/
++curl http://localhost:8080/
+ ```
+
+ ## 测试
+@@ -40,14 +40,22 @@
+ tox
+ ```
+
+-## 容器镜像制作和部署
++## API-doc
++
++### 安装 api-doc
++1. 安装npm:
++ - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本
++ - 在终端输入 npm -v ,输出npm版本号
++2. npm install apidoc –g
++3. 参考:
++ - https://segmentfault.com/a/1190000011636199
++ - https://apidocjs.com/#getting-started
++ - https://github.com/apidoc/apidoc
++
++### 生成接口文档
+
+ ```bash
+-docker build -t industrialai .
+-
+-docker stop industrialai; docker rm industrialai
++apidoc -i industrialai/api/controllers -o api-doc
++```
+
+-docker run -d --name industrialai -p 8888:8080 industrialai
+-
+-curl http://localhost:8888/users/
+-```
+\ No newline at end of file
++点击 api-doc/index.html 访问
+\ No newline at end of file
+Index: examples/industrial-ai-apiserver/.gitignore
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+># Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# Build\nE:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\industrial-ai-apiserver\\\\industrialai.db\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\ncloudkitty.egg-info\n.idea/\n.python-version\n\n# Configuration file\netc/cloudkitty/cloudkitty.conf.sample\netc/cloudkitty/policy.yaml.sample\n\n# vscode\n.vscode\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.stestr/\n.coverage\n.cache\ncoverage.xml\ncover\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndoc/build/\n\n# Rope\n.ropeproject/\n\n# Others\n*.sqlite\n*.swp\n*~\n*.swn\n*.swo\n*.DS_Store\n\n# tox -e docs\nAUTHORS\nChangeLog\n\nreleasenotes/build\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/.gitignore b/examples/industrial-ai-apiserver/.gitignore
+--- a/examples/industrial-ai-apiserver/.gitignore (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/.gitignore (date 1651652456000)
+@@ -2,9 +2,6 @@
+ __pycache__/
+ *.py[cod]
+
+-# Build
+-E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db
+-
+ # C extensions
+ *.so
+
+Index: examples/industrial-ai-apiserver/datainit.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>from industrialai.db.model import Base, Role, User\nfrom sqlalchemy.orm import sessionmaker\nimport os\nfrom sqlalchemy import create_engine\n\ndef init_db():\n DB_URL = os.getenv('db_url') or \\\n \"sqlite:///E:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\industrial-ai-apiserver\\\\industrialai.db?check_same_thread=False\"\n engine = create_engine(DB_URL, echo=True)\n Base.metadata.drop_all(engine)\n Base.metadata.create_all(engine)\n Session = sessionmaker(bind=engine)\n session = Session()\n\n session.add_all([\n Role(title=\"expert\"),\n Role(title=\"government\"),\n Role(title=\"enterpriseAdmin\"),\n Role(title=\"member\"),\n Role(title=\"admin\"),\n User(account=\"admin\", pwd=\"123456\", roleId=3),\n User(account=\"member\", pwd=\"123456\", roleId=4),\n User(account=\"root\", pwd=\"123456\", roleId=5)\n ])\n session.commit()\n\n\ninit_db()\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/datainit.py b/examples/industrial-ai-apiserver/datainit.py
+--- a/examples/industrial-ai-apiserver/datainit.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/datainit.py (date 1651652456000)
+@@ -5,7 +5,7 @@
+
+ def init_db():
+ DB_URL = os.getenv('db_url') or \
+- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
++ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
+ engine = create_engine(DB_URL, echo=True)
+ Base.metadata.drop_all(engine)
+ Base.metadata.create_all(engine)
+Index: examples/industrial-ai-apiserver/industrialai/db/api.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>from industrialai.db import model as db_model\nimport logging\nimport math\nimport os\nfrom pecan import abort\nfrom sqlalchemy import create_engine, func\nfrom sqlalchemy.orm import exc\nfrom sqlalchemy.orm import sessionmaker\n\nlogger = logging.getLogger(__name__)\nDomain = \"sqlalchemy\"\n\n\n_ENGINE = None\n_SESSION_MAKER = None\n\n\ndef get_engine():\n global _ENGINE\n if _ENGINE is not None:\n return _ENGINE\n DB_URL = (\n os.getenv(\"db_url\") or\n \"sqlite:///E:\\\\dhuProject\\\\pecan-swagger\\\\examples\\\\\\\n industrial-ai-apiserver\\\\industrialai.db?check_same_thread=False\"\n )\n _ENGINE = create_engine(DB_URL, echo=True)\n return _ENGINE\n\n\ndef get_session_maker(engine):\n global _SESSION_MAKER\n if _SESSION_MAKER is not None:\n return _SESSION_MAKER\n _SESSION_MAKER = sessionmaker(bind=engine)\n return _SESSION_MAKER\n\n\ndef get_session():\n engine = get_engine()\n maker = get_session_maker(engine)\n session = maker()\n return session\n\n\nclass Connection(object):\n def __init__(self):\n pass\n\n def get_user(self, user_id):\n user = None\n query = get_session().query(db_model.User).filter_by(id=user_id)\n try:\n user = query.one()\n except exc.NoResultFound:\n logger.error(\"query by enterpriseId not found ...\")\n abort(404)\n return user\n\n def list_users(self, page_num, page_size, **kwargs):\n users = []\n # query = get_session().query(db_model.User.account,\n # db_model.User.name, db_model.User.roleId,\n # db_model.User.status, db_model.User.create_at, db_model.User.source)\n query = get_session().query(db_model.User)\n for k, v in kwargs.items():\n if v:\n if k == \"name\":\n query = query.filter(\n db_model.User.name.like(\"%{keyword}%\".\n format(keyword=v))\n )\n elif k == \"account\":\n query = query.filter(\n db_model.User.account.like(\"%{keyword}%\".\n format(keyword=v))\n )\n else:\n query = query.filter_by(**{k: v})\n try:\n users = query.slice((page_num-1)*page_size, page_size).all()\n num = query.count()\n pages = math.ceil(num/page_size)\n for item in users:\n item.pwd = None\n except exc.NoResultFound:\n logger.error(\"query all user occur error ...\")\n abort(404)\n return users, pages\n\n def update_user(self, user):\n id = user.get(\"id\")\n logger.info(\"user.id: %s\" % (id))\n try:\n session = get_session()\n session.query(db_model.User).filter_by(id=id).one()\n for k, v in user.items():\n if v is not None:\n session.query(db_model.User).filter_by(id=id). \\\n update({k: v})\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"update user occur error ...\")\n abort(404)\n\n return user\n\n def delete_user(self, user_id):\n logger.info(\"user.id: %s\" % (user_id))\n try:\n session = get_session()\n user = session.query(db_model.User).filter_by(id=user_id).first()\n session.delete(user)\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"delete user occur error ...\")\n abort(404)\n\n def add_user(self, user):\n db_user = db_model.User(\n name=user.get(\"name\"),\n account=user.get(\"account\"),\n pwd=user.get(\"pwd\"),\n phone=user.get(\"phone\"),\n roleId=user.get(\"roleId\"),\n enterpriseId=user.get(\"enterpriseId\"),\n status=user.get(\"status\"),\n )\n try:\n session = get_session()\n if not user.get(\"groupId\") is None:\n group = (\n session.query(db_model.Group)\n .filter_by(id=user.get(\"groupId\"))\n .one()\n )\n db_user.group = group\n session.add(db_user)\n session.flush()\n session.commit()\n except exc.NoResultFound:\n logger.error(\"add user occour error ...\")\n abort(404)\n # return session.query(db_model.Group).options(\n # joinedload(db_model.Group.users)).all()\n\n def list_roles(self, page_num, page_size):\n roles = []\n query = get_session().query(db_model.Role)\n try:\n roles = query.slice((page_num-1)*page_size, page_size).all()\n num = get_session().query(func.count(db_model.Role.id)).scalar()\n pages = math.ceil(num/page_size)\n except exc.NoResultFound:\n logger.error(\"query all roles occur error ...\")\n abort(404)\n return roles, pages\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py
+--- a/examples/industrial-ai-apiserver/industrialai/db/api.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/industrialai/db/api.py (date 1651652456000)
+@@ -21,8 +21,9 @@
+ return _ENGINE
+ DB_URL = (
+ os.getenv("db_url") or
+- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\\
+- industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
++ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\"
++ "examples\\industrial-ai-apiserver\\"
++ "industrialai.db?check_same_thread=False "
+ )
+ _ENGINE = create_engine(DB_URL, echo=True)
+ return _ENGINE
+Index: examples/industrial-ai-apiserver/industrialai/api/controllers/root.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>from industrialai.api.controllers.v1 import controller as v1_controller\nfrom industrialai.api.expose import expose as wsexpose\nfrom industrialai.db import api\nfrom industrialai.db.model import User\nfrom industrialai.pkg.authentication import JWT\nimport logging\nfrom pecan import abort\nfrom pecan import expose\nfrom pecan import request\nfrom wsme import types as wtypes\nlogger = logging.getLogger(__name__)\n\n\nclass logForm:\n account = wtypes.text\n pwd = wtypes.text\n\n\nclass checkForm:\n id = int\n pwd = wtypes.text\n\n\nclass registryForm:\n account = wtypes.text\n pwd = wtypes.text\n phone = wtypes.text\n mail = wtypes.text\n name = wtypes.text\n number = wtypes.text\n\n\nclass RootController(object):\n\n @expose('json')\n def index(self):\n return None\n\n @wsexpose(wtypes.text, body=logForm)\n def signin(self, logForm):\n account = logForm.account\n pwd = logForm.pwd\n user = api.get_session().query(User). \\\n filter_by(account=account, pwd=pwd).first()\n if user is None:\n abort(401)\n token = JWT.createToken(user.id, user.roleId)\n return {\n \"token\": token\n }\n\n @wsexpose(wtypes.text, body=checkForm)\n def pwdcheck(self, checkForm):\n try:\n api.get_session().query(User). \\\n filter_by(id=checkForm.id, pwd=checkForm.pwd).first()\n except Exception:\n return \"Failure\"\n return \"Success\"\n\n @expose('json')\n def myself(self):\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n uId = message.get(\"userId\")\n user = api.get_session().query(User). \\\n filter_by(id=uId).first()\n user.pwd = None\n return user\n\n v1 = v1_controller.v1Controller()\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py
+--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py (date 1652796268000)
+@@ -7,7 +7,9 @@
+ from pecan import abort
+ from pecan import expose
+ from pecan import request
++from pecan_swagger import decorators as swagger
+ from wsme import types as wtypes
++
+ logger = logging.getLogger(__name__)
+
+
+@@ -21,6 +23,7 @@
+ pwd = wtypes.text
+
+
++@swagger.definitions('object')
+ class registryForm:
+ account = wtypes.text
+ pwd = wtypes.text
+@@ -28,14 +31,60 @@
+ mail = wtypes.text
+ name = wtypes.text
+ number = wtypes.text
++ check = checkForm()
+
+
++@swagger.response('myself', '401', 'fail')
++@swagger.response('index', '401', 'fail')
++@swagger.response('test', '200', 'test description')
++@swagger.method('test', 'index', 'signin', 'pwdcheck', 'myself')
++@swagger.path("/", "Root")
+ class RootController(object):
++ _custom_actions = {
++ 'test': ['GET'],
++ 'signin': ['POST'],
++ 'pwdcheck': ['POST'],
++ 'myself': ['POST']
++ }
++
++ @expose('json')
++ def test(self):
++ print("123")
++ return "test"
+
+ @expose('json')
+ def index(self):
+ return None
+
++ """
++ @api {post} /signin signin
++ @apiName SignIn
++ @apiGroup User
++
++ @apiBody {String} account 账号
++ @apiBody {String} pwd 密码
++
++ @apiSuccess {String} token
++ @apiSuccessExample Success-Response:
++ {
++ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
++ eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ.
++ fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM"
++ }
++
++ @apiError Unauthorized
++ @apiErrorExample {json} Error-Response:
++ {
++ "faultcode": "Client",
++ "faultstring": "This server could not verify that you are
++ authorized to access the document you requested. Either
++ you supplied the wrong credentials (e.g., bad password),
++ or your browser does not understand how to supply the
++ credentials required.",
++ "debuginfo": null
++ }
++ """
++
+ @wsexpose(wtypes.text, body=logForm)
+ def signin(self, logForm):
+ account = logForm.account
+@@ -49,6 +98,22 @@
+ "token": token
+ }
+
++ """
++ @api {post} /pwdcheck check
++ @apiName PwdCheck
++ @apiGroup User
++
++ @apiBody {Integer} id 用户id
++ @apiBody {String} pwd 密码
++
++ @apiSuccess {Bool} result
++ @apiSuccessExample Success-Response:
++ HTTP/1.1 OK 200
++ {
++ "result": True True:校验通过/False:校验失败
++ }
++ """
++
+ @wsexpose(wtypes.text, body=checkForm)
+ def pwdcheck(self, checkForm):
+ try:
+@@ -58,6 +123,24 @@
+ return "Failure"
+ return "Success"
+
++ """@api {get} /myself get_myself
++ @apiName GetMyself
++ @apiGroup User
++
++ @apiSuccess (200) {Object} user
++ @apiSuccessExample Success-Response:
++ {
++ "id": 6, 用户id
++ "number": null, 身份证号
++ "roleId": 3, 角色
++ "status": 1, 状态:0/1
++ "create_at": "2022-03-09 11:24:18.856936", 创建时间
++ "name": "test", 名称
++ "account": "testsss1-put", 账号
++ "phone": "12345", 手机号
++ }
++ """
++
+ @expose('json')
+ def myself(self):
+ token = request.cookies.get("token")
+Index: examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>from industrialai.pkg.authentication import JWT\nfrom industrialai.pkg.page import get_page\nfrom pecan import abort\nfrom pecan import expose\nfrom pecan import request\nfrom pecan.rest import RestController\n\n\nclass UserController(RestController):\n\n @expose(\"json\")\n def get(self, **kwargs):\n page_num, page_size = get_page(request.body)\n validKeys = [\"enterpriseId\", \"name\", \"account\", \"roleId\", \"status\"]\n kwargs = {k: v for k, v in kwargs.items() if k in validKeys}\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n eId = message.get(\"enterpriseId\")\n rId = message.get(\"roleId\")\n if rId != 5:\n kwargs[\"enterpriseId\"] = eId\n db_conn = request.db_conn\n users, pages = db_conn.list_users(page_num, page_size, **kwargs)\n return {\n \"data\": users,\n \"pages\": pages\n }\n\n @expose(\"json\")\n def get_one(self, user_id):\n db_conn = request.db_conn\n user = db_conn.get_user(user_id)\n user.pwd = None\n return user\n\n @expose(\"json\")\n def post(self, user):\n db_conn = request.db_conn\n db_conn.add_user(user)\n return \"Success\"\n\n @expose(\"json\")\n def put(self, user):\n token = request.cookies.get(\"token\")\n message = JWT.deToken(token)\n if message is None:\n abort(401)\n else:\n uId = message.get(\"userId\")\n rId = message.get(\"roleId\")\n if uId != int(user.get(\"id\")) and rId != 5:\n abort(401)\n elif uId == int(user.get(\"id\")):\n user[\"roleId\"] = None\n user[\"phone\"] = None\n db_conn = request.db_conn\n db_conn.update_user(user)\n return \"Success\"\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py
+--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py (date 1652794836000)
+@@ -4,9 +4,64 @@
+ from pecan import expose
+ from pecan import request
+ from pecan.rest import RestController
++from pecan_swagger import decorators as swagger
+
+
++@swagger.response('put', '401', 'fail')
++@swagger.response('post', '401', 'fail')
++@swagger.response('get_one', '401', 'fail')
++@swagger.response('get', '401', 'fail')
++@swagger.response('get', '200', 'test description')
++@swagger.parameter('get', {'name': "status",
++ 'in': "query",
++ 'description': 'Status values that need to be considered for filter',
++ 'required': True,
++ 'type': 'array',
++ 'items': {'type': 'string',
++ 'enum': ["available", "pending", "sold"],
++ 'default': "available"},
++ 'collectionFormat': "multi"})
++@swagger.operationId('get', 'get users')
++@swagger.description('get', 'get users information')
++@swagger.summary('get', 'get users information')
++@swagger.tags('put', 'user')
++@swagger.tags('post', 'user')
++@swagger.tags('get', 'user')
++@swagger.tags('get_one', 'user')
++@swagger.method('get', 'get_one', 'post', 'put')
++@swagger.path("users", "User", "Root")
+ class UserController(RestController):
++ """@api {get} /v1/users/?name= get_all_users
++
++ @apiName GetAllUsers
++ @apiGroup User
++
++ @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配)
++ @apiParam {Integer} status status of user 根据用户状态过滤
++ @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配)
++ @apiParam {Integer} roleId role of user 根据角色进行过滤
++
++ @apiSuccess (200) {Object[]} users the list of user
++ @apiSuccessExample Success-Response:
++ {
++ "data": [
++ {
++ "id": 6, 用户id
++ "number": null, 身份证号
++ "roleId": 3, 角色
++ "status": 1, 状态:0/1
++ "create_at": "2022-03-09 11:24:18.856936", 创建时间
++ "name": "test", 名称
++ "account": "testsss1-put", 账号
++ "phone": "12345", 手机号
++ "email": null, 邮箱
++ },
++ ],
++ "pages": 10 总页数
++ “total": 10 总数
++ }
++ """
++
+
+ @expose("json")
+ def get(self, **kwargs):
+@@ -29,6 +84,27 @@
+ "pages": pages
+ }
+
++ """@api {get} /v1/users/ get_one_user
++ @apiName GetOneUser
++ @apiGroup User
++
++ @apiParam {Integer} userId id of user
++
++ @apiSuccess (200) {Object} user
++ @apiSuccessExample Success-Response:
++ {
++ "id": 6, 用户id
++ "number": null, 身份证号
++ "roleId": 3, 角色
++ "status": 1, 状态:0/1
++ "create_at": "2022-03-09 11:24:18.856936", 创建时间
++ "name": "test", 名称
++ "account": "testsss1-put", 账号
++ "phone": "12345", 手机号
++ "email": null, 邮箱
++ }
++ """
++
+ @expose("json")
+ def get_one(self, user_id):
+ db_conn = request.db_conn
+@@ -36,12 +112,78 @@
+ user.pwd = None
+ return user
+
++ """@api {post} /v1/users create_user
++ @apiName CreateUser
++ @apiGroup User
++
++ @apiBody {Object} user
++ @apiParamExample {json} Request-Example:
++ {
++ "user": {
++ "account": "testsss1-put",
++ "phone": "12345",
++ "email": xxx@xxxx.com,
++ "name": "test",
++ "pwd": "123456",
++ "roleId": 3
++ }
++ }
++
++ @apiSuccess {String} result
++ @apiSuccessExample Success-Response:
++ HTTP/1.1 200 OK
++
++ @apiError Unauthorized
++ @apiErrorExample {json} Error-Response:
++ {
++ "faultcode": "Client",
++ "faultstring": "This server could not verify that you are
++ authorized to access the document you requested. Either you
++ supplied the wrong credentials (e.g., bad password), or your
++ browser does not understand how to supply the credentials
++ required.",
++ "debuginfo": null
++ }
++ """
++
+ @expose("json")
+ def post(self, user):
+ db_conn = request.db_conn
+ db_conn.add_user(user)
+ return "Success"
+
++ """@api {put} /v1/users update_user
++ @apiName UpdateUser
++ @apiGroup User
++
++ @apiBody {Object} user
++ @apiParamExample {json} Request-Example:
++ {
++ "user": {
++ "id": 6,
++ "enterpriseId": 1,
++ "enterpriseGroupId": 1,
++ "name": "test"
++ }
++ }
++
++ @apiSuccess {String} result
++ @apiSuccessExample Success-Response:
++ "Success"
++
++ @apiError Unauthorized
++ @apiErrorExample {json} Error-Response:
++ {
++ "faultcode": "Client",
++ "faultstring": "This server could not verify that you are
++ authorized to access the document you requested. Either you
++ supplied the wrong credentials (e.g., bad password), or your
++ browser does not understand how to supply the credentials
++ required.",
++ "debuginfo": null
++ }
++ """
++
+ @expose("json")
+ def put(self, user):
+ token = request.cookies.get("token")
+Index: examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+>from industrialai.pkg.page import get_page\nfrom pecan import expose\nfrom pecan import request\nfrom pecan.rest import RestController\n\n\nclass RoleController(RestController):\n\n @expose('json')\n def get_all(self):\n page_num, page_size = get_page(request.body)\n db_conn = request.db_conn\n roles, num = db_conn.list_roles(page_num, page_size)\n return {\n \"data\": roles,\n \"pages\": num\n }\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py
+--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py (date 1652794554000)
+@@ -1,10 +1,44 @@
++from industrialai.api.expose import expose as wsexpose
+ from industrialai.pkg.page import get_page
+ from pecan import expose
+ from pecan import request
+ from pecan.rest import RestController
++from pecan_swagger import decorators as swagger
++from wsme import types as wstypes
++
+
++class Roletest(wstypes.Base):
++ message = wstypes.text
++ roleId = int
+
++
++@swagger.response('get_all', '401', 'fail')
++@swagger.method('test', 'get_all')
++@swagger.path("roles", "Role", "Root")
+ class RoleController(RestController):
++ _custom_actions = {
++ 'test': ['POST']
++ }
++
++ """
++ @api {get} /v1/roles get_all_roles
++ @apiName GetAllRoles
++ @apiGroup Role
++
++ @apiSuccess {Object[]} roles
++ @apiSuccessExample {type} Success-Response:
++ HTTP/1.1 200 OK
++ [
++ “data":[ 角色列表
++ {
++ "title": "expert", 角色名称
++ "id": 1 角色id
++ },
++ ]
++ "pages": 12 总页数
++ “total": 10 总数
++ ]
++ """
+
+ @expose('json')
+ def get_all(self):
+@@ -15,3 +49,11 @@
+ "data": roles,
+ "pages": num
+ }
++
++ @wsexpose(Roletest, body=Roletest)
++ def test(self, role):
++ print(role)
++ role.message = "out"
++ role.roleId = 1
++ print(role)
++ return role
+Index: .gitignore
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
+<+># Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# Build\nexample_wsme_app.json\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n# Usually these files are written by a python script from a template\n# before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+diff --git a/.gitignore b/.gitignore
+--- a/.gitignore (revision 2159b53baa4c86d9c1b739260824b7f89f935759)
++++ b/.gitignore (date 1653373170493)
+@@ -55,3 +55,5 @@
+
+ # PyBuilder
+ target/
++/examples/industrial-ai-apiserver/test.json
++/examples/industrial-ai-apiserver/test.yaml
+diff --git a/pecan_swagger/__init__.py b/pecan_swagger/__init__.py
+deleted file mode 100644
diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml
new file mode 100644
index 0000000000000000000000000000000000000000..436185fddf02c9f241ad8a447831b8144e4868ea
--- /dev/null
+++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_2022_5_24__14_46__Changes_.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..94a25f7f4cb416c083d265558da75d457237d671
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000000000000000000000000000000000000..11edc86381186b4238eab5594de549d6814db0a0
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1651814739960
+
+
+ 1651814739960
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/API Reference.md b/API Reference.md
new file mode 100644
index 0000000000000000000000000000000000000000..2169d98915a1ca931fdcde7489c908d50f23f030
--- /dev/null
+++ b/API Reference.md
@@ -0,0 +1,142 @@
+# Pecan Swagger API Reference
+
+```python
+pecan_swagger.utils.swagger_build(title, version)
+```
+Parameters:
+- **title** (String): The title of the application.
+- **version** (String): Provides the version of the application API.
+
+Returns:
+- **Swagger Dict**
+
+```python
+pecan_swagger.utils.add_info(s, description=None, terms_of_service=None, contact=None, license=None)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **description** (String): A short description of the application.
+- **terms_of_service** (String): The Terms of Service for the API.
+- **contact** (Dict): The contact information for the exposed API. Key should be in "name", "url", "email" or patterned string started with "x-". Values should be string for contact.
+- **license** (String): Provides the version of the application API. Key should be in "name", "url" or patterned string started with "x-". Values should be string for license.
+
+Returns:
+- **Swagger Dict**
+
+```python
+pecan_swagger.utils.add_external_docs(s, external_docs)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **external_docs** (Dict): Allows referencing an external resource for extended documentation. Key must include "url" and should be in "description", or patterned string started with "x-". Values should be string for license.
+
+Returns:
+- **Swagger Dict**
+
+```python
+pecan_swagger.utils.add_host(s, host)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **host** (String): The host (name or ip) serving the API.
+This MUST be the host only and does not include the scheme nor sub-paths.
+
+Returns:
+- **Swagger Dict**
+
+```python
+pecan_swagger.utils.add_base_path(s, base_path)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **base_path** (String): The base path on which the API is served, which is relative to the host. If it is not included, the API is served directly under the host.
+
+Returns:
+- **Swagger Dict**
+
+```python
+pecan_swagger.utils.print_json(s, output_path)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **base_path** (String): Path to output the json file.
+
+```python
+pecan_swagger.utils.print_yaml(s, output_path)
+```
+Parameters:
+- **s** (Swagger Dict): Swagger Dict create by swagger_build.
+- **base_path** (String): Path to output the json file.
+
+
+```python
+@pecan_swagger.decorators.path(endpoint, name, parent=None)
+```
+Parameters:
+- **endpoint** (String): The root uri for this controller.
+- **base_path** (String): The name of this path.
+- **parent** (String): An optional path name to indicate a parent/child relationship between this path and another.
+
+```python
+@pecan_swagger.decorators.response(method_name, res_code, des, schema=None)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- **res_code** (String): The HTTP response code of this response.
+- **des** (String): The description of this response.
+- **schema** (String): The schema of this response.
+
+```python
+@pecan_swagger.decorators.tags(method_name, *tags)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- ***tags** (String): The tags of this response.
+
+```python
+@pecan_swagger.decorators.summary(method_name, summary)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- **summary** (String): The summary of the method.
+
+```python
+@pecan_swagger.decorators.description(method_name, des)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- **des** (String): The description of the method.
+
+```python
+@pecan_swagger.decorators.operationId(method_name, oi)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- **oi** (String): The operationId of the method.
+
+```python
+@pecan_swagger.decorators.method(*args)
+```
+Parameters:
+- ***args** (String): The names of the method.
+
+```python
+@pecan_swagger.decorators.definitions(t, *args)
+```
+Parameters:
+- **t** (String): The type of the definition.
+- ***args** (String): The properties of the definition.
+
+```python
+@pecan_swagger.decorators.definitions(t, *args)
+```
+Parameters:
+- **t** (String): The type of the definition.
+- ***args** (String): The properties of the definition.
+
+```python
+@pecan_swagger.decorators.parameter(method_name, *args)
+```
+Parameters:
+- **method_name** (String): The name of the method.
+- ***args** (String): The parameters of the definition.
\ No newline at end of file
diff --git a/examples/.DS_Store b/examples/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..11b20ae6c07fee8a9188fce9c11763f834b55ea0
Binary files /dev/null and b/examples/.DS_Store differ
diff --git a/examples/industrial-ai-apiserver/README.md b/examples/industrial-ai-apiserver/README.md
index a9a80262b6f07825b6f7eef5d1233bff70c4f6d7..fd4789cbe0afb99fa6c88a047ea97a25b45909c9 100644
--- a/examples/industrial-ai-apiserver/README.md
+++ b/examples/industrial-ai-apiserver/README.md
@@ -17,7 +17,7 @@ python -m virtualenv .venv
pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
# 清理原数据库
-rm -rf /tmp/industrialai.db
+#rm -rf /tmp/industrialai.db
# 初始化数据库
python datainit.py
@@ -27,7 +27,7 @@ python run.py
# python run.py 0.0.0.0 8080
# 尝试访问
-curl http://localhost:8080/users/
+curl http://localhost:8080/
```
## 测试
@@ -40,14 +40,22 @@ stestr run
tox
```
-## 容器镜像制作和部署
+## API-doc
-```bash
-docker build -t industrialai .
+### 安装 api-doc
+1. 安装npm:
+ - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本
+ - 在终端输入 npm -v ,输出npm版本号
+2. npm install apidoc –g
+3. 参考:
+ - https://segmentfault.com/a/1190000011636199
+ - https://apidocjs.com/#getting-started
+ - https://github.com/apidoc/apidoc
-docker stop industrialai; docker rm industrialai
+### 生成接口文档
-docker run -d --name industrialai -p 8888:8080 industrialai
+```bash
+apidoc -i industrialai/api/controllers -o api-doc
+```
-curl http://localhost:8888/users/
-```
\ No newline at end of file
+点击 api-doc/index.html 访问
\ No newline at end of file
diff --git a/examples/industrial-ai-apiserver/datainit.py b/examples/industrial-ai-apiserver/datainit.py
index bf11856b32059fa23fbbdf232c12c1c219cbd3fd..64814c81c3aba30f492e88a71376c2f2120ab08a 100644
--- a/examples/industrial-ai-apiserver/datainit.py
+++ b/examples/industrial-ai-apiserver/datainit.py
@@ -5,7 +5,7 @@ from sqlalchemy import create_engine
def init_db():
DB_URL = os.getenv('db_url') or \
- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
+ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
engine = create_engine(DB_URL, echo=True)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py
index fd82b3a291eb8546c47fe2916e0dffedf284f9d2..2462069b60cc43040d64eb4e8196cf7728f7639a 100644
--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py
+++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py
@@ -7,7 +7,9 @@ import logging
from pecan import abort
from pecan import expose
from pecan import request
+from pecan_swagger import decorators as swagger
from wsme import types as wtypes
+
logger = logging.getLogger(__name__)
@@ -21,6 +23,7 @@ class checkForm:
pwd = wtypes.text
+@swagger.definitions('object')
class registryForm:
account = wtypes.text
pwd = wtypes.text
@@ -28,14 +31,60 @@ class registryForm:
mail = wtypes.text
name = wtypes.text
number = wtypes.text
+ check = checkForm()
+@swagger.response('myself', '401', 'fail')
+@swagger.response('index', '401', 'fail')
+@swagger.response('test', '200', 'test description')
+@swagger.method('test', 'index', 'signin', 'pwdcheck', 'myself')
+@swagger.path("/", "Root")
class RootController(object):
+ _custom_actions = {
+ 'test': ['GET'],
+ 'signin': ['POST'],
+ 'pwdcheck': ['POST'],
+ 'myself': ['POST']
+ }
+
+ @expose('json')
+ def test(self):
+ print("123")
+ return "test"
@expose('json')
def index(self):
return None
+ """
+ @api {post} /signin signin
+ @apiName SignIn
+ @apiGroup User
+
+ @apiBody {String} account 账号
+ @apiBody {String} pwd 密码
+
+ @apiSuccess {String} token
+ @apiSuccessExample Success-Response:
+ {
+ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
+ eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ.
+ fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM"
+ }
+
+ @apiError Unauthorized
+ @apiErrorExample {json} Error-Response:
+ {
+ "faultcode": "Client",
+ "faultstring": "This server could not verify that you are
+ authorized to access the document you requested. Either
+ you supplied the wrong credentials (e.g., bad password),
+ or your browser does not understand how to supply the
+ credentials required.",
+ "debuginfo": null
+ }
+ """
+
@wsexpose(wtypes.text, body=logForm)
def signin(self, logForm):
account = logForm.account
@@ -49,6 +98,22 @@ class RootController(object):
"token": token
}
+ """
+ @api {post} /pwdcheck check
+ @apiName PwdCheck
+ @apiGroup User
+
+ @apiBody {Integer} id 用户id
+ @apiBody {String} pwd 密码
+
+ @apiSuccess {Bool} result
+ @apiSuccessExample Success-Response:
+ HTTP/1.1 OK 200
+ {
+ "result": True True:校验通过/False:校验失败
+ }
+ """
+
@wsexpose(wtypes.text, body=checkForm)
def pwdcheck(self, checkForm):
try:
@@ -58,6 +123,24 @@ class RootController(object):
return "Failure"
return "Success"
+ """@api {get} /myself get_myself
+ @apiName GetMyself
+ @apiGroup User
+
+ @apiSuccess (200) {Object} user
+ @apiSuccessExample Success-Response:
+ {
+ "id": 6, 用户id
+ "number": null, 身份证号
+ "roleId": 3, 角色
+ "status": 1, 状态:0/1
+ "create_at": "2022-03-09 11:24:18.856936", 创建时间
+ "name": "test", 名称
+ "account": "testsss1-put", 账号
+ "phone": "12345", 手机号
+ }
+ """
+
@expose('json')
def myself(self):
token = request.cookies.get("token")
diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py
index 34bb33bba79ccdbf314933856fd96f5ee55afc03..9faace8868ab1539cc57678354c7536c35ac9068 100644
--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py
+++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py
@@ -1,10 +1,44 @@
+from industrialai.api.expose import expose as wsexpose
from industrialai.pkg.page import get_page
from pecan import expose
from pecan import request
from pecan.rest import RestController
+from pecan_swagger import decorators as swagger
+from wsme import types as wstypes
+class Roletest(wstypes.Base):
+ message = wstypes.text
+ roleId = int
+
+
+@swagger.response('get_all', '401', 'fail')
+@swagger.method('test', 'get_all')
+@swagger.path("roles", "Role", "Root")
class RoleController(RestController):
+ _custom_actions = {
+ 'test': ['POST']
+ }
+
+ """
+ @api {get} /v1/roles get_all_roles
+ @apiName GetAllRoles
+ @apiGroup Role
+
+ @apiSuccess {Object[]} roles
+ @apiSuccessExample {type} Success-Response:
+ HTTP/1.1 200 OK
+ [
+ “data":[ 角色列表
+ {
+ "title": "expert", 角色名称
+ "id": 1 角色id
+ },
+ ]
+ "pages": 12 总页数
+ “total": 10 总数
+ ]
+ """
@expose('json')
def get_all(self):
@@ -15,3 +49,11 @@ class RoleController(RestController):
"data": roles,
"pages": num
}
+
+ @wsexpose(Roletest, body=Roletest)
+ def test(self, role):
+ print(role)
+ role.message = "out"
+ role.roleId = 1
+ print(role)
+ return role
diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py
index 3c9132778429c786f2883c67ed67ebc7d8061812..5a837490f9c49900e0ed379ff7865f039dcf281c 100644
--- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py
+++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py
@@ -4,9 +4,64 @@ from pecan import abort
from pecan import expose
from pecan import request
from pecan.rest import RestController
+from pecan_swagger import decorators as swagger
+@swagger.response('put', '401', 'fail')
+@swagger.response('post', '401', 'fail')
+@swagger.response('get_one', '401', 'fail')
+@swagger.response('get', '401', 'fail')
+@swagger.response('get', '200', 'test description')
+@swagger.parameter('get', {'name': "status",
+ 'in': "query",
+ 'description': 'Status values that need to be considered for filter',
+ 'required': True,
+ 'type': 'array',
+ 'items': {'type': 'string',
+ 'enum': ["available", "pending", "sold"],
+ 'default': "available"},
+ 'collectionFormat': "multi"})
+@swagger.operationId('get', 'get users')
+@swagger.description('get', 'get users information')
+@swagger.summary('get', 'get users information')
+@swagger.tags('put', 'user')
+@swagger.tags('post', 'user')
+@swagger.tags('get', 'user')
+@swagger.tags('get_one', 'user')
+@swagger.method('get', 'get_one', 'post', 'put')
+@swagger.path("users", "User", "Root")
class UserController(RestController):
+ """@api {get} /v1/users/?name= get_all_users
+
+ @apiName GetAllUsers
+ @apiGroup User
+
+ @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配)
+ @apiParam {Integer} status status of user 根据用户状态过滤
+ @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配)
+ @apiParam {Integer} roleId role of user 根据角色进行过滤
+
+ @apiSuccess (200) {Object[]} users the list of user
+ @apiSuccessExample Success-Response:
+ {
+ "data": [
+ {
+ "id": 6, 用户id
+ "number": null, 身份证号
+ "roleId": 3, 角色
+ "status": 1, 状态:0/1
+ "create_at": "2022-03-09 11:24:18.856936", 创建时间
+ "name": "test", 名称
+ "account": "testsss1-put", 账号
+ "phone": "12345", 手机号
+ "email": null, 邮箱
+ },
+ ],
+ "pages": 10 总页数
+ “total": 10 总数
+ }
+ """
+
@expose("json")
def get(self, **kwargs):
@@ -29,6 +84,27 @@ class UserController(RestController):
"pages": pages
}
+ """@api {get} /v1/users/ get_one_user
+ @apiName GetOneUser
+ @apiGroup User
+
+ @apiParam {Integer} userId id of user
+
+ @apiSuccess (200) {Object} user
+ @apiSuccessExample Success-Response:
+ {
+ "id": 6, 用户id
+ "number": null, 身份证号
+ "roleId": 3, 角色
+ "status": 1, 状态:0/1
+ "create_at": "2022-03-09 11:24:18.856936", 创建时间
+ "name": "test", 名称
+ "account": "testsss1-put", 账号
+ "phone": "12345", 手机号
+ "email": null, 邮箱
+ }
+ """
+
@expose("json")
def get_one(self, user_id):
db_conn = request.db_conn
@@ -36,12 +112,78 @@ class UserController(RestController):
user.pwd = None
return user
+ """@api {post} /v1/users create_user
+ @apiName CreateUser
+ @apiGroup User
+
+ @apiBody {Object} user
+ @apiParamExample {json} Request-Example:
+ {
+ "user": {
+ "account": "testsss1-put",
+ "phone": "12345",
+ "email": xxx@xxxx.com,
+ "name": "test",
+ "pwd": "123456",
+ "roleId": 3
+ }
+ }
+
+ @apiSuccess {String} result
+ @apiSuccessExample Success-Response:
+ HTTP/1.1 200 OK
+
+ @apiError Unauthorized
+ @apiErrorExample {json} Error-Response:
+ {
+ "faultcode": "Client",
+ "faultstring": "This server could not verify that you are
+ authorized to access the document you requested. Either you
+ supplied the wrong credentials (e.g., bad password), or your
+ browser does not understand how to supply the credentials
+ required.",
+ "debuginfo": null
+ }
+ """
+
@expose("json")
def post(self, user):
db_conn = request.db_conn
db_conn.add_user(user)
return "Success"
+ """@api {put} /v1/users update_user
+ @apiName UpdateUser
+ @apiGroup User
+
+ @apiBody {Object} user
+ @apiParamExample {json} Request-Example:
+ {
+ "user": {
+ "id": 6,
+ "enterpriseId": 1,
+ "enterpriseGroupId": 1,
+ "name": "test"
+ }
+ }
+
+ @apiSuccess {String} result
+ @apiSuccessExample Success-Response:
+ "Success"
+
+ @apiError Unauthorized
+ @apiErrorExample {json} Error-Response:
+ {
+ "faultcode": "Client",
+ "faultstring": "This server could not verify that you are
+ authorized to access the document you requested. Either you
+ supplied the wrong credentials (e.g., bad password), or your
+ browser does not understand how to supply the credentials
+ required.",
+ "debuginfo": null
+ }
+ """
+
@expose("json")
def put(self, user):
token = request.cookies.get("token")
diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py
index 2d5825ddacc18587a2e3653730250c25d20b9457..3a6ecd3361894b539fc721fe5caadf31130a7fe8 100644
--- a/examples/industrial-ai-apiserver/industrialai/db/api.py
+++ b/examples/industrial-ai-apiserver/industrialai/db/api.py
@@ -5,7 +5,6 @@ import os
from pecan import abort
from sqlalchemy import create_engine, func
from sqlalchemy.orm import exc
-from sqlalchemy.orm import joinedload
from sqlalchemy.orm import sessionmaker
logger = logging.getLogger(__name__)
@@ -22,7 +21,9 @@ def get_engine():
return _ENGINE
DB_URL = (
os.getenv("db_url") or
- "sqlite:///E:\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False"
+ "sqlite:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\"
+ "examples\\industrial-ai-apiserver\\"
+ "industrialai.db?check_same_thread=False "
)
_ENGINE = create_engine(DB_URL, echo=True)
return _ENGINE
diff --git a/examples/industrial-ai-apiserver/industrialai/db/model.py b/examples/industrial-ai-apiserver/industrialai/db/model.py
index 6c5c9811e5b1ac4740f628da5251515df7dfd955..3c7492cbc76778259de808a7fc694bb9d20e24a4 100644
--- a/examples/industrial-ai-apiserver/industrialai/db/model.py
+++ b/examples/industrial-ai-apiserver/industrialai/db/model.py
@@ -4,9 +4,7 @@ from sqlalchemy import DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
-from sqlalchemy.orm import relationship
from sqlalchemy import String
-from sqlalchemy import Table
Base = declarative_base()
@@ -14,6 +12,7 @@ Base = declarative_base()
def init_db(_ENGINE):
Base.metadata.create_all(_ENGINE)
+
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
@@ -32,4 +31,3 @@ class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
title = Column(String(32))
-
diff --git a/examples/industrial-ai-apiserver/myapp-doc.py b/examples/industrial-ai-apiserver/myapp-doc.py
new file mode 100755
index 0000000000000000000000000000000000000000..88c51afba95579067a619fda6d3e64482db63ff5
--- /dev/null
+++ b/examples/industrial-ai-apiserver/myapp-doc.py
@@ -0,0 +1,24 @@
+# import pprint
+import json
+from pecan_swagger import utils
+import industrialai.api.controllers.root
+
+# pp = pprint.PrettyPrinter(indent=2)
+# pp.pprint(utils.swagger_build('industrialai', '1.0'))
+# Create JSON file
+s = utils.swagger_build('industrialai', '1.0')
+description = 'This is a This is a test project'
+termsOfService = 'http://swagger.io/terms/'
+contact = {'email': "apiteam@swagger.io"}
+license = {'name': "Apache 2.0",
+ 'url': "http://www.apache.org/licenses/LICENSE-2.0.html"}
+s = utils.add_info(s, description, termsOfService, contact, license)
+externalDocs = {'description': "Find out more about Swagger",
+ 'url': "http://swagger.io"}
+s = utils.add_external_docs(s, externalDocs)
+host = "127.0.0.1"
+s = utils.add_host(s, host)
+basePath = "/v1"
+s = utils.add_base_path(s, basePath)
+output_path = "test.json"
+utils.print_json(s, output_path)
diff --git a/examples/industrial-ai-apiserver/pecan_swagger/__init__.py b/examples/industrial-ai-apiserver/pecan_swagger/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/industrial-ai-apiserver/pecan_swagger/decorators.py b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py
new file mode 100755
index 0000000000000000000000000000000000000000..08de1584942739d632cec61f5d2c636b454e92df
--- /dev/null
+++ b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py
@@ -0,0 +1,161 @@
+import pecan_swagger.g as g
+
+"""
+decorators module
+
+these decorators are meant to be used by developers who wish to markup
+their pecan applications to produce swagger.
+"""
+
+
+def path(endpoint, name, parent=None):
+ """
+ path decorator
+
+ this decorator should be used on pecan controllers to instruct how
+ they map to routes.
+
+ :param endpoint: the root uri for this controller.
+ :param name: the name of this path.
+ :param parent: an optional path name to indicate a parent/child
+ relationship between this path and another.
+ """
+
+ def decorator(c):
+ if hasattr(c, '__swag'):
+ raise Exception('{} already has swag'.format(c.__name__))
+ c.__swag = dict(endpoint=endpoint, name=name, parent=parent)
+ g.add_path(c)
+ return c
+
+ return decorator
+
+
+def response(method_name, res_code, des, schema=None):
+ """
+ 获取方法的response
+ 生成字典{response:res_code:{description:des}}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+
+ if not m.__swag['method'][method_name].get('responses'):
+ m.__swag['method'][method_name]['responses'] = {}
+ if not m.__swag['method'][method_name]['responses'].get(res_code):
+ m.__swag['method'][method_name]['responses'][res_code] = {}
+ m.__swag['method'][method_name]['responses'][res_code]['description'] = des
+ if schema:
+ m.__swag['method'][method_name]['responses'][res_code]['schema'] = schema
+ return m
+
+ return decorator
+
+
+def tags(method_name, *tags):
+ """
+ 获取方法的tags
+ 生成字典{tags:tag}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+ if not m.__swag['method'][method_name].get('tags'):
+ m.__swag['method'][method_name]['tags'] = []
+ for i in tags:
+ m.__swag['method'][method_name]['tags'].append(i)
+ return m
+
+ return decorator
+
+
+def summary(method_name, summary):
+ """
+ 获取方法的summary
+ 生成字典{summary:summary}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+ m.__swag['method'][method_name]['summary'] = summary
+ return m
+
+ return decorator
+
+
+def description(method_name, des):
+ """
+ 获取方法的description
+ 生成字典{description:des}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+ m.__swag['method'][method_name]['description'] = des
+ return m
+
+ return decorator
+
+
+def operationId(method_name, oi):
+ """
+ 获取方法的operationId
+ 生成字典{operationId:oi}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+ m.__swag['method'][method_name]['operationId'] = oi
+ return m
+
+ return decorator
+
+
+def method(*args):
+ """
+ 获取被装饰类包含的方法
+ 生成method字典
+ 并加入controller.__swag
+ """
+
+ def decorator(m):
+ d = {}
+ for i in args:
+ if hasattr(m, i):
+ d[i] = {}
+ m.__swag['method'] = d
+ return m
+
+ return decorator
+
+
+def definitions(t, *args):
+ """
+ 添加definitions到g.py中
+ """
+
+ def decorator(m):
+ g.add_definitions(t, m)
+ return m
+
+ return decorator
+
+
+def parameter(method_name, *args):
+ """
+ 获取方法的parameter
+ 生成字典{parameter:[]}
+ 并加入controller.__swag[method][method_name]
+ """
+
+ def decorator(m):
+ if not m.__swag['method'][method_name].get('parameters'):
+ m.__swag['method'][method_name]['parameters'] = []
+ for i in args:
+ if i.get('schema'):
+ i['schema'] = {'$ref': '#/definitions/' + i['schema']}
+ m.__swag['method'][method_name]['parameters'].append(i)
+ return m
+
+ return decorator
+
diff --git a/pecan_swagger/g.py b/examples/industrial-ai-apiserver/pecan_swagger/g.py
old mode 100644
new mode 100755
similarity index 74%
rename from pecan_swagger/g.py
rename to examples/industrial-ai-apiserver/pecan_swagger/g.py
index 383c2c6fda52f76e98717f62d5b99155dc7fc12f..15f610405e3b349cff971e534609cfc901d180c0
--- a/pecan_swagger/g.py
+++ b/examples/industrial-ai-apiserver/pecan_swagger/g.py
@@ -1,487 +1,617 @@
-import copy
-import inspect
-
-from pecan import util as p_u
-import wsme.types as wtypes
-import six
-import weakref
-
-
-"""
-global hierarchy module
-
-this module is at the heart of the swagger conversion utility. it
-contains a global "hierarchy" object which will provide a single point
-to assemble an application's swagger output.
-
-there are also several helper functions to assist in building the
-hierarchy of controllers, routes, and methods.
-"""
-
-_hierarchy = {}
-
-_http_methods = {'get': '', 'post': '', 'put': '',
- 'patch': '', 'delete': '',
- 'head': '', 'trace': ''}
-
-_all_wsme_types = wtypes.native_types + (
- wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType,
- wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType,
- wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType)
-
-_definitions = {}
-
-
-def add_path(c):
- """adds a named controller to the hierarchy."""
- if _hierarchy.get(c.__swag['name']):
- raise Exception(
- 'name {} already exists in hierarchy'.format(c.__swag['name']))
- _hierarchy[c.__swag['name']] = c
-
-
-def build_path(swaginfo):
- """return the route path for a swag metadata item."""
- if swaginfo.get('parent') is not None:
- return path_join(build_path(get_swag(swaginfo.get('parent'))),
- swaginfo.get('endpoint'))
- return swaginfo.get('endpoint')
-
-
-def get_controller_paths(controllers, wsme_defs):
- """
- get a list of paths with methods
-
- returns a list of tuples (controller path, methods).
- """
- def get_methods_for_generic(name):
- methods = []
- generic_handlers = lc.get(name).get('generic_handlers', {})
- for method, func in generic_handlers.items():
- if method == 'DEFAULT':
- methods.append('get')
- else:
- methods.append(method.lower())
- # TODO drill down through decorators to get true function name
- truename = getrealname(func)
- del lc[truename]
- return methods
-
- def append_methods_for_specific(name, tpl):
- paths.append(tpl)
- del lc[name]
-
- lc = copy.deepcopy(controllers)
- paths = []
- # TODO incorporate method decorator, removing functions marked
- if lc.get('index'):
- for method in get_methods_for_generic('index'):
- paths.append(('', (method, {})))
-
- # for REST controller
- for method, path in _http_methods.items():
- if lc.get(method):
- spec = wsme_defs.get(method, {}) # add wsme definitions
- append_methods_for_specific(method, (path, (method, spec)))
-
- if lc.get('get_all'):
- # add wsme definitions
- spec = wsme_defs.get('get_all') # add wsme definitions
- append_methods_for_specific('get_all', ('', ('get', spec)))
- if lc.get('get_one'):
- # add wsme definitions
- spec = wsme_defs.get('get_one') # add wsme definitions
- append_methods_for_specific('get_one', ('', ('get', spec)))
-
- if lc.get('_default'):
- append_methods_for_specific('_default', ('', ('*', {})))
- if lc.get('_route'):
- append_methods_for_specific('_route', ('', ('*', {})))
- if lc.get('_lookup'):
- del lc['_lookup']
-
- if lc.get('_custom_actions'):
- for ca, ca_method in lc['_custom_actions'].items():
- spec = wsme_defs.get(ca) # add wsme definitions
- for m in ca_method:
- paths.append((ca, (m.lower(), spec)))
- del lc['_custom_actions']
-
- generic_controllers = [c for c in lc if lc[c].get('generic')]
- for controller in generic_controllers:
- for method in get_methods_for_generic(controller):
- paths.append((controller, (method, {})))
- for controller in lc:
- if controller not in [path[0] for path in paths]:
- paths.append((controller, ('get', {})))
- return paths
-
-
-def get_wsme_defs(name):
-
- def datatype_to_type_and_format(datatype):
- tf = {}
- if datatype == 'uuid' or datatype == 'ipv4address' \
- or datatype == 'ipv6address' or datatype == 'date' \
- or datatype == 'time':
- tf['type'] = 'string'
- tf['format'] = datatype
- elif datatype == 'datetime':
- tf['type'] = 'string'
- tf['format'] = 'date-time'
- elif datatype == 'binary' or datatype == 'bytes':
- # base64 encoded characters
- tf['type'] = 'string'
- tf['format'] = 'byte'
- elif datatype == 'array' or datatype == 'boolean' \
- or datatype == 'integer' or datatype == 'string':
- # no format
- tf['type'] = datatype
- elif datatype == 'float':
- # number
- tf['type'] = 'number'
- tf['format'] = datatype
- elif datatype == 'unicode' or datatype == 'str' \
- or datatype == 'text':
- # primitive, no format
- tf['type'] = 'string'
- elif datatype == 'int' or datatype == 'decimal':
- # primitive, no format
- tf['type'] = 'integer'
- elif datatype == 'bool':
- # primitive, no format
- tf['type'] = 'boolean'
- elif datatype == 'enum':
- tf['type'] = 'enum'
- elif datatype == 'unset' or datatype == 'none':
- tf['type'] = None
- elif datatype == 'dict':
- tf['type'] = 'object'
- else:
- tf['type'] = 'object'
- return tf
-
- def class_to_name_str(c):
- return (('%s' % c).replace('<', '').replace('>', '')
- .replace('class ', '').replace('\'', '')
- .split(' ', 1)[0].rsplit('.', 1)[-1])
-
- def conv_class_name_to_type_str(cn):
- type_str = ''
- if cn.endswith('Type'):
- type_str = cn[:-4]
- elif cn == 'str':
- type_str = 'string'
- else:
- type_str = cn
- type_str = type_str.lower()
- return type_str
-
- def get_type_str(obj, src_dict=None):
- type_str = ''
- if hasattr(obj, '__name__'):
- type_str = obj.__name__
- else:
- type_str = obj.__class__.__name__
- type_str = conv_class_name_to_type_str(type_str)
-
- tf = datatype_to_type_and_format(type_str)
-
- if hasattr(obj, 'basetype') and \
- (obj.__class__ not in _all_wsme_types or type_str == 'enum'):
- # UserType or Enum
- tf['type'] = get_type_str(obj.basetype)
-
- if isinstance(src_dict, dict):
- # if dict is in args, set 'type' and 'format' into the dict and
- # return
- src_dict.update({'type': tf['type']})
- if 'format' in tf:
- src_dict.update({'format': tf['format']})
-
- # get datatype options. ex.) min_length, minimum, ..
- for k, v in inspect.getmembers(obj):
- if ((k == 'minimum' or k == 'maxmum'
- or k == 'min_length' or k == 'max_length')
- and v is not None):
- src_dict[to_swag_key(k)] = v
- elif k == 'pattern' and v is not None:
- src_dict[to_swag_key(k)] = v.pattern
- # TODO(shu-mutou): this should be removed for production.
- # else:
- # src_dict[to_swag_key(k)] = v
-
- if type_str == 'enum':
- # EnumType use 'set' that doesn't have sequence.
- # So use 'sorted' for keeping static output.
- src_dict['enum'] = sorted([v for v in obj.values])
-
- # return 'type' only
- return tf['type']
-
- def to_swag_key(key):
- keys = {
- 'doc': 'description',
- 'arguments': 'parameters',
- 'return_type': 'schema',
- 'datatype': 'type',
- 'mandatory': 'required',
- 'sample': 'examples',
- 'readonly': 'readOnly',
- 'min_length': 'minLength',
- 'max_length': 'maxLength',
- }
- if key in keys:
- return keys[key]
- else:
- return key
-
- def get_wm_item_prop(item, isparams=False):
- # Add prop into 'properties' and 'required' in 'Items Object'
- # 'Items Object' can be part of 'Schema Object' or 'Items Object',
- # and can use recursively
- prop_dict = {}
- # TODO(shu-mutou): this should be removed for production.
- # prop_dict['obj'] = inspect.getmembers(item)
-
- _item = item
- if wtypes.iscomplex(item):
- _item = weakref.ref(item)
-
- for a, i in inspect.getmembers(_item):
- if a == 'datatype':
- datatype = get_type_str(i, prop_dict)
- if datatype == 'array':
- # if array, do recursively
- prop_dict['items'] = inspect_wm_schema(i.item_type)
- elif datatype == 'object':
- # if obuject, do recursively
- prop_dict['items'] = inspect_wm_schema(i)
- elif a == 'default' and i:
- prop_dict[to_swag_key(a)] = i
- elif a == 'name' and isparams:
- prop_dict[to_swag_key(a)] = i
- elif a == 'mandatory' and i:
- prop_dict[to_swag_key(a)] = i
- elif a == 'readonly' and i:
- prop_dict[to_swag_key(a)] = i
- elif a == 'doc' and i is not None:
- prop_dict[to_swag_key(a)] = i
-
- if isparams and prop_dict['type'] in ['object', 'array']:
- prop_dict['schema'] = {'items': prop_dict['items'],
- 'type': prop_dict['type']}
- del prop_dict['type']
- del prop_dict['items']
- return prop_dict
-
- def get_wsattr_and_wsproperty(obj):
- ws_dict = {}
- for key, value in inspect.getmembers(obj):
- if (key[0] != '_' and
- (value.__class__.__name__ == 'wsattr'
- or value.__class__.__name__ == 'wsproperty')):
- ws_dict[key] = value
- return ws_dict
-
- def inspect_wm_schema(schema_obj, isparams=False):
- schema_dict = {}
- # TODO(shu-mutou): this should be removed for production.
- # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj)
- ws_len = len(get_wsattr_and_wsproperty(schema_obj))
- model_name = class_to_name_str(schema_obj)
-
- for key, obj in inspect.getmembers(schema_obj):
- if (key[0] != '_' and
- (obj.__class__.__name__ == 'wsattr'
- or obj.__class__.__name__ == 'wsproperty')):
- # TODO(shu-mutou): this should be removed for production.
- # _definitions[model_name][to_swag_key(key)] = \
- # {'obj': inspect.getmembers(obj)}
-
- if ws_len == 1:
- # single schema
- schema_dict = get_wm_item_prop(obj, isparams)
- else:
- # multi property schema
- schema_dict.update({'type': 'object'})
- # properties
- if 'items' not in schema_dict:
- schema_dict['items'] = {'properties': {}}
- prop = {key: get_wm_item_prop(obj, isparams)}
- # required as array of string
- if 'required' in prop[key] and prop[key]['required'] \
- and isinstance(prop[key]['required'], bool):
- if 'required' not in schema_dict:
- schema_dict['required'] = []
- schema_dict['required'].append(key)
- del prop[key]['required']
- schema_dict['items']['properties'].update(prop)
-
- if schema_obj not in _all_wsme_types:
- if model_name not in _definitions:
- _definitions[model_name] = schema_dict
- schema_dict = {'$ref': "#/definitions/%s" % model_name}
-
- return schema_dict
-
- def get_wm_def(o):
- wsme = {'description': ''}
- for w, d in inspect.getmembers(o):
- if w == 'arguments':
- wsme[to_swag_key(w)] = []
- for arg in d:
- # set each 'Parameter Object', it can include
- # 'Items Object' recursively
- item_dict = get_wm_item_prop(arg, True)
- # TODO: MUST be set one of
- # 'body|query|path|header|formData'
- item_dict['in'] = 'query'
- wsme[to_swag_key(w)].append(item_dict)
- # 'body' should be set due to existing 'body' option
- # in WSME expose
- if o.body_type is not None:
- # if body is set, last arg is it.
- wsme[to_swag_key(w)][-1]['in'] = 'body'
- elif w == 'doc' and d:
- wsme[to_swag_key(w)] = d
- elif w == 'return_type':
- wsme['responses'] = {'status': {'description': ''}}
- if d:
- if d in _all_wsme_types:
- # d is set single WSME type class or implicit type
- wsme['responses']['status'][to_swag_key(w)] = (
- datatype_to_type_and_format(
- conv_class_name_to_type_str(
- class_to_name_str(d))))
- else:
- # d is model class
- wsme['responses']['status'][to_swag_key(w)] = (
- inspect_wm_schema(d, False))
- doc = inspect.getdoc(d)
- if doc is not None:
- wsme['responses']['status']['description'] = doc
- elif w == 'status_code':
- wsme['responses'][d] = wsme['responses']['status']
- del wsme['responses']['status']
- # TODO(shu-mutou): this should be removed for production.
- # elif w == 'body_type':
- # wsme[to_swag_key(w)] = get_type_str(d)
- # elif w == 'extra_options' or w == 'ignore_extra_args' \
- # or w == 'name' or w == 'pass_request':
- # wsme[to_swag_key(w)] = d
- # else:
- # wsme[to_swag_key(w)] = d
- return wsme
-
- c = _hierarchy[name]
- wsme_defs = {}
- for k, v in inspect.getmembers(c):
- if p_u.iscontroller(v):
- for m, o in inspect.getmembers(v):
- if m == "_wsme_definition":
- wsme_defs[k] = get_wm_def(o)
-
- # TODO(shu-mutou): this should be removed for production.
- # output wsme info into files by each controller for dev
- # import pprint
- # with open(name + '.txt', 'w') as fout:
- # pprint.pprint(wsme_defs, stream=fout, indent=2)
-
- return wsme_defs
-
-
-def get_controllers(name):
- """
- get all the controllers associated with a path
-
- returns a dictionary of controllers indexed by their names.
- """
- c = _hierarchy[name]
- controllers = {k: p_u._cfg(v)
- for k, v in c.__dict__.items() if p_u.iscontroller(v)}
- cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'}
- if len(cacts):
- controllers.update(cacts)
- return controllers
-
-
-def get_paths():
- """
- return all the registered paths
-
- loops through the hierarchy and retuns a list of tuples containing the
- paths and their methods.
-
- :returns: [(path, methods), ...]
- """
- pathlist = []
- for name in _hierarchy:
- fullpath = build_path(get_swag(name))
- controllers = get_controllers(name)
- wsme_defs = get_wsme_defs(name)
- paths = get_controller_paths(controllers, wsme_defs)
- for path in paths:
- ptuple = (path_join(fullpath, path[0]), path[1])
- pathlist.append(ptuple)
- return pathlist
-
-
-def get_swag(name):
- """return the swag metadata from an named controller."""
- return _hierarchy.get(name).__swag
-
-
-def getrealname(method):
- """attempt to get a method's real name."""
- argspec = inspect.getargspec(method)
- args = argspec[0]
- if args and args[0] == 'self':
- return method.__name__
- if hasattr(method, '__func__'):
- method = method.__func__
-
- func_closure = six.get_function_closure(method)
-
- # NOTE(sileht): if the closure is None we cannot look deeper,
- # so return actual argspec, this occurs when the method
- # is static for example.
- if func_closure is None:
- return method.__name__
-
- closure = next(
- (
- c for c in func_closure if six.callable(c.cell_contents)
- ),
- None
- )
- method = closure.cell_contents
- return getrealname(method)
-
-
-def methods_get(name):
- """get all the methods for a named controller."""
- c = _hierarchy[name]
- mlist = []
- if hasattr(c, 'index') and p_u.iscontroller(c.index):
- cfg = p_u._cfg(c.index)
- if cfg.get('generic_handlers').get('DEFAULT'):
- mlist.append('get')
- mlist += cfg.get('allowed_methods')
- for i in c.__dict__:
- ii = getattr(c, i)
- if hasattr(ii, '__swag'):
- m = ii.__swag.get('method')
- if m is not None:
- mlist.append(m)
- return mlist
-
-
-def path_join(part1, part2):
- """join two url paths."""
- if len(part2) == 0:
- return part1
- sep = '/'
- if part1[-1] == sep:
- sep = ''
- return part1 + sep + part2
+import copy
+import inspect
+
+from pecan import util as p_u
+import wsme.types as wtypes
+import six
+import weakref
+
+"""
+global hierarchy module
+
+this module is at the heart of the swagger conversion utility. it
+contains a global "hierarchy" object which will provide a single point
+to assemble an application's swagger output.
+
+there are also several helper functions to assist in building the
+hierarchy of controllers, routes, and methods.
+"""
+
+_hierarchy = {}
+
+_http_methods = {'get': '', 'post': '', 'put': '',
+ 'patch': '', 'delete': '',
+ 'head': '', 'trace': ''}
+
+_all_wsme_types = wtypes.native_types + (
+ wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType,
+ wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType,
+ wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType)
+
+_definitions = {}
+
+all_definitions = {}
+
+
+def add_path(c):
+ """adds a named controller to the hierarchy."""
+ if _hierarchy.get(c.__swag['name']):
+ raise Exception(
+ 'name {} already exists in hierarchy'.format(c.__swag['name']))
+ _hierarchy[c.__swag['name']] = c
+
+
+def build_path(swaginfo):
+ """return the route path for a swag metadata item."""
+ if swaginfo.get('parent') is not None:
+ return path_join(build_path(get_swag(swaginfo.get('parent'))),
+ swaginfo.get('endpoint'))
+ return swaginfo.get('endpoint')
+
+
+def get_controller_paths(controllers, wsme_defs):
+ """
+ get a list of paths with methods
+
+ returns a list of tuples (controller path, methods).
+ """
+
+ def get_methods_for_generic(name):
+ methods = []
+ generic_handlers = lc.get(name).get('generic_handlers', {})
+ for method, func in generic_handlers.items():
+ if method == 'DEFAULT':
+ methods.append('get')
+ else:
+ methods.append(method.lower())
+ # TODO drill down through decorators to get true function name
+ truename = getrealname(func)
+ del lc[truename]
+ return methods
+
+ def append_methods_for_specific(name, tpl):
+ paths.append(tpl)
+ del lc[name]
+
+ lc = copy.deepcopy(controllers)
+ paths = []
+ # TODO incorporate method decorator, removing functions marked
+ if lc.get('index'):
+ for method in get_methods_for_generic('index'):
+ paths.append(('', (method, {})))
+ spec = wsme_defs.get('index') # add wsme definitions
+ append_methods_for_specific('index', ('', ('get', spec)))
+
+ # for REST controller
+ for method, path in _http_methods.items():
+ if lc.get(method):
+ spec = wsme_defs.get(method, {}) # add wsme definitions
+ append_methods_for_specific(method, (path, (method, spec)))
+
+ if lc.get('get_all'):
+ # add wsme definitions
+ spec = wsme_defs.get('get_all') # add wsme definitions
+ append_methods_for_specific('get_all', ('get_all', ('get', spec)))
+ if lc.get('get_one'):
+ # add wsme definitions
+ spec = wsme_defs.get('get_one') # add wsme definitions
+ append_methods_for_specific('get_one', ('get_one', ('get', spec)))
+
+ if lc.get('_default'):
+ append_methods_for_specific('_default', ('', ('*', {})))
+ if lc.get('_route'):
+ append_methods_for_specific('_route', ('', ('*', {})))
+ if lc.get('_lookup'):
+ del lc['_lookup']
+
+ if lc.get('_custom_actions'):
+ for ca, ca_method in lc['_custom_actions'].items():
+ spec = wsme_defs.get(ca) # add wsme definitions
+ for m in ca_method:
+ paths.append((ca, (m.lower(), spec)))
+ del lc['_custom_actions']
+
+ generic_controllers = [c for c in lc if lc[c].get('generic')]
+ for controller in generic_controllers:
+ for method in get_methods_for_generic(controller):
+ paths.append((controller, (method, {})))
+ for controller in lc:
+ if controller not in [path[0] for path in paths]:
+ paths.append((controller, ('get', {})))
+ return paths
+
+
+def get_wsme_defs(name):
+ """获得wsexpose装饰方法的definitions."""
+
+ def datatype_to_type_and_format(datatype):
+ tf = {}
+ if datatype == 'uuid' or datatype == 'ipv4address' \
+ or datatype == 'ipv6address' or datatype == 'date' \
+ or datatype == 'time':
+ tf['type'] = 'string'
+ tf['format'] = datatype
+ elif datatype == 'datetime':
+ tf['type'] = 'string'
+ tf['format'] = 'date-time'
+ elif datatype == 'binary' or datatype == 'bytes':
+ # base64 encoded characters
+ tf['type'] = 'string'
+ tf['format'] = 'byte'
+ elif datatype == 'array' or datatype == 'boolean' \
+ or datatype == 'integer' or datatype == 'string':
+ # no format
+ tf['type'] = datatype
+ elif datatype == 'float':
+ # number
+ tf['type'] = 'number'
+ tf['format'] = datatype
+ elif datatype == 'unicode' or datatype == 'str' \
+ or datatype == 'text':
+ # primitive, no format
+ tf['type'] = 'string'
+ elif datatype == 'int' or datatype == 'decimal':
+ # primitive, no format
+ tf['type'] = 'integer'
+ elif datatype == 'bool':
+ # primitive, no format
+ tf['type'] = 'boolean'
+ elif datatype == 'enum':
+ tf['type'] = 'enum'
+ elif datatype == 'unset' or datatype == 'none':
+ tf['type'] = None
+ elif datatype == 'dict':
+ tf['type'] = 'object'
+ else:
+ tf['type'] = 'object'
+ return tf
+
+ def class_to_name_str(c):
+ return (('%s' % c).replace('<', '').replace('>', '')
+ .replace('class ', '').replace('\'', '')
+ .split(' ', 1)[0].rsplit('.', 1)[-1])
+
+ def conv_class_name_to_type_str(cn):
+ type_str = ''
+ if cn.endswith('Type'):
+ type_str = cn[:-4]
+ elif cn == 'str':
+ type_str = 'string'
+ else:
+ type_str = cn
+ type_str = type_str.lower()
+ return type_str
+
+ def get_type_str(obj, src_dict=None):
+ type_str = ''
+ if hasattr(obj, '__name__'):
+ type_str = obj.__name__
+ else:
+ type_str = obj.__class__.__name__
+ type_str = conv_class_name_to_type_str(type_str)
+
+ tf = datatype_to_type_and_format(type_str)
+
+ if hasattr(obj, 'basetype') and \
+ (obj.__class__ not in _all_wsme_types or type_str == 'enum'):
+ # UserType or Enum
+ tf['type'] = get_type_str(obj.basetype)
+
+ if isinstance(src_dict, dict):
+ # if dict is in args, set 'type' and 'format' into the dict and
+ # return
+ src_dict.update({'type': tf['type']})
+ if 'format' in tf:
+ src_dict.update({'format': tf['format']})
+
+ # get datatype options. ex.) min_length, minimum, ..
+ for k, v in inspect.getmembers(obj):
+ if ((k == 'minimum' or k == 'maxmum'
+ or k == 'min_length' or k == 'max_length')
+ and v is not None):
+ src_dict[to_swag_key(k)] = v
+ elif k == 'pattern' and v is not None:
+ src_dict[to_swag_key(k)] = v.pattern
+ # TODO(shu-mutou): this should be removed for production.
+ # else:
+ # src_dict[to_swag_key(k)] = v
+
+ if type_str == 'enum':
+ # EnumType use 'set' that doesn't have sequence.
+ # So use 'sorted' for keeping static output.
+ src_dict['enum'] = sorted([v for v in obj.values])
+
+ # return 'type' only
+ return tf['type']
+
+ def to_swag_key(key):
+ keys = {
+ 'doc': 'description',
+ 'arguments': 'parameters',
+ 'return_type': 'schema',
+ 'datatype': 'type',
+ 'mandatory': 'required',
+ 'sample': 'examples',
+ 'readonly': 'readOnly',
+ 'min_length': 'minLength',
+ 'max_length': 'maxLength',
+ }
+ if key in keys:
+ return keys[key]
+ else:
+ return key
+
+ def get_wm_item_prop(item, isparams=False):
+ # Add prop into 'properties' and 'required' in 'Items Object'
+ # 'Items Object' can be part of 'Schema Object' or 'Items Object',
+ # and can use recursively
+ prop_dict = {}
+ # TODO(shu-mutou): this should be removed for production.
+ # prop_dict['obj'] = inspect.getmembers(item)
+
+ _item = item
+ if wtypes.iscomplex(item):
+ _item = weakref.ref(item)
+
+ for a, i in inspect.getmembers(_item):
+ if a == 'datatype':
+ datatype = get_type_str(i, prop_dict)
+ if datatype == 'array':
+ # if array, do recursively
+ prop_dict['items'] = inspect_wm_schema(i.item_type)
+ elif datatype == 'object':
+ # if obuject, do recursively
+ prop_dict['items'] = inspect_wm_schema(i)
+ elif a == 'default' and i:
+ prop_dict[to_swag_key(a)] = i
+ elif a == 'name' and isparams:
+ prop_dict[to_swag_key(a)] = i
+ elif a == 'mandatory' and i:
+ prop_dict[to_swag_key(a)] = i
+ elif a == 'readonly' and i:
+ prop_dict[to_swag_key(a)] = i
+ elif a == 'doc' and i is not None:
+ prop_dict[to_swag_key(a)] = i
+
+ if isparams and prop_dict['type'] in ['object', 'array']:
+ prop_dict['schema'] = {'items': prop_dict['items'],
+ 'type': prop_dict['type']}
+ if prop_dict['type'] == 'object':
+ prop_dict['schema'] = prop_dict['items']
+ del prop_dict['type']
+ del prop_dict['items']
+ return prop_dict
+
+ def get_wsattr_and_wsproperty(obj):
+ ws_dict = {}
+ for key, value in inspect.getmembers(obj):
+ if (key[0] != '_' and
+ (value.__class__.__name__ == 'wsattr'
+ or value.__class__.__name__ == 'wsproperty')):
+ ws_dict[key] = value
+ return ws_dict
+
+ def inspect_wm_schema(schema_obj, isparams=False):
+ schema_dict = {}
+ # TODO(shu-mutou): this should be removed for production.
+ # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj)
+ ws_len = len(get_wsattr_and_wsproperty(schema_obj))
+ model_name = class_to_name_str(schema_obj)
+
+ for key, obj in inspect.getmembers(schema_obj):
+ if (key[0] != '_' and
+ (obj.__class__.__name__ == 'wsattr'
+ or obj.__class__.__name__ == 'wsproperty')):
+ # TODO(shu-mutou): this should be removed for production.
+ # _definitions[model_name][to_swag_key(key)] = \
+ # {'obj': inspect.getmembers(obj)}
+
+ if ws_len == 1:
+ # single schema
+ schema_dict = get_wm_item_prop(obj, isparams)
+ else:
+ # multi property schema
+ schema_dict.update({'type': 'object'})
+ # properties
+ if 'properties' not in schema_dict:
+ schema_dict['properties'] = {}
+ prop = {key: get_wm_item_prop(obj, isparams)}
+ # required as array of string
+ if 'required' in prop[key] and prop[key]['required'] \
+ and isinstance(prop[key]['required'], bool):
+ if 'required' not in schema_dict:
+ schema_dict['required'] = []
+ schema_dict['required'].append(key)
+ del prop[key]['required']
+ schema_dict['properties'].update(prop)
+
+ if schema_obj not in _all_wsme_types:
+ if model_name not in _definitions:
+ _definitions[model_name] = schema_dict
+ schema_dict = {'$ref': "#/definitions/%s" % model_name}
+
+ return schema_dict
+
+ def get_wm_def(o):
+ wsme = {'description': ''}
+ for w, d in inspect.getmembers(o):
+ if w == 'arguments':
+ wsme[to_swag_key(w)] = []
+ for arg in d:
+ # set each 'Parameter Object', it can include
+ # 'Items Object' recursively
+ item_dict = get_wm_item_prop(arg, True)
+ # TODO: MUST be set one of
+ # 'body|query|path|header|formData'
+ item_dict['in'] = 'query'
+ wsme[to_swag_key(w)].append(item_dict)
+ # 'body' should be set due to existing 'body' option
+ # in WSME expose
+ if o.body_type is not None:
+ # if body is set, last arg is it.
+ wsme[to_swag_key(w)][-1]['in'] = 'body'
+ elif w == 'doc' and d:
+ wsme[to_swag_key(w)] = d
+ elif w == 'return_type':
+ wsme['responses'] = {'status': {'description': ''}}
+ if d:
+ if d in _all_wsme_types:
+ # d is set single WSME type class or implicit type
+ wsme['responses']['status'][to_swag_key(w)] = (
+ datatype_to_type_and_format(
+ conv_class_name_to_type_str(
+ class_to_name_str(d))))
+ else:
+ # d is model class
+ wsme['responses']['status'][to_swag_key(w)] = (
+ inspect_wm_schema(d, False))
+ doc = inspect.getdoc(d)
+ if doc is not None:
+ wsme['responses']['status']['description'] = doc
+ elif w == 'status_code':
+ wsme['responses'][d] = wsme['responses']['status']
+ del wsme['responses']['status']
+ # TODO(shu-mutou): this should be removed for production.
+ # elif w == 'body_type':
+ # wsme[to_swag_key(w)] = get_type_str(d)
+ # elif w == 'extra_options' or w == 'ignore_extra_args' \
+ # or w == 'name' or w == 'pass_request':
+ # wsme[to_swag_key(w)] = d
+ # else:
+ # wsme[to_swag_key(w)] = d
+ return wsme
+
+ c = _hierarchy[name]
+ wsme_defs = {}
+ for k, v in inspect.getmembers(c):
+ if p_u.iscontroller(v):
+ for m, o in inspect.getmembers(v):
+ if m == "_wsme_definition":
+ wsme_defs[k] = get_wm_def(o)
+
+ # TODO(shu-mutou): this should be removed for production.
+ # output wsme info into files by each controller for dev
+ # import pprint
+ # with open(name + '.txt', 'w') as fout:
+ # pprint.pprint(wsme_defs, stream=fout, indent=2)
+
+ return wsme_defs
+
+
+def get_controllers(name):
+ """
+ get all the controllers associated with a path
+
+ returns a dictionary of controllers indexed by their names.
+ """
+ c = _hierarchy[name]
+ controllers = {k: p_u._cfg(v)
+ for k, v in c.__dict__.items() if p_u.iscontroller(v)}
+ cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'}
+ if len(cacts):
+ controllers.update(cacts)
+ return controllers
+
+
+def get_paths():
+ """
+ return all the registered paths
+
+ loops through the hierarchy and retuns a list of tuples containing the
+ paths and their methods.
+
+ :returns: [(path, methods), ...]
+ """
+ pathlist = []
+ for name in _hierarchy:
+ fullpath = build_path(get_swag(name))
+ controllers = get_controllers(name)
+ paths = get_controller_paths(controllers, combine_method_defs(name))
+ for path in paths:
+ ptuple = (path_join(fullpath, path[0]), path[1])
+ pathlist.append(ptuple)
+ combine_all_definitions()
+ return pathlist
+
+
+def get_swag(name):
+ """return the swag metadata from an named controller."""
+ return _hierarchy.get(name).__swag
+
+
+def getrealname(method):
+ """attempt to get a method's real name."""
+ argspec = inspect.getargspec(method)
+ args = argspec[0]
+ if args and args[0] == 'self':
+ return method.__name__
+ if hasattr(method, '__func__'):
+ method = method.__func__
+
+ func_closure = six.get_function_closure(method)
+
+ # NOTE(sileht): if the closure is None we cannot look deeper,
+ # so return actual argspec, this occurs when the method
+ # is static for example.
+ if func_closure is None:
+ return method.__name__
+
+ closure = next(
+ (
+ c for c in func_closure if six.callable(c.cell_contents)
+ ),
+ None
+ )
+ method = closure.cell_contents
+ return getrealname(method)
+
+
+def methods_get(name):
+ """get all the methods for a named controller."""
+ c = _hierarchy[name]
+ mlist = []
+ if hasattr(c, 'index') and p_u.iscontroller(c.index):
+ cfg = p_u._cfg(c.index)
+ if cfg.get('generic_handlers') and cfg.get('generic_handlers').get('DEFAULT'):
+ mlist.append('get')
+ if cfg.get('allowed_methods'):
+ mlist += cfg.get('allowed_methods')
+ if hasattr(c, '__swag') and c.__swag.get('method'):
+ for i in c.__swag['method']:
+ mlist.append(i)
+ # for i in c.__dict__:
+ # ii = getattr(c, i)
+ # if hasattr(ii, '__swag'):
+ # m = ii.__swag.get('method')
+ # if m is not None:
+ # mlist.append(m)
+ return mlist
+
+
+def path_join(part1, part2):
+ """join two url paths."""
+ if len(part2) == 0:
+ return part1
+ sep = '/'
+ if part1[-1] == sep:
+ sep = ''
+ return part1 + sep + part2
+
+
+def get_def(name):
+ """获取所有expose装饰方法的definitions."""
+ c = _hierarchy[name]
+ if hasattr(c, '__swag') and c.__swag.get('method'):
+ return c.__swag['method']
+ return {}
+
+
+def combine_method_defs(name):
+ """合并expose和wsexpose装饰方法的definitions."""
+
+ c = _hierarchy[name]
+ lc = copy.deepcopy(c)
+ w_d = get_wsme_defs(name)
+ e_d = get_def(name)
+ if w_d and e_d:
+ for i in w_d:
+ if e_d.get(i):
+ for j in w_d[i]:
+ e_d[i][j] = w_d[i][j]
+ else:
+ e_d[i] = w_d[i]
+ elif w_d:
+ return w_d
+ else:
+ return e_d
+ return e_d
+
+
+def format_type(t):
+ """
+ 判断数据类型是否为自定义的数据类型
+ """
+
+ def datatype_to_type_and_format(datatype):
+
+ tf = {}
+ if datatype == 'uuid' or datatype == 'ipv4address' \
+ or datatype == 'ipv6address' or datatype == 'date' \
+ or datatype == 'time':
+ tf['type'] = 'string'
+ tf['format'] = datatype
+ elif datatype == 'datetime':
+ tf['type'] = 'string'
+ tf['format'] = 'date-time'
+ elif datatype == 'binary' or datatype == 'bytes':
+ # base64 encoded characters
+ tf['type'] = 'string'
+ tf['format'] = 'byte'
+ elif datatype == 'array' or datatype == 'boolean' \
+ or datatype == 'integer' or datatype == 'string':
+ # no format
+ tf['type'] = datatype
+ elif datatype == 'float':
+ # number
+ tf['type'] = 'number'
+ tf['format'] = datatype
+ elif datatype == 'unicode' or datatype == 'str' \
+ or datatype == 'text':
+ # primitive, no format
+ tf['type'] = 'string'
+ elif datatype == 'int' or datatype == 'decimal':
+ # primitive, no format
+ tf['type'] = 'integer'
+ elif datatype == 'bool':
+ # primitive, no format
+ tf['type'] = 'boolean'
+ elif datatype == 'enum':
+ tf['type'] = 'enum'
+ elif datatype == 'unset' or datatype == 'none':
+ tf['type'] = None
+ elif datatype == 'dict':
+ tf['type'] = 'object'
+ else:
+ tf['$ref'] = '#/definitions/' + datatype
+ return tf
+
+ def conv_class_name_to_type_str(cn):
+ type_str = ''
+ if cn.endswith('Type'):
+ type_str = cn[:-4]
+ elif cn == 'str':
+ type_str = 'string'
+ else:
+ type_str = cn
+ type_str = type_str
+ return type_str
+
+ def class_to_name_str(c):
+ return (('%s' % c).replace('<', '').replace('>', '')
+ .replace('class ', '').replace('\'', '')
+ .split(' ', 1)[0].rsplit('.', 1)[-1])
+
+ return datatype_to_type_and_format(conv_class_name_to_type_str(class_to_name_str(t)))
+
+
+def add_definitions(t, m):
+ """将定义的类添加到all_definitions."""
+
+ if not all_definitions.get(m.__name__):
+ d = {}
+ d['type'] = t
+ d['properties'] = {}
+ for i, j in inspect.getmembers(m):
+ if not i.endswith('__'):
+ d['properties'][i] = format_type(j)
+ all_definitions[m.__name__] = d
+
+
+def combine_all_definitions():
+ """合并自定义和wsexpose解析出的definitions."""
+
+ for i in all_definitions:
+ if not _definitions.get(i):
+ _definitions[i] = all_definitions[i]
+
+ return _definitions
diff --git a/pecan_swagger/utils.py b/examples/industrial-ai-apiserver/pecan_swagger/utils.py
old mode 100644
new mode 100755
similarity index 62%
rename from pecan_swagger/utils.py
rename to examples/industrial-ai-apiserver/pecan_swagger/utils.py
index aa3d3b6155a6322005791e833abb278cd6ed2a01..889fbf4d19bf24a2948c156be442f15db5ec7ccd
--- a/pecan_swagger/utils.py
+++ b/examples/industrial-ai-apiserver/pecan_swagger/utils.py
@@ -1,64 +1,108 @@
-from pecan_swagger import g as g
-
-
-"""
-utility function module
-
-this module contains the utility functions to assemble the swagger
-dictionary object. they can be consumed by end-user applications to
-build up swagger objects for applications.
-
-functions:
-swagger_build -- build a full swagger dictionary
-"""
-
-
-def swagger_build(title, version):
- swag = dict()
- swag['swagger'] = '2.0'
- swag['info'] = dict(title=title, version=version)
- swag['consumes'] = []
- swag['produces'] = []
- swag['paths'] = {}
- for p in g.get_paths():
- if p[0] not in swag['paths']:
- swag['paths'][p[0]] = _tuple_to_dict(p[1])
- elif len(p[1]) > 0:
- swag['paths'][p[0]].update(_tuple_to_dict(p[1]))
- swag['definitions'] = g._definitions
- return swag
-
-
-def _tuple_to_dict(tpl):
- """Convert tuple to dictionary
-
- each tuple must have key and value.
- This function arrows taple including lists.
-
- ex.) acceptable taple
- OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name']))))
- NG: ('/', ('get',('desc','res',('params',['id','name']))))
- """
- if isinstance(tpl, list):
- d = []
- for e in tpl:
- d.append(_tuple_to_dict(e))
- elif isinstance(tpl, tuple):
- d = {}
- if isinstance(tpl[0], tuple):
- # tuple has some child tuple
- for e in tpl:
- d[e[0]] = _tuple_to_dict(e[1])
- elif isinstance(tpl[0], list):
- # list member should be processed recursively
- d = _tuple_to_dict(tpl[0])
- else:
- if len(tpl) == 2:
- # single tuple node
- d[tpl[0]] = _tuple_to_dict(tpl[1])
- else:
- raise Exception(tpl)
- else:
- # value or dict
- d = tpl
- return d
+from pecan_swagger import g as g
+import json
+import yaml
+
+"""
+utility function module
+
+this module contains the utility functions to assemble the swagger
+dictionary object. they can be consumed by end-user applications to
+build up swagger objects for applications.
+
+functions:
+swagger_build -- build a full swagger dictionary
+"""
+
+
+def swagger_build(title, version):
+ swag = dict()
+ swag['swagger'] = '2.0'
+ swag['info'] = dict(title=title, version=version)
+ swag['consumes'] = []
+ swag['produces'] = []
+ swag['paths'] = {}
+ swag['tags'] = []
+ swag['schemes'] = []
+ for p in g.get_paths():
+ if p[0] not in swag['paths']:
+ swag['paths'][p[0]] = _tuple_to_dict(p[1])
+ elif len(p[1]) > 0:
+ swag['paths'][p[0]].update(_tuple_to_dict(p[1]))
+ swag['definitions'] = g._definitions
+ return swag
+
+
+def print_json(s, output_path):
+ json_str = json.dumps(s, indent=4)
+ f = open(output_path, "w")
+ f.write(json_str)
+ f.close()
+
+
+def print_yaml(s, output_path):
+ yaml_str = yaml.dump(s, indent=4)
+ f = open(output_path, "w")
+ f.write(yaml_str)
+ f.close()
+
+
+def add_info(s, description=None, terms_of_service=None, contact=None, license=None):
+ if description is not None:
+ s['info']['description'] = description
+ if terms_of_service is not None:
+ s['info']['termsOfService'] = terms_of_service
+ if contact is not None:
+ s['info']['contact'] = contact
+ if license is not None:
+ s['info']['license'] = license
+ return s
+
+
+def add_external_docs(s, external_docs):
+ s["externalDocs"] = external_docs
+ return s
+
+
+def add_host(s, host):
+ s["host"] = host
+ return s
+
+
+def add_base_path(s, basepath):
+ s["basePath"] = basepath
+ return s
+
+
+def _tuple_to_dict(tpl):
+ """Convert tuple to dictionary
+
+ each tuple must have key and value.
+ This function arrows taple including lists.
+
+ ex.) acceptable taple
+ OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name']))))
+ NG: ('/', ('get',('desc','res',('params',['id','name']))))
+ """
+ if isinstance(tpl, list):
+ d = []
+ for e in tpl:
+ d.append(_tuple_to_dict(e))
+ elif isinstance(tpl, tuple):
+ d = {}
+ if isinstance(tpl[0], tuple):
+ # tuple has some child tuple
+ for e in tpl:
+ d[e[0]] = _tuple_to_dict(e[1])
+ elif isinstance(tpl[0], list):
+ # list member should be processed recursively
+ d = _tuple_to_dict(tpl[0])
+ else:
+ if len(tpl) == 2:
+ # single tuple node
+ d[tpl[0]] = _tuple_to_dict(tpl[1])
+ else:
+ raise Exception(tpl)
+ else:
+ # value or dict
+ d = tpl
+ return d
diff --git a/pecan_swagger/.DS_Store b/pecan_swagger/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6
Binary files /dev/null and b/pecan_swagger/.DS_Store differ
diff --git a/pecan_swagger/decorators.py b/pecan_swagger/decorators.py
deleted file mode 100644
index 5b8ef4aefaa1919dbeca2d4e6f9715f41b14c6ed..0000000000000000000000000000000000000000
--- a/pecan_swagger/decorators.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pecan_swagger.g as g
-
-"""
-decorators module
-
-these decorators are meant to be used by developers who wish to markup
-their pecan applications to produce swagger.
-"""
-
-
-def path(endpoint, name, parent=None):
- """
- path decorator
-
- this decorator should be used on pecan controllers to instruct how
- they map to routes.
-
- :param endpoint: the root uri for this controller.
- :param name: the name of this path.
- :param parent: an optional path name to indicate a parent/child
- relationship between this path and another.
- """
- def decorator(c):
- if hasattr(c, '__swag'):
- raise Exception('{} already has swag'.format(c.__name__))
- c.__swag = dict(endpoint=endpoint, name=name, parent=parent)
- g.add_path(c)
- return c
- return decorator
-
-
-def method(method):
- def decorator(m):
- if hasattr(m, '__swag'):
- raise Exception('{} already has swag'.format(m.__name__))
- m.__swag = dict(method=method)
- return m
- return decorator