Added files
This commit is contained in:
commit
aaf3c17220
24 changed files with 1614 additions and 0 deletions
442
libraries/apidoc.py
Normal file
442
libraries/apidoc.py
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
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
|
||||
"""
|
||||
Loading…
Add table
Add a link
Reference in a new issue