mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 12:05:53 +00:00
Render API docs (#6463)
* Render API docs
* Cleanup broken links
* Re-enable strict mode
* Change json to yaml
* Update docs/docs/api/schema.md
Co-authored-by: Matthias Mair <code@mjmair.com>
* Update docs/docs/api/schema.md
Co-authored-by: Matthias Mair <code@mjmair.com>
* Use neoteroi-mkdocs instead
- seems to render more reliably
* Fix SERVERS section for SPECTACTULAR_SETTINGS
* Script for splitting schema into smaller sections
* Generate an index file for the schema pages
* Move schema.md up one directory
* Fix formatting
* Remove tracked file
* Add hook for rebuilding API schema as part of RTD build
* Extract schema as RTD build step
* install invoke
* export env vars
* remove argparse
* Fix order of operations
* Compress env vars
* Remove custom env vars
- Now configured as part of RTD project
* Migrate db
* Revert "remove argparse"
This reverts commit 4665805340
.
* Post-process generated schema file
* Fix file formatting
* Add note about schema repo
* no message
* Reduce schema overhead
* Ignore generated files
* Delete generated file
* Update .gitignore
* Add extra split for machine integration
* Remove schema files
- These will be auto-generated too
* Generate individual schema .md files
* Re-add .md files
- Need git commit log to work
* Update .gitignore
* Fix for CI test
* patch machine.api
* Revert previous change
* Formatting fix
* Adjust export step
* Bump API version
---------
Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
232
docs/extract_schema.py
Normal file
232
docs/extract_schema.py
Normal file
@ -0,0 +1,232 @@
|
||||
"""Extract API schema and split into smaller subsections."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
import yaml
|
||||
|
||||
OUTPUT_DIR = './docs/api/schema/'
|
||||
|
||||
# General path
|
||||
GENERAL_PATH = 'general'
|
||||
|
||||
# List of special paths we want to split out
|
||||
SPECIAL_PATHS = {
|
||||
'auth': 'Authorization and Authentication',
|
||||
'background-task': 'Background Task Management',
|
||||
'barcode': 'Barcode Scanning',
|
||||
'bom': 'Bill of Materials',
|
||||
'build': 'Build Order Management',
|
||||
'company': 'Company Management',
|
||||
'label': 'Label Printing',
|
||||
'machine': 'External Machine Management',
|
||||
'order': 'External Order Management',
|
||||
'part': 'Parts and Part Categories',
|
||||
'plugins': 'Plugin Functionality',
|
||||
'report': 'Report Generation',
|
||||
'settings': 'Settings Management',
|
||||
'stock': 'Stock and Stock Locations',
|
||||
'user': 'User Management',
|
||||
}
|
||||
|
||||
|
||||
def top_level_path(path: str) -> str:
|
||||
"""Return the top level path of the input path."""
|
||||
path = path.strip()
|
||||
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
if path.endswith('/'):
|
||||
path = path[:-1]
|
||||
|
||||
path = path.strip()
|
||||
|
||||
key = path.split('/')[1]
|
||||
|
||||
if key in SPECIAL_PATHS.keys():
|
||||
return key
|
||||
|
||||
return GENERAL_PATH
|
||||
|
||||
|
||||
def generate_schema_file(key: str) -> None:
|
||||
"""Generate a schema file for the provided key."""
|
||||
description = (
|
||||
SPECIAL_PATHS[key] if key in SPECIAL_PATHS else 'General API Endpoints'
|
||||
)
|
||||
|
||||
output = f"""
|
||||
---
|
||||
title: {description}
|
||||
---
|
||||
|
||||
The *{description}* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/{key}.yml)]
|
||||
"""
|
||||
|
||||
output = textwrap.dedent(output).strip() + '\n'
|
||||
|
||||
output_file = os.path.join(os.path.dirname(__file__), OUTPUT_DIR, f'{key}.md')
|
||||
output_file = os.path.abspath(output_file)
|
||||
|
||||
print('Writing schema file to:', output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
def generate_index_file(version: str):
|
||||
"""Generate the index file for the API schema."""
|
||||
output = f"""
|
||||
---
|
||||
title: InvenTree API Schema
|
||||
---
|
||||
|
||||
The InvenTree API is implemented using the [Django REST framework](https://www.django-rest-framework.org).
|
||||
The API schema as documented below is generated using the [drf-spectactular](https://github.com/tfranzel/drf-spectacular/) extension.
|
||||
|
||||
## API Version
|
||||
|
||||
This documentation is for API version: `{version}`
|
||||
|
||||
!!! tip "API Schema History"
|
||||
We track API schema changes, and provide a snapshot of each API schema version in the [API schema repository](https://github.com/inventree/schema/).
|
||||
|
||||
## API Schema File
|
||||
|
||||
The API schema file is available for download, and can be used for generating client libraries, or for testing API endpoints.
|
||||
|
||||
## API Schema Documentation
|
||||
|
||||
API schema documentation is split into the following categories:
|
||||
|
||||
| Category | Description |
|
||||
| --- | --- |
|
||||
"""
|
||||
|
||||
output = textwrap.dedent(output).strip() + '\n'
|
||||
|
||||
for key, value in SPECIAL_PATHS.items():
|
||||
output += f'| [{value}](./schema/{key}.md) | {value} |\n'
|
||||
|
||||
output += '| [General](./schema/general.md) | General API endpoints |\n'
|
||||
|
||||
output += '\n'
|
||||
|
||||
output_file = os.path.join(os.path.dirname(__file__), OUTPUT_DIR, '..', 'schema.md')
|
||||
|
||||
print('Writing index file to:', output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
def extract_refs(data: dict, components: dict) -> list:
|
||||
"""Extract a list of refs from the provided paths dict.
|
||||
|
||||
The refs are located like so:
|
||||
<path>:<method>:responses:<status>:content:application/json:schema:$ref
|
||||
|
||||
Also, the referenced components might reference *sub* components,
|
||||
so we need to do this step recursively.
|
||||
|
||||
"""
|
||||
pattern = r"'?\$ref'?: '#\/components\/schemas\/(\S+)'"
|
||||
|
||||
# First, extract the results from the paths data dict
|
||||
matches = re.findall(pattern, str(data))
|
||||
|
||||
refs = list(matches)
|
||||
|
||||
# Next, extract additional refs from the components dict
|
||||
# Note that the components dict may reference other components
|
||||
|
||||
idx = 0
|
||||
|
||||
while idx < len(refs):
|
||||
ref = refs[idx]
|
||||
|
||||
schema = components.get('schemas', {}).get(ref, None)
|
||||
|
||||
if schema:
|
||||
# Search for additional refs
|
||||
matches = re.findall(pattern, str(schema))
|
||||
|
||||
for match in matches:
|
||||
if match not in refs:
|
||||
refs.append(match)
|
||||
|
||||
idx += 1
|
||||
|
||||
# Return a dict only of the extracted refs
|
||||
schemas = {ref: components['schemas'][ref] for ref in refs}
|
||||
|
||||
return schemas
|
||||
|
||||
|
||||
def parse_api_file(filename: str):
|
||||
"""Parse the input API file, and split into smaller sections.
|
||||
|
||||
The intent is to make the API schema easier to peruse on the documentation.
|
||||
"""
|
||||
with open(filename, 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
paths = data['paths']
|
||||
|
||||
top_level_paths = {}
|
||||
|
||||
for path, methods in paths.items():
|
||||
tlp = top_level_path(path)
|
||||
|
||||
if tlp not in top_level_paths:
|
||||
top_level_paths[tlp] = {}
|
||||
|
||||
top_level_paths[tlp][path] = methods
|
||||
|
||||
# Generate output files
|
||||
for key, value in top_level_paths.items():
|
||||
output_file = os.path.join(os.path.dirname(__file__), OUTPUT_DIR, f'{key}.yml')
|
||||
|
||||
output = {}
|
||||
|
||||
output['paths'] = value
|
||||
|
||||
components = data.get('components', {})
|
||||
|
||||
# Extract only the schemas relating to the provided paths
|
||||
path_schemas = extract_refs(value, components)
|
||||
|
||||
for k, v in data.items():
|
||||
if k == 'components':
|
||||
v = v.copy()
|
||||
v['schemas'] = path_schemas
|
||||
|
||||
if k != 'paths':
|
||||
output[k] = v
|
||||
|
||||
print(f'Writing schema file to {output_file}...')
|
||||
|
||||
output_file = os.path.abspath(output_file)
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
yaml.dump(output, f)
|
||||
|
||||
# Generate a markdown file for the schema
|
||||
generate_schema_file(key)
|
||||
|
||||
# Finally, generate an index file for the API schema
|
||||
generate_index_file(data['info']['version'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('input', help='Input API schema file (.yml)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
parse_api_file(args.input)
|
Reference in New Issue
Block a user