442 lines
14 KiB
Python
442 lines
14 KiB
Python
import inspect
|
|
import re
|
|
from typing import Annotated, get_type_hints
|
|
from paramecio2.libraries.responsesapi import ListItem
|
|
from settings import config
|
|
|
|
wsgi_type='flask'
|
|
|
|
if hasattr(config, 'wsgi_type'):
|
|
wsgi_type=config.wsgi_type
|
|
|
|
if wsgi_type=='flask':
|
|
|
|
from flask import current_app
|
|
|
|
#from app import app
|
|
|
|
"""
|
|
import app
|
|
|
|
for key, view in app.app.view_functions.items():
|
|
#print(key)
|
|
#print(url_for(key), key, view)
|
|
if key.find('.')!=-1:
|
|
#print(url_for(key))
|
|
print(rule, view)
|
|
for rule in app.app.url_map.iter_rules():
|
|
print(rule.endpoint, rule)
|
|
"""
|
|
|
|
class AppDoc:
|
|
|
|
def __init__(self, app, api_description):
|
|
|
|
self.routes={}
|
|
self.description=api_description
|
|
self.tags={}
|
|
self.components={}
|
|
|
|
arr_apidocs=[]
|
|
arr_components=[]
|
|
|
|
rules={}
|
|
|
|
if wsgi_type=='flask':
|
|
|
|
for rule in current_app.url_map.iter_rules():
|
|
#print(rule.endpoint, rule)
|
|
if rule.endpoint.startswith(app.name+'.'):
|
|
rules[rule.endpoint]=rule
|
|
|
|
#print(rules)
|
|
|
|
for key, view in current_app.view_functions.items():
|
|
#print(app.name)
|
|
#print(key)
|
|
if key.startswith(app.name+'.'):
|
|
#print(view.get_callback_args())
|
|
#if 'tag' in inspect.getmembers(view, lambda a:not(inspect.isroutine(a))):
|
|
# print(key)
|
|
if 'tag' in list(inspect.signature(view).parameters.keys()):
|
|
route=re.sub(r'<\w+[:](\w+)?>', '{\\1}', rules[key].rule)
|
|
#print(route)
|
|
|
|
if 'GET' in rules[key].methods:
|
|
method='get'
|
|
if 'POST' in rules[key].methods:
|
|
method='post'
|
|
if 'PUT' in rules[key].methods:
|
|
method='put'
|
|
if 'DELETE' in rules[key].methods:
|
|
method='delete'
|
|
|
|
apidoc=ApiDoc(view, route, method, 'Action summary', 'Description')
|
|
|
|
self.routes[key]=apidoc
|
|
|
|
if not apidoc.tag in arr_apidocs:
|
|
|
|
arr_apidocs.append(apidoc.tag)
|
|
|
|
self.tags[apidoc.tag]=apidoc.tag.capitalize()
|
|
|
|
if len(apidoc.component)>0:
|
|
|
|
self.components.update(apidoc.component)
|
|
|
|
type_hints=get_type_hints(view)
|
|
|
|
if 'return' in type_hints:
|
|
|
|
return_obj=type_hints['return']
|
|
|
|
name_comp=return_obj.__name__
|
|
|
|
self.components=get_fields(return_obj, name_comp, self.components)
|
|
else:
|
|
|
|
for entry in app.routes:
|
|
#print(entry.args)
|
|
# Function is added to doc api if tag inside
|
|
|
|
if 'tag' in entry.get_callback_args():
|
|
|
|
route=re.sub(r'<(\w+)[:]?.*?>', '{\\1}', entry.rule)
|
|
#print(entry.name)
|
|
#routes.append({'route': orule, 'method': entry.method.lower(), 'api_description': api_description, 'tag': tag})
|
|
method=entry.method.lower()
|
|
|
|
apidoc=ApiDoc(entry.callback, route, method, 'Action summary', 'Description')
|
|
|
|
self.routes[entry.name]=apidoc
|
|
|
|
#{'name': 'apidoc', 'description': 'apidoc description'}
|
|
|
|
if not apidoc.tag in arr_apidocs:
|
|
|
|
arr_apidocs.append(apidoc.tag)
|
|
|
|
self.tags[apidoc.tag]=apidoc.tag.capitalize()
|
|
|
|
if len(apidoc.component)>0:
|
|
#print(apidoc.component)
|
|
self.components.update(apidoc.component)
|
|
|
|
#if not apidoc.components
|
|
|
|
# Get return from route function called entry.callback
|
|
|
|
type_hints=get_type_hints(entry.callback)
|
|
|
|
if 'return' in type_hints:
|
|
|
|
return_obj=type_hints['return']
|
|
|
|
name_comp=return_obj.__name__
|
|
|
|
#fields_return=inspect.getmembers(return_obj, lambda a:not(inspect.isroutine(a)))
|
|
|
|
self.components=get_fields(return_obj, name_comp, self.components)
|
|
|
|
def get_fields(return_obj, name_comp, components):
|
|
|
|
if not name_comp in components and name_comp!='StandardResponse':
|
|
|
|
components[name_comp]={}
|
|
|
|
fields_return=inspect.getmembers(return_obj, lambda a:not(inspect.isroutine(a)))
|
|
|
|
for f in [field for field in fields_return if not field[0].startswith('__')]:
|
|
|
|
name_prop=f[0]
|
|
|
|
if type(f[1]).__bases__[0] is not ListItem:
|
|
|
|
components[name_comp][name_prop]={}
|
|
components[name_comp][name_prop]['type']=getattr(f[1], 'jtype')
|
|
|
|
try:
|
|
components[name_comp][name_prop]['format']=getattr(f[1], 'jformat')
|
|
except AttributeError:
|
|
components[name_comp][name_prop]['format']=''
|
|
|
|
try:
|
|
components[name_comp][name_prop]['example']=getattr(f[1], 'jexample')
|
|
except AttributeError:
|
|
components[name_comp][name_prop]['example']=''
|
|
else:
|
|
#components=get_fields(f[1]
|
|
|
|
components[name_comp][name_prop]={}
|
|
components[name_comp][name_prop]['type']='array'
|
|
|
|
name_comp_sub=type(f[1]).__name__
|
|
components[name_comp][name_prop]['object']=name_comp_sub
|
|
|
|
components=get_fields(f[1], name_comp_sub, components)
|
|
|
|
pass
|
|
|
|
return components
|
|
|
|
|
|
#print(self.tags)
|
|
class ApiDoc:
|
|
|
|
def __init__(self, func_api, route, method, summary, description):
|
|
|
|
self.route=route
|
|
self.method=method
|
|
self.summary=summary
|
|
self.description=description
|
|
#self.tag_name=tag_name
|
|
self.operationId="".join([f.capitalize() for f in func_api.__name__.split('_')])
|
|
|
|
self.parameters=[]
|
|
|
|
self.tag=''
|
|
|
|
self.bearer=False
|
|
|
|
self.post={}
|
|
|
|
sign=inspect.signature(func_api)
|
|
|
|
# The return object
|
|
|
|
self.return_obj=None
|
|
|
|
self.component={}
|
|
|
|
#if hasattr(sign, 'return_annotation'):
|
|
if sign.return_annotation.__name__!='_empty':
|
|
self.return_obj=sign.return_annotation.__name__
|
|
#print(self.return_obj)
|
|
args=inspect.getfullargspec(func_api)
|
|
annotations=func_api.__annotations__
|
|
|
|
# The parameters
|
|
|
|
#print(annotations)
|
|
params=sign.parameters
|
|
#print(params)
|
|
|
|
args_len=len(tuple(re.finditer(r'\{(\w+)[:]?.*?\}', route)))
|
|
|
|
#print(args_len)
|
|
z=0
|
|
|
|
for k, f in params.items():
|
|
|
|
if k!='tag' and k!='post' and z<args_len:
|
|
|
|
required='false'
|
|
#print(type(f).__name__)
|
|
if type(f.default).__name__=='type':
|
|
required='true'
|
|
|
|
description=''
|
|
|
|
if k in annotations:
|
|
"""
|
|
for property, value in vars(annotations[k]).items():
|
|
print(property, ":", value)
|
|
"""
|
|
#print(annotations[k].__name__)
|
|
if annotations[k].__name__=='Annotated':
|
|
description=annotations[k].__metadata__[0]
|
|
type_arg=annotations[k].__args__[0].__name__
|
|
else:
|
|
description=k.capitalize()
|
|
type_arg=annotations[k].__name__
|
|
else:
|
|
description=k.capitalize()
|
|
type_arg='string'
|
|
|
|
if type_arg=='int':
|
|
type_arg='integer'
|
|
if type_arg=='str':
|
|
type_arg='string'
|
|
|
|
self.parameters.append({'name': k, 'in': 'path', 'description': description, 'required': required, 'schema': {'type': type_arg}})
|
|
|
|
z+=1
|
|
|
|
|
|
if 'tag' in params:
|
|
self.tag=params['tag'].default
|
|
|
|
if 'bearer' in params:
|
|
self.bearer=True
|
|
|
|
# Set json object if method=post
|
|
|
|
if method=='post' or method=='put':
|
|
#print(params)
|
|
if 'post' in annotations:
|
|
#print(params['post'])
|
|
#print(annotations['post'])
|
|
self.post['description']=annotations['post'].__metadata__[0]
|
|
self.post['content']=annotations['post'].__args__[0].__name__
|
|
|
|
# Add to self.component
|
|
|
|
post_obj=annotations['post'].__args__[0]
|
|
|
|
name_comp=post_obj.__name__
|
|
|
|
self.component[name_comp]={}
|
|
|
|
fields_post=inspect.getmembers(post_obj, lambda a:not(inspect.isroutine(a)))
|
|
|
|
for f in [field for field in fields_post if not field[0].startswith('__')]:
|
|
|
|
name_prop=f[0]
|
|
|
|
self.component[name_comp][name_prop]={}
|
|
self.component[name_comp][name_prop]['type']=getattr(f[1], 'jtype')
|
|
|
|
try:
|
|
self.component[name_comp][name_prop]['format']=getattr(f[1], 'jformat')
|
|
except AttributeError:
|
|
self.component[name_comp][name_prop]['format']=''
|
|
|
|
try:
|
|
self.component[name_comp][name_prop]['example']=getattr(f[1], 'jexample')
|
|
except AttributeError:
|
|
self.component[name_comp][name_prop]['example']=''
|
|
|
|
pass
|
|
|
|
#self.post['description']=
|
|
|
|
"""
|
|
requestBody:
|
|
description: Create a new pet in the store
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Pet'
|
|
"""
|
|
pass
|
|
|
|
#print(get_type_hints(func_api, include_extras=True))
|
|
# api_description=metadata.default['api_description']
|
|
#print(self.parameters)
|
|
#for k,f in params:
|
|
|
|
|
|
#metadata=signature.parameters.get('metadata')
|
|
|
|
self.response={}
|
|
|
|
self.response[200]={'description': 'successful operation'}
|
|
self.response[400]={'description': 'Invalid ID supplied'}
|
|
self.response[404]={'description': 'Element not found'}
|
|
|
|
def add_parameter(self, name, in_type, description, required, type_arg):
|
|
|
|
self.parameters.append({'name': name, 'in': in_type, 'description': description, 'required': required, 'schema': {'type': type_arg}})
|
|
|
|
class JsonObject:
|
|
def toJSON(self):
|
|
return json.dumps(
|
|
self,
|
|
default=lambda o: o.__dict__,
|
|
sort_keys=True,
|
|
indent=4)
|
|
|
|
|
|
class Schema:
|
|
|
|
properties=[]
|
|
|
|
class Property:
|
|
|
|
name=''
|
|
type=''
|
|
format=''
|
|
example=''
|
|
|
|
# If type==array you neeed fill items.
|
|
|
|
items=None
|
|
|
|
"""
|
|
Item:
|
|
type: object
|
|
properties:
|
|
reference:
|
|
type: string
|
|
part_number:
|
|
type: string
|
|
name:
|
|
type: string
|
|
manufacturer:
|
|
type: string
|
|
description:
|
|
type: string
|
|
unit_price:
|
|
type: number
|
|
format: float
|
|
category:
|
|
type: string
|
|
family:
|
|
type: string
|
|
accounting_account:
|
|
type: string
|
|
discount:
|
|
type: number
|
|
format: float
|
|
tax_type:
|
|
type: number
|
|
format: float
|
|
tax_total:
|
|
type: number
|
|
format: float
|
|
subtotal:
|
|
type: number
|
|
format: float
|
|
total:
|
|
type: number
|
|
format: float
|
|
serial_numbers:
|
|
type: array
|
|
items:
|
|
type: string
|
|
"""
|
|
|
|
"""
|
|
% for route in routes:
|
|
'${route['route']}':
|
|
${route['method']}:
|
|
tags:
|
|
- item
|
|
summary: Find item by ID
|
|
description: Returns a single item
|
|
operationId: getItemById
|
|
parameters:
|
|
- name: itemId
|
|
in: path
|
|
description: ID of item to return
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
format: int64
|
|
responses:
|
|
'200':
|
|
description: successful operation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Item'
|
|
'400':
|
|
description: Invalid ID supplied
|
|
'404':
|
|
description: Pet not found
|
|
security:
|
|
- api_key: []
|
|
% endfor
|
|
"""
|