mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-14 12:32:44 +00:00
[docs] Zensical docs (#13253)
* add zensical workflow * rebuild schema dynamically * load in new module * Use zensical for docs invoke tasks * modern * improved carousel layout * Increased content width * fix for app icons * Ensure schema dir exists * remove unused requirement * update readthedocs process * fix polar.sh link * adjust link checker pattern * Revert some of the original style * remove legacy mkdocs requirements * fix for ready.py * reduce db warnings during schema generation * Adjust padding * fix index issues * fix page title * fix pages with broken or missing headers * split data import API schema * cleanup old carousel * remove djlint link
This commit is contained in:
1 parent
80ad822688
commit
19dfb81bfb
52 files changed
+1272
-4885
No files matched your search
@@ -53,7 +53,7 @@
|
||||
"label": "InvenTree Server"
|
||||
},
|
||||
"8080": {
|
||||
"label": "properdocs server"
|
||||
"label": "Documentation server"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
pip install --require-hashes --only-binary :all: -r docs/requirements.txt
|
||||
- name: Build documentation
|
||||
run: |
|
||||
invoke build-docs --mkdocs
|
||||
invoke build-docs --build
|
||||
- name: Zip build docs
|
||||
run: |
|
||||
cd docs/site
|
||||
|
||||
@@ -12,6 +12,7 @@ site/
|
||||
|
||||
# Generated API schema files
|
||||
docs/api/schema/*.yml
|
||||
docs/api/schema/*.md
|
||||
|
||||
# Temporary cache files
|
||||
url_cache.txt
|
||||
|
||||
+15
-9
@@ -2,11 +2,11 @@
|
||||
|
||||
[](https://inventree.readthedocs.io/en/latest/?badge=latest)
|
||||
|
||||
This repository hosts the [official documentation](https://inventree.readthedocs.io/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system.
|
||||
This repository hosts the [official documentation](https://docs.inventree.org/) for [InvenTree](https://github.com/inventree/inventree), an open source inventory management system.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
InvenTree uses [MkDocs](https://www.mkdocs.org/) to convert [Markdown](https://www.mkdocs.org/user-guide/writing-your-docs/#writing-with-markdown) format `.md` files into HTML suitable for viewing in a web browser.
|
||||
InvenTree uses [Zensical](https://zensical.org/) to convert [Markdown](https://zensical.org/docs/authoring/markdown/) format `.md` files into HTML suitable for viewing in a web browser. The site configuration is in `docs/mkdocs.yml` (Zensical reads the mkdocs configuration format).
|
||||
|
||||
!!! info "Prerequisites"
|
||||
To build and serve this documentation locally (e.g. for development), you will need:
|
||||
@@ -31,7 +31,7 @@ $ source env/bin/activate
|
||||
!!! info "(env) prefix"
|
||||
The shell prompt should now display the `(env)` prefix, showing that you are operating within the context of the python virtual environment
|
||||
|
||||
You can now install the additional packages needed by mkdocs:
|
||||
You can now install the additional packages needed to build the documentation:
|
||||
|
||||
```
|
||||
$ cd src
|
||||
@@ -40,7 +40,7 @@ $ pip install --require-hashes -r docs/requirements.txt
|
||||
|
||||
## Schema generation
|
||||
|
||||
Building the documentation requires extracting the API schema from the source code.
|
||||
Building the documentation requires extracting the API schema (and settings definitions) from the source code. The API reference pages in `docs/docs/api/schema/` are generated from this schema and are not tracked in git.
|
||||
|
||||
!!! tip
|
||||
This command is only required when building the documentation for the first time, or when changes have been made to the API schema.
|
||||
@@ -62,15 +62,21 @@ Exporting definitions complete
|
||||
Exporting schema file to '/home/inventree/src/docs/generated/schema.yml'
|
||||
|
||||
Schema export completed: /home/inventree/src/docs/generated/schema.yml
|
||||
Documentation build complete, but properdocs not requested
|
||||
Documentation build complete, but zensical build not requested
|
||||
```
|
||||
|
||||
To generate the schema files *and* build the static HTML site (in `docs/site/`), including the post-build checks that all settings are documented, run:
|
||||
|
||||
```
|
||||
$ invoke build-docs --build
|
||||
```
|
||||
|
||||
## Viewing the documentation
|
||||
|
||||
Generate the HTML files from the markdown source files, and start the properdocs webpage server:
|
||||
Generate the HTML files from the markdown source files, and start the zensical webpage server:
|
||||
|
||||
```
|
||||
$ properdocs serve -f docs/mkdocs.yml
|
||||
$ zensical serve -a localhost:8080 -f docs/mkdocs.yml
|
||||
```
|
||||
|
||||
You can then point your web browser at http://localhost:8080/
|
||||
@@ -81,7 +87,7 @@ Alternatively, you can use the `invoke` command:
|
||||
$ invoke dev.docs-server
|
||||
```
|
||||
|
||||
If you need to, use the `-a` option after `properdocs` or `invoke` to set the address and port. Run `invoke dev.docs-server --help` for details.
|
||||
If you need to, use the `-a` option after `zensical serve` or `invoke` to set the address and port. Run `invoke dev.docs-server --help` for details.
|
||||
|
||||
## Editing the Documentation Files
|
||||
|
||||
@@ -146,4 +152,4 @@ Global variables should be added in the `# Global Variables` section of the `mkd
|
||||
|
||||
## Credits
|
||||
|
||||
This documentation makes use of the [mkdocs-material template](https://github.com/squidfunk/mkdocs-material)
|
||||
This documentation is built with [Zensical](https://zensical.org/), from the team behind [Material for MkDocs](https://github.com/squidfunk/mkdocs-material)
|
||||
@@ -11,22 +11,40 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener( 'DOMContentLoaded', function () {
|
||||
new Splide(
|
||||
// With navigation.instant, page content is swapped in without a new
|
||||
// DOMContentLoaded event, so mount immediately if the document has loaded
|
||||
(function () {
|
||||
function mountCarousel() {
|
||||
|
||||
if (window.carouselInstance) {
|
||||
window.carouselInstance.destroy();
|
||||
}
|
||||
window.carouselInstance = new Splide(
|
||||
'.splide', {
|
||||
type: 'loop',
|
||||
perPage: {{ per_page }},
|
||||
autoplay: true,
|
||||
pagination: false,
|
||||
// cover: true,
|
||||
// autoWidth: true,
|
||||
height: '25rem',
|
||||
// Each slide is as wide as its image, so that all images
|
||||
// are scaled to the same height (set in extra.css)
|
||||
autoWidth: true,
|
||||
height: '{{ height | default("14rem") }}',
|
||||
breakpoints: {
|
||||
// Narrow screens (e.g. mobile)
|
||||
700: {
|
||||
height: '{{ mobile_height | default("8rem") }}',
|
||||
},
|
||||
},
|
||||
gap: '1rem',
|
||||
focus: 'center',
|
||||
padding: {
|
||||
left: '5rem',
|
||||
right: '5rem',
|
||||
}
|
||||
}
|
||||
).mount();
|
||||
} );
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', mountCarousel);
|
||||
} else {
|
||||
mountCarousel();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -20,3 +20,7 @@ To export API data to a file, add the `&export=<format>` modifier to the query.
|
||||
Any other query filters used in the API request are also observed when downloading the data. For example, to download a list of all stock items in a given location:
|
||||
|
||||
`<host>/api/stock/?format=csv&location=10`
|
||||
|
||||
### Further Information
|
||||
|
||||
For further information, refer to the [data export documentation](../concepts/data_export.md).
|
||||
@@ -2,12 +2,14 @@
|
||||
title: InvenTree API Schema
|
||||
---
|
||||
|
||||
## 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: `568`
|
||||
This documentation is for API version: `578`
|
||||
|
||||
!!! 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/).
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Authorization and Authentication
|
||||
---
|
||||
|
||||
The *Authorization and Authentication* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/auth.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Background Task Management
|
||||
---
|
||||
|
||||
The *Background Task Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/background-task.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Barcode Scanning
|
||||
---
|
||||
|
||||
The *Barcode Scanning* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/barcode.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Bill of Materials
|
||||
---
|
||||
|
||||
The *Bill of Materials* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/bom.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Build Order Management
|
||||
---
|
||||
|
||||
The *Build Order Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/build.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Company Management
|
||||
---
|
||||
|
||||
The *Company Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/company.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: General API Endpoints
|
||||
---
|
||||
|
||||
The *General API Endpoints* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/general.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Label Printing
|
||||
---
|
||||
|
||||
The *Label Printing* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/label.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: External Machine Management
|
||||
---
|
||||
|
||||
The *External Machine Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/machine.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: External Order Management
|
||||
---
|
||||
|
||||
The *External Order Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/order.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Parts and Part Categories
|
||||
---
|
||||
|
||||
The *Parts and Part Categories* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/part.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Plugin Functionality
|
||||
---
|
||||
|
||||
The *Plugin Functionality* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/plugins.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Part Pricing
|
||||
---
|
||||
|
||||
The *Part Pricing* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/pricing.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Report Generation
|
||||
---
|
||||
|
||||
The *Report Generation* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/report.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Settings Management
|
||||
---
|
||||
|
||||
The *Settings Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/settings.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Stock and Stock Locations
|
||||
---
|
||||
|
||||
The *Stock and Stock Locations* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/stock.yml)]
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: User Management
|
||||
---
|
||||
|
||||
The *User Management* section of the InvenTree API schema is documented below.
|
||||
|
||||
[OAD(./docs/docs/api/schema/user.yml)]
|
||||
@@ -2,14 +2,16 @@
|
||||
title: InvenTree Mobile App
|
||||
---
|
||||
|
||||
{% with directory="appgallery", per_page=2 %}
|
||||
## InvenTree Mobile App
|
||||
|
||||
The InvenTree Mobile App brings stock control to your pocket. Integrating seamlessly with the [InvenTree API](../api/index.md), the app provides immediate access to inventory data without requiring physical access to a computer.
|
||||
|
||||
{% with directory="appgallery", per_page=2, height="25rem", mobile_height="25rem" %}
|
||||
{% include "carousel.html" %}
|
||||
{% endwith %}
|
||||
|
||||
-----
|
||||
|
||||
The InvenTree Mobile App brings stock control to your pocket. Integrating seamlessly with the [InvenTree API](../api/index.md), the app provides immediate access to inventory data without requiring physical access to a computer.
|
||||
|
||||
Native barcode support provides a multitude of context-sensitive stock control actions, allowing streamlined inventory management at your fingertips. The app has been optimized for speed, providing instant access to stock knowledge and handy on-site functionality.
|
||||
|
||||
## Features
|
||||
@@ -29,7 +31,9 @@ The InvenTree app can be downloaded from either the Android or Apple app stores,
|
||||
<span class='fab fa-android'></span> [Android Play Store](https://play.google.com/store/apps/details?id=inventree.inventree_app).
|
||||
|
||||
!!! info "APK Download"
|
||||
As an alternative to using the Android Play Store, the InvenTree app APK file can be downloaded directly via our [polar.sh](https://polar.sh/inventree/products/299bf0d5-af88-4e0f-becf-c007ad37ecf2) page.
|
||||
As an alternative to using the Android Play Store, the InvenTree app APK file can be downloaded directly via our [polar.sh](https://buy.polar.sh/polar_cl_UnGILJ0c7P3hQrOrJs127oyLTTDOTHKrnqfCg30XtBI) page.
|
||||
|
||||
|
||||
|
||||
### iOS
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
title: Companies
|
||||
---
|
||||
|
||||
|
||||
## Companies
|
||||
|
||||
External companies are represented by the *Company* database model. Each company may be classified into the following categories:
|
||||
|
||||
@@ -3,9 +3,9 @@ title: Contribution Guide
|
||||
---
|
||||
|
||||
|
||||
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase.
|
||||
## Contribution Guide
|
||||
|
||||
## Quickstart
|
||||
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase.
|
||||
|
||||
The following commands will get you quickly configure and run a development server, complete with a demo dataset to work with:
|
||||
|
||||
@@ -281,7 +281,7 @@ If you have followed the setup devtools procedure, then code style checking is p
|
||||
|
||||
Django are checked by [djlint](https://github.com/Riverside-Healthcare/djlint) through pre-commit.
|
||||
|
||||
The following rules out of the [default set](https://djlint.com/docs/linter/) are not applied:
|
||||
The following rules out of the default set of djlint rules are not applied:
|
||||
```bash
|
||||
D018: (Django) Internal links should use the { % url ... % } pattern
|
||||
H006: Img tag should have height and width attributes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: React Frontend Development
|
||||
---
|
||||
|
||||
## Setup
|
||||
## React Frontend Development
|
||||
|
||||
The following documentation details how to setup and run a development installation of the InvenTree frontend user interface. It is assumed that you are using the [InvenTree devcontainer setup](./devcontainer.md).
|
||||
|
||||
|
||||
@@ -6,16 +6,6 @@ title: High level roadmap
|
||||
|
||||
## High level Epics
|
||||
|
||||
### 1.0
|
||||
|
||||
Smaller items can be viewed [on the milestone](https://github.com/inventree/InvenTree/issues?q=is%3Aissue%20milestone%3A1.0.0).
|
||||
|
||||
Aiming to stabelise several aspects of the software:
|
||||
|
||||
- Only shipping the new frontend and removing reliance on templating
|
||||
- Stabelize API for client generation
|
||||
- Making data import- / export-mechanisms more stable
|
||||
- Updating and Re-Organising documentation to enable CII best practices compliance
|
||||
|
||||
### 2.0
|
||||
|
||||
@@ -29,7 +19,3 @@ Smaller items can be viewed [on the milestone](https://github.com/inventree/Inve
|
||||
### Future
|
||||
|
||||
There are several epics that target [the horizion](https://github.com/inventree/InvenTree/issues?q=is%3Aissue%20state%3Aopen%20type%3AEpic).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
TBD
|
||||
+6
-1
@@ -1,4 +1,9 @@
|
||||
"""Custom mkdocs hooks, using the mkdocs-simple-hooks plugin."""
|
||||
"""Documentation build hooks.
|
||||
|
||||
Zensical does not run mkdocs hooks, so these are called directly:
|
||||
- on_config: via docs/docs_data.py (hook_config)
|
||||
- on_post_build: via the "invoke build-docs --build" task
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
Vendored
-2607
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,6 @@
|
||||
|
||||
## InvenTree Project Governance
|
||||
|
||||
As a MIT licensed open-source project, there are not legal obligations for the InvenTree project team or any contributor to provide support or maintenance. The software is provided "as-is" - without warranty of any kind.
|
||||
However, the project team is committed to providing a reliable and [secure](../security.md) code base with predictable upgrade paths.
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: Sponsored Resources
|
||||
---
|
||||
|
||||
## Sponsored Resources
|
||||
|
||||
The InvenTree project is grateful to receive resources from various vendors free of charge.
|
||||
|
||||
Individuals and companies can also support via [GitHub sponsors](https://github.com/sponsors/inventree).
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: Helper Functions
|
||||
---
|
||||
|
||||
## Report Helper Functions
|
||||
|
||||
Some common functions are provided for use in custom report and label templates. To include these, load the `report` functions at the start of the template:
|
||||
|
||||
```html
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
---
|
||||
title: Sales Orders
|
||||
---
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
## InvenTree Security Overview
|
||||
|
||||
The InvenTree project is committed to providing a secure and safe environment for all users. We know that many of our users rely on InvenTree to manage the inventory and manufacturing for their small and mid-size businesses, and we take that responsibility seriously.
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: Experimental Features
|
||||
---
|
||||
|
||||
## Feature Flags
|
||||
|
||||
InvenTree ships with django-flags and enables path (parameter), user, session, date or settings based feature flags. This allows admins to slowly test and roll out new features on their instance without running parallel instances.
|
||||
|
||||
Additional flags can be provided via the the `INVENTREE_FLAGS` environment key (see [configuration](../start/config.md#environment-variables)).
|
||||
|
||||
Superusers can configure run-time conditions [as per django-flags](https://cfpb.github.io/django-flags/conditions/) docs under `/admin/flags/flagstate/`.
|
||||
|
||||
## Current Experimental Features
|
||||
|
||||
| Feature | Key | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
---
|
||||
title: Transfer Orders
|
||||
---
|
||||
|
||||
@@ -68,12 +68,20 @@ p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
div:nth-of-type(1) p {
|
||||
div:nth-of-type(1) p:not(.admonition-title) {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
/* Remove page title */
|
||||
.md-content h1:first-child {
|
||||
/* Use the theme code font (bootstrap.css otherwise overrides it) */
|
||||
.md-typeset code,
|
||||
.md-typeset kbd,
|
||||
.md-typeset pre {
|
||||
font-family: var(--md-code-font-family);
|
||||
}
|
||||
|
||||
/* Remove page title (Zensical inserts a skip-link anchor before the h1) */
|
||||
.md-content h1:first-child,
|
||||
.md-content #__skip + h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -154,3 +162,84 @@ div:nth-of-type(1) p {
|
||||
background-color: #5bc0de;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* HTTP method badges for the generated API schema documentation */
|
||||
.api-method {
|
||||
display: inline-block;
|
||||
min-width: 4.2em;
|
||||
padding: 0.1em 0.5em;
|
||||
border-radius: 0.2rem;
|
||||
color: #fff;
|
||||
font-family: var(--md-code-font-family);
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.api-method-get { background-color: #2b8a3e; }
|
||||
.api-method-post { background-color: #1971c2; }
|
||||
.api-method-put { background-color: #e67700; }
|
||||
.api-method-patch { background-color: #9c36b5; }
|
||||
.api-method-delete { background-color: #c92a2a; }
|
||||
.api-method-head, .api-method-options { background-color: #495057; }
|
||||
|
||||
/* Image carousel: scale every image to the slide height, keeping aspect ratio */
|
||||
.splide__slide {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.splide__slide img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
/* Very wide images shrink to fit the carousel on narrow screens */
|
||||
max-width: calc(100vw - 2rem);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Widen the page layout (theme default is 61rem), and keep ~5% horizontal
|
||||
* padding so the content does not run to the window edges */
|
||||
.md-grid {
|
||||
max-width: 80rem;
|
||||
padding-left: 5%;
|
||||
padding-right: 5%;
|
||||
}
|
||||
|
||||
/* ...but not for grids nested inside page content (e.g. the home page hero) */
|
||||
.md-content .md-grid {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/* Solid primary-colour header and navigation tabs, as in the classic theme.
|
||||
* (The Zensical "modern" variant uses a translucent neutral header.) */
|
||||
.md-header,
|
||||
.md-tabs {
|
||||
background-color: var(--md-primary-fg-color);
|
||||
color: var(--md-primary-bg-color);
|
||||
}
|
||||
|
||||
.md-header {
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
/* Search button on the coloured header (darker blue box, white text) */
|
||||
.md-header .md-search__button {
|
||||
background-color: rgba(0, 0, 0, 0.26);
|
||||
color: var(--md-primary-bg-color);
|
||||
}
|
||||
|
||||
.md-header .md-search__button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
.md-header .md-search__button::before {
|
||||
background-color: var(--md-primary-bg-color);
|
||||
}
|
||||
|
||||
.md-header .md-search__button::after {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
color: var(--md-primary-bg-color);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Font Awesome Free 5.13.0 - SVG with JS stylesheet (https://fontawesome.com)
|
||||
* License - https://fontawesome.com/license/free (Code: MIT License)
|
||||
*
|
||||
* fontawesome.js normally injects this into <head> at runtime, but with
|
||||
* navigation.instant the head is replaced on page change and it is lost.
|
||||
*/
|
||||
svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;overflow:visible;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;-webkit-box-sizing:border-box;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top left;transform-origin:top left}.fa-lg{font-size:1.3333333333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-Line truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,194 @@
|
||||
"""Shared data for the documentation build process.
|
||||
|
||||
This module loads the generated InvenTree definition files (settings, status codes,
|
||||
report context, etc) which are used by the custom macros defined in 'main.py'.
|
||||
|
||||
It is deliberately separate from 'main.py', as some static site generators
|
||||
(e.g. zensical) re-execute the macros module for *every* page. As a regular
|
||||
module, this file is imported (and cached) only once per build process:
|
||||
|
||||
- Generated data files are loaded lazily, and reloaded only if they change on disk
|
||||
- Observed settings / status codes are accumulated across all pages of the build
|
||||
- The 'on_config' hook values are computed once (for generators without hook support)
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
GENERATED_DIR = HERE.joinpath('generated')
|
||||
|
||||
# Files where we *store* information on what has been documented during the build.
|
||||
# These are checked after the build completes (see 'on_post_build' in docs/hooks.py)
|
||||
OBSERVED_SETTINGS_FILE = GENERATED_DIR.joinpath('observed_settings.json')
|
||||
OBSERVED_STATUS_CODES_FILE = GENERATED_DIR.joinpath('observed_status_codes.json')
|
||||
|
||||
_lock = threading.RLock()
|
||||
|
||||
# Cache of loaded data files: {filename: (mtime, data)}
|
||||
_file_cache: dict[str, tuple[float, Any]] = {}
|
||||
|
||||
# Things which have been rendered somewhere in the documentation
|
||||
_observed_settings: dict[str, dict[str, bool]] = {'global': {}, 'user': {}}
|
||||
_observed_status_codes: dict[str, bool] = {}
|
||||
|
||||
# Cached result of the 'on_config' hook
|
||||
_hook_config: Optional[dict] = None
|
||||
|
||||
|
||||
def print_build_environment():
|
||||
"""Print some useful debugging information - useful for diagnosing CI build issues.
|
||||
|
||||
Ref: https://docs.readthedocs.io/en/stable/reference/environment-variables.html
|
||||
"""
|
||||
print('loading ./docs/docs_data.py...')
|
||||
|
||||
for key in [
|
||||
'GITHUB_ACTIONS',
|
||||
'GITHUB_REF',
|
||||
'READTHEDOCS',
|
||||
'READTHEDOCS_GIT_IDENTIFIER',
|
||||
'READTHEDOCS_GIT_CLONE_URL',
|
||||
'READTHEDOCS_GIT_COMMIT_HASH',
|
||||
'READTHEDOCS_PROJECT',
|
||||
'READTHEDOCS_VERSION',
|
||||
'READTHEDOCS_VERSION_NAME',
|
||||
'READTHEDOCS_VERSION_TYPE',
|
||||
]:
|
||||
val = os.environ.get(key, None) or '-- MISSING --'
|
||||
print(f' - {key}: {val}')
|
||||
|
||||
|
||||
def _write_json(path: Path, data: Any):
|
||||
"""Write data to a JSON file."""
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
|
||||
def reset_observations():
|
||||
"""Reset the record of observed settings and status codes.
|
||||
|
||||
Called once when this module is first imported (i.e. once per build process).
|
||||
"""
|
||||
with _lock:
|
||||
_observed_settings.clear()
|
||||
_observed_settings.update({'global': {}, 'user': {}})
|
||||
_observed_status_codes.clear()
|
||||
|
||||
_write_json(OBSERVED_SETTINGS_FILE, _observed_settings)
|
||||
_write_json(OBSERVED_STATUS_CODES_FILE, _observed_status_codes)
|
||||
|
||||
|
||||
def observe_setting(key: str, group: str):
|
||||
"""Record that a particular setting has been documented.
|
||||
|
||||
Arguments:
|
||||
key: The name of the setting
|
||||
group: The group of the setting (e.g. 'global', 'user', 'config')
|
||||
"""
|
||||
with _lock:
|
||||
group_data = _observed_settings.setdefault(group, {})
|
||||
|
||||
if key not in group_data:
|
||||
group_data[key] = True
|
||||
_write_json(OBSERVED_SETTINGS_FILE, _observed_settings)
|
||||
|
||||
|
||||
def observe_status_code(class_name: str):
|
||||
"""Record that a particular status code class has been documented."""
|
||||
with _lock:
|
||||
if class_name not in _observed_status_codes:
|
||||
_observed_status_codes[class_name] = True
|
||||
_write_json(OBSERVED_STATUS_CODES_FILE, _observed_status_codes)
|
||||
|
||||
|
||||
def load_file(filename: str) -> Any:
|
||||
"""Load a generated data file (JSON or YAML), caching the result.
|
||||
|
||||
The file is reloaded only if it has been modified since it was last loaded.
|
||||
|
||||
Arguments:
|
||||
filename: Name of the file, relative to the 'generated' directory
|
||||
"""
|
||||
path = GENERATED_DIR.joinpath(filename)
|
||||
mtime = path.stat().st_mtime
|
||||
|
||||
with _lock:
|
||||
cached = _file_cache.get(filename)
|
||||
|
||||
if cached and cached[0] == mtime:
|
||||
return cached[1]
|
||||
|
||||
with open(path, encoding='utf-8') as f:
|
||||
if path.suffix in ('.yml', '.yaml'):
|
||||
data = yaml.load(f, yaml.BaseLoader)
|
||||
else:
|
||||
data = json.load(f)
|
||||
|
||||
_file_cache[filename] = (mtime, data)
|
||||
return data
|
||||
|
||||
|
||||
def settings(group: str) -> dict:
|
||||
"""Return the settings definitions for a given group ('global', 'user', 'config')."""
|
||||
return load_file('inventree_settings.json')[group]
|
||||
|
||||
|
||||
def tags() -> Any:
|
||||
"""Return the custom template tags definitions."""
|
||||
return load_file('inventree_tags.yml')
|
||||
|
||||
|
||||
def filters() -> Any:
|
||||
"""Return the custom template filters definitions."""
|
||||
return load_file('inventree_filters.yml')
|
||||
|
||||
|
||||
def status_codes() -> dict:
|
||||
"""Return the status code class definitions."""
|
||||
return load_file('inventree_status_codes.json')
|
||||
|
||||
|
||||
def report_context() -> dict:
|
||||
"""Return the report context definitions."""
|
||||
return load_file('inventree_report_context.json')
|
||||
|
||||
|
||||
def roles() -> Any:
|
||||
"""Return the user permission role definitions."""
|
||||
return load_file('inventree_roles.json')
|
||||
|
||||
|
||||
def audited_models() -> Any:
|
||||
"""Return the audited model definitions."""
|
||||
return load_file('inventree_audited_models.json')
|
||||
|
||||
|
||||
def hook_config(site_url: Optional[str] = None) -> dict:
|
||||
"""Return the config values provided by the 'on_config' hook in docs/hooks.py.
|
||||
|
||||
Zensical does not run mkdocs hooks, so
|
||||
the hook is run once here, and the result cached for subsequent pages.
|
||||
"""
|
||||
global _hook_config
|
||||
|
||||
with _lock:
|
||||
if _hook_config is None:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
'inventree_docs_hooks', HERE.joinpath('docs', 'hooks.py')
|
||||
)
|
||||
hooks = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(hooks)
|
||||
_hook_config = hooks.on_config({'site_url': site_url})
|
||||
|
||||
return _hook_config
|
||||
|
||||
|
||||
print_build_environment()
|
||||
reset_observations()
|
||||
+15
-94
@@ -2,10 +2,10 @@
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
import yaml
|
||||
from render_schema import SchemaRenderer
|
||||
|
||||
OUTPUT_DIR = './docs/api/schema/'
|
||||
|
||||
@@ -20,12 +20,13 @@ SPECIAL_PATHS = {
|
||||
'bom': 'Bill of Materials',
|
||||
'build': 'Build Order Management',
|
||||
'company': 'Company Management',
|
||||
'importer': 'Data Importing',
|
||||
'label': 'Label Printing',
|
||||
'machine': 'External Machine Management',
|
||||
'order': 'External Order Management',
|
||||
'part': 'Parts and Part Categories',
|
||||
'plugins': 'Plugin Functionality',
|
||||
'pricing': 'Part Pricing',
|
||||
'pricing': 'Part and Stock Pricing',
|
||||
'report': 'Report Generation',
|
||||
'settings': 'Settings Management',
|
||||
'stock': 'Stock and Stock Locations',
|
||||
@@ -54,27 +55,20 @@ def top_level_path(path: str) -> str:
|
||||
return GENERAL_PATH
|
||||
|
||||
|
||||
def generate_schema_file(key: str) -> None:
|
||||
"""Generate a schema file for the provided key."""
|
||||
def generate_schema_file(key: str, renderer: SchemaRenderer, paths: dict) -> None:
|
||||
"""Render the API documentation page for the provided key."""
|
||||
description = SPECIAL_PATHS.get(key, '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 = renderer.render_page(description, paths)
|
||||
|
||||
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)
|
||||
|
||||
# Generated pages are not tracked in git, so the directory may not exist yet
|
||||
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(output)
|
||||
|
||||
@@ -125,99 +119,26 @@ def generate_index_file(version: str):
|
||||
f.write(output)
|
||||
|
||||
|
||||
def extract_refs(data: dict, components: dict) -> dict:
|
||||
"""Extract a dict 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.
|
||||
"""Parse the input API file, and render it as a set of smaller documentation pages.
|
||||
|
||||
The intent is to make the API schema easier to peruse on the documentation.
|
||||
"""
|
||||
with open(filename, encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
paths = data['paths']
|
||||
|
||||
top_level_paths = {}
|
||||
|
||||
for path, methods in paths.items():
|
||||
for path, methods in data['paths'].items():
|
||||
tlp = top_level_path(path)
|
||||
top_level_paths.setdefault(tlp, {})[path] = methods
|
||||
|
||||
if tlp not in top_level_paths:
|
||||
top_level_paths[tlp] = {}
|
||||
|
||||
top_level_paths[tlp][path] = methods
|
||||
# The renderer uses the complete schema, so all references can be resolved
|
||||
renderer = SchemaRenderer(data)
|
||||
|
||||
# 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', encoding='utf-8') as f:
|
||||
yaml.dump(output, f)
|
||||
|
||||
# Generate a markdown file for the schema
|
||||
generate_schema_file(key)
|
||||
generate_schema_file(key, renderer, value)
|
||||
|
||||
# Finally, generate an index file for the API schema
|
||||
generate_index_file(data['info']['version'])
|
||||
|
||||
+40
-127
@@ -1,9 +1,8 @@
|
||||
"""Main entry point for the documentation build process."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Literal, Optional
|
||||
@@ -11,89 +10,17 @@ from typing import Literal, Optional
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
# Debugging output - useful for diagnosing CI build issues
|
||||
print('loading ./docs/main.py...')
|
||||
|
||||
logging.getLogger('openapidocs').setLevel(logging.ERROR)
|
||||
|
||||
# Print out some useful debugging information
|
||||
# Ref: https://docs.readthedocs.io/en/stable/reference/environment-variables.html
|
||||
for key in [
|
||||
'GITHUB_ACTIONS',
|
||||
'GITHUB_REF',
|
||||
'READTHEDOCS',
|
||||
'READTHEDOCS_GIT_IDENTIFIER',
|
||||
'READTHEDOCS_GIT_CLONE_URL',
|
||||
'READTHEDOCS_GIT_COMMIT_HASH',
|
||||
'READTHEDOCS_PROJECT',
|
||||
'READTHEDOCS_VERSION',
|
||||
'READTHEDOCS_VERSION_NAME',
|
||||
'READTHEDOCS_VERSION_TYPE',
|
||||
]:
|
||||
val = os.environ.get(key, None) or '-- MISSING --'
|
||||
print(f' - {key}: {val}')
|
||||
|
||||
# Cached settings dict values
|
||||
global CONFIG_SETTINGS
|
||||
global GLOBAL_SETTINGS
|
||||
global USER_SETTINGS
|
||||
global TAGS
|
||||
global FILTERS
|
||||
global REPORT_CONTEXT
|
||||
global STATUS_CODES
|
||||
global ROLES
|
||||
|
||||
# Read in the InvenTree settings file
|
||||
here = Path(__file__).parent
|
||||
|
||||
gen_base = here.joinpath('generated')
|
||||
|
||||
# File where we expect to find the settings definitions
|
||||
settings_file = gen_base.joinpath('inventree_settings.json')
|
||||
# Generated data is loaded (and cached) by a separate module, as some static site
|
||||
# generators (e.g. zensical) re-execute this file for every page.
|
||||
if str(here) not in sys.path:
|
||||
sys.path.append(str(here))
|
||||
|
||||
# File where we will *store* information on the settings we have observed
|
||||
observed_settings_file = gen_base.joinpath('observed_settings.json')
|
||||
|
||||
# Overwrite the observed settings file
|
||||
with open(observed_settings_file, 'w', encoding='utf-8') as f:
|
||||
data = {'global': {}, 'user': {}}
|
||||
|
||||
# Write an empty dict to the file
|
||||
# This is used to track which settings we have observed during the build process
|
||||
f.write(json.dumps(data, indent=4))
|
||||
|
||||
# File where we will *store* information on the status code classes we have observed
|
||||
observed_status_codes_file = gen_base.joinpath('observed_status_codes.json')
|
||||
|
||||
# Overwrite the observed status codes file
|
||||
with open(observed_status_codes_file, 'w', encoding='utf-8') as f:
|
||||
f.write(json.dumps({}, indent=4))
|
||||
|
||||
with open(settings_file, encoding='utf-8') as sf:
|
||||
settings = json.load(sf)
|
||||
|
||||
GLOBAL_SETTINGS = settings['global']
|
||||
USER_SETTINGS = settings['user']
|
||||
CONFIG_SETTINGS = settings['config']
|
||||
|
||||
# Tags
|
||||
with open(gen_base.joinpath('inventree_tags.yml'), encoding='utf-8') as f:
|
||||
TAGS = yaml.load(f, yaml.BaseLoader)
|
||||
# Filters
|
||||
with open(gen_base.joinpath('inventree_filters.yml'), encoding='utf-8') as f:
|
||||
FILTERS = yaml.load(f, yaml.BaseLoader)
|
||||
# Status codes
|
||||
with open(gen_base.joinpath('inventree_status_codes.json'), encoding='utf-8') as f:
|
||||
STATUS_CODES = json.load(f)
|
||||
# Report context
|
||||
with open(gen_base.joinpath('inventree_report_context.json'), encoding='utf-8') as f:
|
||||
REPORT_CONTEXT = json.load(f)
|
||||
# User permission roles
|
||||
with open(gen_base.joinpath('inventree_roles.json'), encoding='utf-8') as f:
|
||||
ROLES = json.load(f)
|
||||
# Audited models
|
||||
with open(gen_base.joinpath('inventree_audited_models.json'), encoding='utf-8') as f:
|
||||
AUDITED_MODELS = json.load(f)
|
||||
import docs_data
|
||||
|
||||
|
||||
def get_repo_url(raw=False):
|
||||
@@ -161,9 +88,30 @@ def get_build_environment() -> Optional[str]:
|
||||
return 'master'
|
||||
|
||||
|
||||
def apply_hook_config(env):
|
||||
"""Provide values normally injected by the 'on_config' mkdocs hook.
|
||||
|
||||
Zensical does not support mkdocs hooks, so 'config.releases',
|
||||
'config.assets_dir' etc are never set. Run the hook ourselves and
|
||||
inject the results into the template 'config' variable.
|
||||
"""
|
||||
template_config = env.variables.get('config')
|
||||
|
||||
if template_config is None:
|
||||
return
|
||||
|
||||
hook_config = docs_data.hook_config(template_config.get('site_url'))
|
||||
|
||||
for key in ['releases', 'assets_dir', 'readthedocs', 'version_banner']:
|
||||
if key in hook_config:
|
||||
template_config[key] = hook_config[key]
|
||||
|
||||
|
||||
def define_env(env):
|
||||
"""Define custom environment variables for the documentation build process."""
|
||||
config = env.config
|
||||
apply_hook_config(env)
|
||||
|
||||
config = env.variables.get('config') or env.config
|
||||
assets_dir = config.get('assets_dir', None)
|
||||
|
||||
if assets_dir is None:
|
||||
@@ -326,18 +274,10 @@ def define_env(env):
|
||||
(produced by the `export_status_codes` management command), so it can never
|
||||
drift out of sync with the status codes actually defined in the source code.
|
||||
"""
|
||||
global STATUS_CODES
|
||||
|
||||
status_class = STATUS_CODES[class_name]
|
||||
status_class = docs_data.status_codes()[class_name]
|
||||
|
||||
# Record that this status code class has been rendered somewhere in the docs
|
||||
with open(observed_status_codes_file, encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
data[class_name] = True
|
||||
|
||||
with open(observed_status_codes_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
docs_data.observe_status_code(class_name)
|
||||
|
||||
ret_data = '| Status | Value | Description |\n| --- | --- | --- |\n'
|
||||
|
||||
@@ -356,18 +296,7 @@ def define_env(env):
|
||||
key: The name of the setting to observe
|
||||
group: The group of the setting (e.g. 'global', 'user')
|
||||
"""
|
||||
# Read the observed settings file
|
||||
with open(observed_settings_file, encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if group not in data:
|
||||
data[group] = {}
|
||||
|
||||
data[group][key] = True
|
||||
|
||||
# Write the updated data back to the file
|
||||
with open(observed_settings_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
docs_data.observe_setting(key, group)
|
||||
|
||||
@env.macro
|
||||
def rendersetting(
|
||||
@@ -403,8 +332,7 @@ def define_env(env):
|
||||
short: If True, return a short version of the setting (default: False)
|
||||
default: An optional default value to override the setting's default display value.
|
||||
"""
|
||||
global GLOBAL_SETTINGS
|
||||
setting = GLOBAL_SETTINGS[key]
|
||||
setting = docs_data.settings('global')[key]
|
||||
|
||||
# Settings are only 'observed' if they are displayed in full
|
||||
if not short:
|
||||
@@ -420,8 +348,7 @@ def define_env(env):
|
||||
key: The name of the user setting to extract information for.
|
||||
short: If True, return a short version of the setting (default: False)
|
||||
"""
|
||||
global USER_SETTINGS
|
||||
setting = USER_SETTINGS[key]
|
||||
setting = docs_data.settings('user')[key]
|
||||
|
||||
# Settings are only 'observed' if they are displayed in full
|
||||
if not short:
|
||||
@@ -442,8 +369,7 @@ def define_env(env):
|
||||
key: The name of the configuration setting to extract information for.
|
||||
default: An optional default value to override the setting's default display value.
|
||||
"""
|
||||
global CONFIG_SETTINGS
|
||||
setting = CONFIG_SETTINGS[key]
|
||||
setting = docs_data.settings('config')[key]
|
||||
|
||||
observe_setting(key, 'config')
|
||||
|
||||
@@ -460,11 +386,8 @@ def define_env(env):
|
||||
@env.macro
|
||||
def tags_and_filters():
|
||||
"""Return a list of all tags and filters."""
|
||||
global TAGS
|
||||
global FILTERS
|
||||
|
||||
ret_data = ''
|
||||
for ref in [['Tags', TAGS], ['Filters', FILTERS]]:
|
||||
for ref in [['Tags', docs_data.tags()], ['Filters', docs_data.filters()]]:
|
||||
ret_data += f'### {ref[0]}\n\n| Namespace | Name | Description |\n| --- | --- | --- |\n'
|
||||
for value in ref[1]:
|
||||
title = (
|
||||
@@ -488,11 +411,9 @@ def define_env(env):
|
||||
`users.ruleset.RULESET_CHOICES`), so it can never drift out of sync with
|
||||
the roles actually defined in the source code.
|
||||
"""
|
||||
global ROLES
|
||||
|
||||
ret_data = '| Role | Description |\n| --- | --- |\n'
|
||||
|
||||
for role in ROLES:
|
||||
for role in docs_data.roles():
|
||||
description = role['description'] or role['label']
|
||||
ret_data += f'| **{role["label"]}** | {description} |\n'
|
||||
|
||||
@@ -507,12 +428,10 @@ def define_env(env):
|
||||
the models which implement `InvenTreeAuditMixin`), so it can never drift out
|
||||
of sync with the models actually audited in the source code.
|
||||
"""
|
||||
global AUDITED_MODELS
|
||||
|
||||
ret_data = '| Category | Model | Model Type | Parent | Notes |\n'
|
||||
ret_data += '| --- | --- | --- | --- | --- |\n'
|
||||
|
||||
for model in AUDITED_MODELS:
|
||||
for model in docs_data.audited_models():
|
||||
parent = model['parent']
|
||||
notes = model['parent_note']
|
||||
|
||||
@@ -527,9 +446,7 @@ def define_env(env):
|
||||
@env.macro
|
||||
def report_context(type_: Literal['models', 'base'], model: str):
|
||||
"""Extract information on a particular report context."""
|
||||
global REPORT_CONTEXT
|
||||
|
||||
context = REPORT_CONTEXT.get(type_).get(model)
|
||||
context = docs_data.report_context().get(type_).get(model)
|
||||
|
||||
ret_data = '| Variable | Type | Description |\n| --- | --- | --- |\n'
|
||||
for k, v in context['context'].items():
|
||||
@@ -568,9 +485,7 @@ def define_env(env):
|
||||
`InvenTreeReportMixin`, so there is no manually-maintained per-model heading
|
||||
list to keep in sync here.
|
||||
"""
|
||||
global REPORT_CONTEXT
|
||||
|
||||
models = REPORT_CONTEXT.get('models', {})
|
||||
models = docs_data.report_context().get('models', {})
|
||||
|
||||
ret_data = ''
|
||||
|
||||
@@ -593,9 +508,7 @@ def define_env(env):
|
||||
the `export_report_context` management command, so there is no manually-maintained
|
||||
list of related models to keep in sync here.
|
||||
"""
|
||||
global REPORT_CONTEXT
|
||||
|
||||
related = REPORT_CONTEXT.get('related_models', {})
|
||||
related = docs_data.report_context().get('related_models', {})
|
||||
|
||||
ret_data = ''
|
||||
|
||||
|
||||
+17
-21
@@ -1,5 +1,14 @@
|
||||
# Documentation is built with Zensical (https://zensical.org), which reads this
|
||||
# mkdocs-format config. Build from the repo root with "invoke build-docs --build"
|
||||
# (or "zensical build -f docs/mkdocs.yml" after running "invoke build-docs").
|
||||
#
|
||||
# Zensical does not run mkdocs hooks, so:
|
||||
# - the values from docs/docs/hooks.py:on_config are injected by docs/main.py
|
||||
# - the post-build checks (hooks.py:on_post_build) are run by "invoke build-docs"
|
||||
# - site_url is taken from READTHEDOCS_CANONICAL_URL when building on ReadTheDocs
|
||||
|
||||
# Project
|
||||
site_url: https://inventree.readthedocs.io
|
||||
site_url: !ENV [READTHEDOCS_CANONICAL_URL, 'https://docs.inventree.org/']
|
||||
site_name: InvenTree Documentation
|
||||
site_description: InvenTree - Open Source Inventory Management
|
||||
site_author: InvenTree
|
||||
@@ -12,8 +21,7 @@ repo_name: inventree/inventree
|
||||
# Theme
|
||||
theme:
|
||||
name: material
|
||||
font:
|
||||
text: Roboto
|
||||
variant: modern
|
||||
custom_dir: _includes/overrides
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
@@ -39,7 +47,7 @@ theme:
|
||||
- header.autohide
|
||||
- navigation.expand
|
||||
- navigation.footer
|
||||
- navigation.indexes
|
||||
# - navigation.indexes
|
||||
- navigation.instant
|
||||
# - navigation.sections
|
||||
- navigation.tracking
|
||||
@@ -54,10 +62,10 @@ extra_css:
|
||||
- stylesheets/brands.css
|
||||
- stylesheets/regular.css
|
||||
- stylesheets/solid.css
|
||||
- stylesheets/fontawesome-svg.css
|
||||
- stylesheets/bootstrap.css
|
||||
- stylesheets/splide.min.css
|
||||
- stylesheets/extra.css
|
||||
- stylesheets/neoteroi-mkdocs.css
|
||||
- https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.31.0/dist/tabler-icons.min.css
|
||||
extra_javascript:
|
||||
- javascripts/extra.js
|
||||
@@ -203,7 +211,6 @@ nav:
|
||||
- Multi Factor Authentication: settings/MFA.md
|
||||
- SCIM Provisioning: settings/SCIM.md
|
||||
- Email Settings: settings/email.md
|
||||
- Experimental Features: settings/experimental.md
|
||||
- Operations:
|
||||
- Background Tasks: settings/tasks.md
|
||||
- Error Logs: settings/logs.md
|
||||
@@ -304,22 +311,10 @@ nav:
|
||||
|
||||
# Plugins
|
||||
plugins:
|
||||
- neoteroi.mkdocsoad:
|
||||
use_pymdownx: true
|
||||
- include-markdown:
|
||||
opening_tag: "{!"
|
||||
closing_tag: "!}"
|
||||
- search
|
||||
- mermaid2:
|
||||
# version: 11.6.0
|
||||
javascript: javascripts/mermaid.min.js
|
||||
- git-revision-date-localized
|
||||
- mkdocs-simple-hooks:
|
||||
hooks:
|
||||
on_config: "docs.docs.hooks:on_config"
|
||||
on_post_build: "docs.docs.hooks:on_post_build"
|
||||
- macros:
|
||||
include_dir: docs/_includes
|
||||
include_dir: _includes
|
||||
on_error_fail: true
|
||||
module_name: main
|
||||
- mkdocstrings:
|
||||
default_handler: python
|
||||
@@ -366,7 +361,7 @@ markdown_extensions:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:mermaid2.fence_mermaid_custom
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
# - pymdownx.emoji:
|
||||
# emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
# emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
@@ -412,3 +407,4 @@ extra:
|
||||
|
||||
use_directory_urls: true
|
||||
strict: true
|
||||
site_dir: site
|
||||
@@ -11,6 +11,9 @@
|
||||
429
|
||||
],
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "^\\./schema/[a-z-]+\\.md$"
|
||||
},
|
||||
{
|
||||
"pattern": "http://inventree.localhost"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,667 @@
|
||||
"""Render sections of the InvenTree OpenAPI schema as static Markdown pages.
|
||||
|
||||
This replaces the 'neoteroi.mkdocsoad' plugin, so that the API documentation
|
||||
is plain Markdown which can be rendered by any static site generator
|
||||
(mkdocs, zensical, etc), without requiring a build-time plugin.
|
||||
|
||||
The rendered pages are generated as a pre-build step (see 'extract_schema.py').
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
|
||||
HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
|
||||
|
||||
# Placeholder values for generated examples, keyed by OpenAPI 'format'
|
||||
FORMAT_EXAMPLES = {
|
||||
'date': '2024-01-01',
|
||||
'date-time': '2024-01-01T12:00:00Z',
|
||||
'time': '12:00:00',
|
||||
'email': 'user@example.com',
|
||||
'uri': 'https://example.com',
|
||||
'uuid': '3fa85f64-5717-4562-b3fc-2c963f66afa6',
|
||||
'decimal': '0.00',
|
||||
'binary': '<binary>',
|
||||
'ipv4': '127.0.0.1',
|
||||
}
|
||||
|
||||
# Maximum depth when generating JSON examples (guards against deep nesting)
|
||||
EXAMPLE_MAX_DEPTH = 5
|
||||
|
||||
|
||||
def schema_anchor(name: str) -> str:
|
||||
"""Return the HTML anchor for a named component schema."""
|
||||
return 'schema-' + re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
|
||||
|
||||
|
||||
def operation_anchor(method: str, path: str) -> str:
|
||||
"""Return the HTML anchor for a single API operation."""
|
||||
return method.lower() + '-' + re.sub(r'[^a-z0-9]+', '-', path.lower()).strip('-')
|
||||
|
||||
|
||||
def escape_text(text: Any) -> str:
|
||||
"""Escape free text so it cannot inject HTML into the rendered page.
|
||||
|
||||
Markdown formatting (e.g. `code` or *emphasis*) is preserved.
|
||||
"""
|
||||
text = '' if text is None else str(text)
|
||||
return text.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
|
||||
|
||||
def escape_cell(text: Any) -> str:
|
||||
"""Escape text for use within a single Markdown table cell."""
|
||||
text = escape_text(text).strip()
|
||||
text = text.replace('|', '\\|')
|
||||
|
||||
lines = []
|
||||
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
# Bulleted lists cannot be rendered within a table cell
|
||||
if line.startswith(('* ', '- ')):
|
||||
line = '• ' + line[2:]
|
||||
lines.append(line)
|
||||
|
||||
return '<br>'.join(line for line in lines if line)
|
||||
|
||||
|
||||
def inline_code(text: Any) -> str:
|
||||
"""Wrap a value as inline code, coping with embedded backticks."""
|
||||
text = str(text)
|
||||
fence = '``' if '`' in text else '`'
|
||||
pad = ' ' if '`' in text else ''
|
||||
return f'{fence}{pad}{text}{pad}{fence}'
|
||||
|
||||
|
||||
class SchemaRenderer:
|
||||
"""Render API operations and their component schemas to Markdown."""
|
||||
|
||||
def __init__(self, spec: dict):
|
||||
"""Initialize the renderer with the *complete* OpenAPI spec.
|
||||
|
||||
The full spec is required so that '$ref' entries can always be resolved,
|
||||
regardless of which subset of paths is being rendered.
|
||||
"""
|
||||
self.spec = spec
|
||||
self.components = spec.get('components', {})
|
||||
self.schemas = self.components.get('schemas', {})
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Reference handling
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def ref_name(ref: str) -> str:
|
||||
"""Return the final component name from a '$ref' string."""
|
||||
return ref.split('/')[-1]
|
||||
|
||||
def resolve(self, obj: Any) -> Any:
|
||||
"""Resolve a '$ref' (to any component type) into the referenced object."""
|
||||
seen = set()
|
||||
|
||||
while isinstance(obj, dict) and '$ref' in obj:
|
||||
ref = obj['$ref']
|
||||
|
||||
if ref in seen or not ref.startswith('#/'):
|
||||
break
|
||||
|
||||
seen.add(ref)
|
||||
node = self.spec
|
||||
|
||||
for part in ref[2:].split('/'):
|
||||
node = node.get(part, {}) if isinstance(node, dict) else {}
|
||||
|
||||
obj = node
|
||||
|
||||
return obj
|
||||
|
||||
def collect_schema_refs(self, obj: Any, found: Optional[set] = None) -> set:
|
||||
"""Find all component schemas referenced (transitively) by an object.
|
||||
|
||||
References to other component types (e.g. responses or parameters)
|
||||
are followed, so that the schemas they use are also included.
|
||||
"""
|
||||
found = set() if found is None else found
|
||||
stack = [obj]
|
||||
visited = set()
|
||||
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
|
||||
if isinstance(node, list):
|
||||
stack.extend(node)
|
||||
continue
|
||||
|
||||
if not isinstance(node, dict):
|
||||
continue
|
||||
|
||||
ref = node.get('$ref')
|
||||
|
||||
if isinstance(ref, str) and ref not in visited:
|
||||
visited.add(ref)
|
||||
|
||||
if ref.startswith('#/components/schemas/'):
|
||||
found.add(self.ref_name(ref))
|
||||
|
||||
stack.append(self.resolve(node))
|
||||
|
||||
stack.extend(v for k, v in node.items() if k != '$ref')
|
||||
|
||||
return found
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Type descriptions
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def type_str(self, schema: Any) -> str:
|
||||
"""Return a short (Markdown) description of a schema's type."""
|
||||
if not isinstance(schema, dict):
|
||||
return ''
|
||||
|
||||
if '$ref' in schema:
|
||||
ref = schema['$ref']
|
||||
|
||||
if ref.startswith('#/components/schemas/'):
|
||||
name = self.ref_name(ref)
|
||||
return f'[{name}](#{schema_anchor(name)})'
|
||||
|
||||
return self.type_str(self.resolve(schema))
|
||||
|
||||
for key, joiner in (('allOf', ' & '), ('oneOf', ' or '), ('anyOf', ' or ')):
|
||||
if key in schema:
|
||||
options = [self.type_str(s) for s in schema[key]]
|
||||
return joiner.join(o for o in options if o) or 'any'
|
||||
|
||||
schema_type = schema.get('type')
|
||||
|
||||
if schema_type == 'array':
|
||||
return f'array[{self.type_str(schema.get("items", {})) or "any"}]'
|
||||
|
||||
if schema_type == 'object' or (not schema_type and 'properties' in schema):
|
||||
extra = schema.get('additionalProperties')
|
||||
|
||||
if isinstance(extra, dict) and extra:
|
||||
return f'object[string, {self.type_str(extra) or "any"}]'
|
||||
|
||||
return 'object'
|
||||
|
||||
if not schema_type:
|
||||
return 'any'
|
||||
|
||||
if fmt := schema.get('format'):
|
||||
return f'{schema_type}({fmt})'
|
||||
|
||||
return schema_type
|
||||
|
||||
def merged(self, schema: dict) -> dict:
|
||||
"""Flatten 'allOf' wrappers so that flags such as 'readOnly' are visible."""
|
||||
if not isinstance(schema, dict):
|
||||
return {}
|
||||
|
||||
result = {k: v for k, v in schema.items() if k != 'allOf'}
|
||||
|
||||
for sub in schema.get('allOf', []):
|
||||
sub = self.resolve(sub)
|
||||
if isinstance(sub, dict):
|
||||
for k, v in sub.items():
|
||||
result.setdefault(k, v)
|
||||
|
||||
return result
|
||||
|
||||
def constraints(self, schema: dict, description: str = '') -> list[str]:
|
||||
"""Return a list of human readable constraints for a schema.
|
||||
|
||||
Arguments:
|
||||
schema: The schema to describe
|
||||
description: Accompanying description text. Enum values are not
|
||||
repeated if the description already lists them all.
|
||||
"""
|
||||
notes = []
|
||||
|
||||
enum_listed = all(
|
||||
f'`{v}`' in (description or '') for v in schema.get('enum', []) if v
|
||||
)
|
||||
|
||||
if 'enum' in schema and not enum_listed:
|
||||
values = ', '.join(inline_code(json.dumps(v)) for v in schema['enum'])
|
||||
notes.append(f'One of: {values}')
|
||||
|
||||
for key, label in (
|
||||
('minLength', 'Min length'),
|
||||
('maxLength', 'Max length'),
|
||||
('minimum', 'Minimum'),
|
||||
('maximum', 'Maximum'),
|
||||
('minItems', 'Min items'),
|
||||
('maxItems', 'Max items'),
|
||||
):
|
||||
if key in schema:
|
||||
notes.append(f'{label}: {inline_code(schema[key])}')
|
||||
|
||||
if 'pattern' in schema:
|
||||
notes.append(f'Pattern: {inline_code(schema["pattern"])}')
|
||||
|
||||
if 'default' in schema:
|
||||
notes.append(f'Default: {inline_code(json.dumps(schema["default"]))}')
|
||||
|
||||
return notes
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Example generation
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def example(
|
||||
self, schema: Any, mode: str = 'response', depth: int = 0, stack=()
|
||||
) -> Any:
|
||||
"""Generate an example value for a schema.
|
||||
|
||||
Arguments:
|
||||
schema: The schema to generate an example for
|
||||
mode: 'request' (omit read-only fields) or 'response' (omit write-only fields)
|
||||
depth: Current nesting depth
|
||||
stack: Component names currently being expanded (cycle protection)
|
||||
"""
|
||||
if not isinstance(schema, dict) or depth > EXAMPLE_MAX_DEPTH:
|
||||
return None
|
||||
|
||||
if '$ref' in schema:
|
||||
name = self.ref_name(schema['$ref'])
|
||||
|
||||
if name in stack:
|
||||
return {}
|
||||
|
||||
return self.example(self.resolve(schema), mode, depth, (*stack, name))
|
||||
|
||||
if 'example' in schema:
|
||||
return schema['example']
|
||||
|
||||
if 'allOf' in schema:
|
||||
result = {}
|
||||
for sub in schema['allOf']:
|
||||
value = self.example(sub, mode, depth, stack)
|
||||
if isinstance(value, dict):
|
||||
result.update(value)
|
||||
elif value is not None:
|
||||
return value
|
||||
return result
|
||||
|
||||
for key in ('oneOf', 'anyOf'):
|
||||
if key in schema:
|
||||
# Prefer the first option which is not a 'null' or 'blank' variant
|
||||
for sub in schema[key]:
|
||||
value = self.example(sub, mode, depth, stack)
|
||||
if value not in (None, ''):
|
||||
return value
|
||||
return None
|
||||
|
||||
if 'default' in schema:
|
||||
return schema['default']
|
||||
|
||||
if 'enum' in schema:
|
||||
options = [v for v in schema['enum'] if v not in (None, '')]
|
||||
return options[0] if options else None
|
||||
|
||||
schema_type = schema.get('type')
|
||||
|
||||
if schema_type == 'object' or 'properties' in schema:
|
||||
result = {}
|
||||
|
||||
for name, prop in schema.get('properties', {}).items():
|
||||
flags = self.merged(prop)
|
||||
if mode == 'request' and flags.get('readOnly'):
|
||||
continue
|
||||
if mode == 'response' and flags.get('writeOnly'):
|
||||
continue
|
||||
result[name] = self.example(prop, mode, depth + 1, stack)
|
||||
|
||||
return result
|
||||
|
||||
if schema_type == 'array':
|
||||
item = self.example(schema.get('items', {}), mode, depth + 1, stack)
|
||||
return [] if item is None else [item]
|
||||
|
||||
if schema_type == 'string':
|
||||
return FORMAT_EXAMPLES.get(schema.get('format'), 'string')
|
||||
|
||||
if schema_type == 'integer':
|
||||
return 0
|
||||
|
||||
if schema_type == 'number':
|
||||
return 0.0
|
||||
|
||||
if schema_type == 'boolean':
|
||||
return False
|
||||
|
||||
return None
|
||||
|
||||
def example_block(self, schema: dict, mode: str, title: str) -> list[str]:
|
||||
"""Render a collapsible JSON example for the given schema."""
|
||||
value = self.example(schema, mode)
|
||||
|
||||
if value is None:
|
||||
return []
|
||||
|
||||
body = json.dumps(value, indent=2)
|
||||
|
||||
lines = [f'??? example "{title}"', ' ```json']
|
||||
lines += [' ' + line for line in body.splitlines()]
|
||||
lines += [' ```', '']
|
||||
|
||||
return lines
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Operations
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def security_names(security: Optional[list]) -> tuple:
|
||||
"""Return a sorted tuple of security scheme names for an operation."""
|
||||
if not security:
|
||||
return ()
|
||||
|
||||
names = set()
|
||||
|
||||
for requirement in security:
|
||||
names.update(requirement.keys())
|
||||
|
||||
return tuple(sorted(names))
|
||||
|
||||
def render_security(self, names: tuple) -> str:
|
||||
"""Render a list of security schemes as Markdown."""
|
||||
if not names:
|
||||
return 'No authentication required'
|
||||
|
||||
return ', '.join(inline_code(n) for n in names)
|
||||
|
||||
def render_parameters(self, parameters: list) -> list[str]:
|
||||
"""Render the parameters of an operation as a table."""
|
||||
if not parameters:
|
||||
return []
|
||||
|
||||
lines = [
|
||||
'**Parameters**',
|
||||
'',
|
||||
'| Name | In | Type | Required | Description |',
|
||||
'| --- | --- | --- | --- | --- |',
|
||||
]
|
||||
|
||||
for param in parameters:
|
||||
param = self.resolve(param)
|
||||
schema = param.get('schema', {})
|
||||
notes = [escape_cell(param.get('description', ''))]
|
||||
notes += self.constraints(
|
||||
self.merged(self.resolve(schema)), param.get('description', '')
|
||||
)
|
||||
description = '<br>'.join(n for n in notes if n)
|
||||
|
||||
lines.append(
|
||||
f'| {inline_code(param.get("name", ""))} '
|
||||
f'| {param.get("in", "")} '
|
||||
f'| {self.type_str(schema)} '
|
||||
f'| {"Yes" if param.get("required") else "No"} '
|
||||
f'| {description} |'
|
||||
)
|
||||
|
||||
return [*lines, '']
|
||||
|
||||
def render_request_body(self, body: dict) -> list[str]:
|
||||
"""Render the request body of an operation."""
|
||||
body = self.resolve(body)
|
||||
content = body.get('content', {})
|
||||
|
||||
if not content:
|
||||
return []
|
||||
|
||||
content_types = ', '.join(inline_code(c) for c in content)
|
||||
required = ' (required)' if body.get('required') else ''
|
||||
|
||||
# Prefer the JSON representation for documentation purposes
|
||||
media = content.get('application/json') or next(iter(content.values()))
|
||||
schema = media.get('schema', {})
|
||||
|
||||
lines = [
|
||||
f'**Request body**{required}: {self.type_str(schema)}',
|
||||
'',
|
||||
f'Content types: {content_types}',
|
||||
'',
|
||||
]
|
||||
|
||||
lines += self.example_block(schema, 'request', 'Example request body')
|
||||
|
||||
return lines
|
||||
|
||||
def render_responses(self, responses: dict) -> list[str]:
|
||||
"""Render the responses of an operation."""
|
||||
if not responses:
|
||||
return []
|
||||
|
||||
lines = [
|
||||
'**Responses**',
|
||||
'',
|
||||
'| Status | Description | Schema |',
|
||||
'| --- | --- | --- |',
|
||||
]
|
||||
|
||||
examples = []
|
||||
|
||||
for status, response in responses.items():
|
||||
response = self.resolve(response)
|
||||
content = response.get('content', {})
|
||||
media = content.get('application/json') or (
|
||||
next(iter(content.values())) if content else {}
|
||||
)
|
||||
schema = media.get('schema', {}) if media else {}
|
||||
|
||||
lines.append(
|
||||
f'| {inline_code(status)} '
|
||||
f'| {escape_cell(response.get("description", ""))} '
|
||||
f'| {self.type_str(schema) if schema else ""} |'
|
||||
)
|
||||
|
||||
# Provide an example for successful JSON responses only
|
||||
if schema and str(status).startswith('2') and not examples:
|
||||
examples = self.example_block(
|
||||
schema, 'response', f'Example response ({status})'
|
||||
)
|
||||
|
||||
return [*lines, '', *examples]
|
||||
|
||||
def render_operation(
|
||||
self, path: str, method: str, operation: dict, default_security: tuple
|
||||
) -> list[str]:
|
||||
"""Render a single API operation (path + method)."""
|
||||
anchor = operation_anchor(method, path)
|
||||
label = method.upper()
|
||||
|
||||
lines = [
|
||||
f'### <span class="api-method api-method-{method}">{label}</span> '
|
||||
f'{inline_code(path)} {{#{anchor}}}',
|
||||
'',
|
||||
]
|
||||
|
||||
if operation.get('deprecated'):
|
||||
lines += [
|
||||
'!!! warning "Deprecated"',
|
||||
' This endpoint is deprecated.',
|
||||
'',
|
||||
]
|
||||
|
||||
if summary := operation.get('summary'):
|
||||
lines += [f'**{escape_text(summary).strip()}**', '']
|
||||
|
||||
if description := operation.get('description'):
|
||||
lines += [escape_text(description).strip(), '']
|
||||
|
||||
security = self.security_names(operation.get('security'))
|
||||
|
||||
if security != default_security:
|
||||
lines += [f'**Authentication**: {self.render_security(security)}', '']
|
||||
|
||||
lines += self.render_parameters(operation.get('parameters', []))
|
||||
lines += self.render_request_body(operation.get('requestBody', {}))
|
||||
lines += self.render_responses(operation.get('responses', {}))
|
||||
|
||||
return lines
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Component schemas
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def render_schema(self, name: str) -> list[str]:
|
||||
"""Render a single named component schema."""
|
||||
schema = self.schemas.get(name, {})
|
||||
|
||||
lines = [f'### {name} {{#{schema_anchor(name)}}}', '']
|
||||
|
||||
if description := schema.get('description'):
|
||||
lines += [escape_text(description).strip(), '']
|
||||
|
||||
properties = schema.get('properties', {})
|
||||
|
||||
if properties:
|
||||
required = set(schema.get('required', []))
|
||||
|
||||
lines += [
|
||||
'| Field | Type | Required | Description |',
|
||||
'| --- | --- | --- | --- |',
|
||||
]
|
||||
|
||||
for field, prop in properties.items():
|
||||
flags = self.merged(prop)
|
||||
resolved = self.merged(self.resolve(prop))
|
||||
|
||||
notes = []
|
||||
badges = []
|
||||
|
||||
if flags.get('readOnly'):
|
||||
badges.append('read-only')
|
||||
if flags.get('writeOnly'):
|
||||
badges.append('write-only')
|
||||
if flags.get('nullable'):
|
||||
badges.append('nullable')
|
||||
|
||||
if badges:
|
||||
notes.append(' '.join(f'*{b}*' for b in badges))
|
||||
|
||||
if title := flags.get('title'):
|
||||
if title.lower() != field.replace('_', ' ').lower():
|
||||
notes.append(f'**{escape_cell(title)}**')
|
||||
|
||||
field_description = flags.get('description') or resolved.get(
|
||||
'description'
|
||||
)
|
||||
notes.append(escape_cell(field_description))
|
||||
|
||||
# Constraints of referenced schemas are documented on that schema
|
||||
notes += self.constraints(flags, field_description)
|
||||
|
||||
lines.append(
|
||||
f'| {inline_code(field)} '
|
||||
f'| {self.type_str(prop)} '
|
||||
f'| {"Yes" if field in required else "No"} '
|
||||
f'| {"<br>".join(n for n in notes if n)} |'
|
||||
)
|
||||
|
||||
lines.append('')
|
||||
|
||||
elif 'enum' in schema:
|
||||
base_type = self.type_str({k: v for k, v in schema.items() if k != 'enum'})
|
||||
lines += [f'Type: {base_type}', '']
|
||||
|
||||
# Only list the values if the description does not already do so
|
||||
if self.constraints({'enum': schema['enum']}, description or ''):
|
||||
lines += ['| Value |', '| --- |']
|
||||
lines += [f'| {inline_code(json.dumps(v))} |' for v in schema['enum']]
|
||||
lines.append('')
|
||||
|
||||
else:
|
||||
lines += [f'Type: {self.type_str(schema)}', '']
|
||||
|
||||
if notes := self.constraints(schema):
|
||||
lines += ['<br>'.join(notes), '']
|
||||
|
||||
return lines
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Page rendering
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def default_security(self, paths: dict) -> tuple:
|
||||
"""Return the most common set of security schemes across the given paths."""
|
||||
counts = {}
|
||||
|
||||
for methods in paths.values():
|
||||
for method, operation in methods.items():
|
||||
if method in HTTP_METHODS:
|
||||
key = self.security_names(operation.get('security'))
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
|
||||
if not counts:
|
||||
return ()
|
||||
|
||||
return max(counts, key=counts.get)
|
||||
|
||||
def render_page(self, title: str, paths: dict) -> str:
|
||||
"""Render a full documentation page for a subset of API paths.
|
||||
|
||||
Arguments:
|
||||
title: The title of this section of the API
|
||||
paths: Dict of {path: {method: operation}} items to render
|
||||
"""
|
||||
info = self.spec.get('info', {})
|
||||
default_security = self.default_security(paths)
|
||||
|
||||
lines = [
|
||||
'---',
|
||||
f'title: {json.dumps(title)}',
|
||||
# Schema text must never be interpreted as Jinja2 template code
|
||||
'render_macros: false',
|
||||
'---',
|
||||
'',
|
||||
f'The *{escape_text(title)}* section of the InvenTree API schema is documented below.',
|
||||
'',
|
||||
f'!!! info "API Version {escape_text(info.get("version", ""))}"',
|
||||
' This page was generated automatically from the InvenTree API schema.',
|
||||
'',
|
||||
]
|
||||
|
||||
if default_security:
|
||||
lines += [
|
||||
'## Authentication',
|
||||
'',
|
||||
'Unless otherwise noted, endpoints on this page accept the following authentication methods: '
|
||||
+ self.render_security(default_security)
|
||||
+ '. Refer to the [API documentation](../index.md#authentication) for more information.',
|
||||
'',
|
||||
]
|
||||
|
||||
lines += ['## Endpoints', '']
|
||||
|
||||
referenced = set()
|
||||
|
||||
for path in sorted(paths):
|
||||
methods = paths[path]
|
||||
|
||||
# Parameters may also be declared at the path level (shared by all methods)
|
||||
path_params = methods.get('parameters', [])
|
||||
|
||||
for method in HTTP_METHODS:
|
||||
if operation := methods.get(method):
|
||||
if path_params:
|
||||
operation = {
|
||||
**operation,
|
||||
'parameters': path_params + operation.get('parameters', []),
|
||||
}
|
||||
|
||||
lines += self.render_operation(
|
||||
path, method, operation, default_security
|
||||
)
|
||||
self.collect_schema_refs(operation, referenced)
|
||||
|
||||
if referenced:
|
||||
lines += ['## Schemas', '']
|
||||
|
||||
for name in sorted(referenced, key=str.lower):
|
||||
lines += self.render_schema(name)
|
||||
|
||||
return '\n'.join(lines).rstrip() + '\n'
|
||||
+1
-10
@@ -1,11 +1,2 @@
|
||||
properdocs==1.6.7
|
||||
mkdocs<2.0
|
||||
mkdocs-macros-plugin>=0.7,<2.0
|
||||
mkdocs-material>=9.7.7,<10.0
|
||||
mkdocs-git-revision-date-localized-plugin>=1.6.0,<2.0
|
||||
mkdocs-redirects
|
||||
mkdocs-simple-hooks
|
||||
mkdocs-include-markdown-plugin
|
||||
neoteroi-mkdocs
|
||||
zensical
|
||||
mkdocstrings[python]
|
||||
mkdocs-mermaid2-plugin
|
||||
+92
-414
@@ -1,299 +1,27 @@
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile docs/requirements.in -o docs/requirements.txt -c src/backend/requirements.txt
|
||||
anyio==4.15.1 \
|
||||
--hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \
|
||||
--hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94
|
||||
# via httpx
|
||||
babel==2.18.0 \
|
||||
--hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \
|
||||
--hash=sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs-git-revision-date-localized-plugin
|
||||
# mkdocs-material
|
||||
backrefs==8.1 \
|
||||
--hash=sha256:11f61846e446fa8637af2f5c073f1083aa58dbb7460b019fd51689f564e44f7e \
|
||||
--hash=sha256:1e857c32e91b8fe46ddb359ac21021a37b090fa4239566a61e9856123e334041 \
|
||||
--hash=sha256:438bd38dc7fa0cded52786a1351a2dad9efc2cda95d047284c41d916a22312a8 \
|
||||
--hash=sha256:9c8fe3e2eaae4301a9ce787341d986e5ba25a887d89da6de0de5d58eca26a949 \
|
||||
--hash=sha256:b472c76082769f86a97ffccfd1f2599b2b706610c6154955664fffe67e909035 \
|
||||
--hash=sha256:db577dbae1c61187534d35fc9bfbd0c01158fff6239a66fecf7590e5b17879a9 \
|
||||
--hash=sha256:f86d9312df3ebae1241758af80809a319cafa1d8f32189edfb242c9aa55573b6
|
||||
# via mkdocs-material
|
||||
beautifulsoup4==4.15.0 \
|
||||
--hash=sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7 \
|
||||
--hash=sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9
|
||||
# via mkdocs-mermaid2-plugin
|
||||
bracex==3.2 \
|
||||
--hash=sha256:0a3d40bfae4caae4d3bfd0ea715874799fab711c38fd4012cc35343e815405cd \
|
||||
--hash=sha256:8f3e908a452f66707e1d6d91b3d7d7d4c649d248df18537c0dc710aeb9c30d87
|
||||
# via wcmatch
|
||||
certifi==2026.10.23 \
|
||||
--hash=sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd \
|
||||
--hash=sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
charset-normalizer==3.5.2 \
|
||||
--hash=sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e \
|
||||
--hash=sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf \
|
||||
--hash=sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5 \
|
||||
--hash=sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56 \
|
||||
--hash=sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26 \
|
||||
--hash=sha256:0891b9d3903c5571c03771ca669a4b0ec5618ca722a5c957d3d29cd4e5062848 \
|
||||
--hash=sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718 \
|
||||
--hash=sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93 \
|
||||
--hash=sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640 \
|
||||
--hash=sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3 \
|
||||
--hash=sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875 \
|
||||
--hash=sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e \
|
||||
--hash=sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275 \
|
||||
--hash=sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204 \
|
||||
--hash=sha256:15bb4005af6320d259dc7593ca84a38d7fe06a421dbcf7b910ae23979101e787 \
|
||||
--hash=sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234 \
|
||||
--hash=sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3 \
|
||||
--hash=sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98 \
|
||||
--hash=sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3 \
|
||||
--hash=sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187 \
|
||||
--hash=sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d \
|
||||
--hash=sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f \
|
||||
--hash=sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7 \
|
||||
--hash=sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011 \
|
||||
--hash=sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f \
|
||||
--hash=sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869 \
|
||||
--hash=sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1 \
|
||||
--hash=sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d \
|
||||
--hash=sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847 \
|
||||
--hash=sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320 \
|
||||
--hash=sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9 \
|
||||
--hash=sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93 \
|
||||
--hash=sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd \
|
||||
--hash=sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00 \
|
||||
--hash=sha256:2cc961b171b3f3440f410489ab3573e86aea8736134ebbb40ea1338b7f0831bc \
|
||||
--hash=sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0 \
|
||||
--hash=sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09 \
|
||||
--hash=sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac \
|
||||
--hash=sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621 \
|
||||
--hash=sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c \
|
||||
--hash=sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8 \
|
||||
--hash=sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a \
|
||||
--hash=sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51 \
|
||||
--hash=sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0 \
|
||||
--hash=sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef \
|
||||
--hash=sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa \
|
||||
--hash=sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6 \
|
||||
--hash=sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649 \
|
||||
--hash=sha256:3ddacd27458c45bdacd6bd6db644bfb730efbf9e830310186e3045c9c5be8fb2 \
|
||||
--hash=sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229 \
|
||||
--hash=sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e \
|
||||
--hash=sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd \
|
||||
--hash=sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115 \
|
||||
--hash=sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9 \
|
||||
--hash=sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c \
|
||||
--hash=sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c \
|
||||
--hash=sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab \
|
||||
--hash=sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253 \
|
||||
--hash=sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995 \
|
||||
--hash=sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438 \
|
||||
--hash=sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0 \
|
||||
--hash=sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be \
|
||||
--hash=sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b \
|
||||
--hash=sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7 \
|
||||
--hash=sha256:55ea99acb17b9325618de155a0cd6a2e8f5d10be008113e1d433bbb58db543b2 \
|
||||
--hash=sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a \
|
||||
--hash=sha256:588461c2e8384d309bd63e5826019b6977bc66d629b99ac8737bb795d7b2cb5a \
|
||||
--hash=sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a \
|
||||
--hash=sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c \
|
||||
--hash=sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5 \
|
||||
--hash=sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37 \
|
||||
--hash=sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e \
|
||||
--hash=sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4 \
|
||||
--hash=sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800 \
|
||||
--hash=sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055 \
|
||||
--hash=sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e \
|
||||
--hash=sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5 \
|
||||
--hash=sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c \
|
||||
--hash=sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b \
|
||||
--hash=sha256:68eb192d85ab8e5f6ec69c2bc6ac0179fbf04a5ac1569d12fbef74883fe102d0 \
|
||||
--hash=sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80 \
|
||||
--hash=sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a \
|
||||
--hash=sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4 \
|
||||
--hash=sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2 \
|
||||
--hash=sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58 \
|
||||
--hash=sha256:75a3ceed0724d625d64b86ca20aba182e4df462e04c2414fc941c0f523f06aac \
|
||||
--hash=sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc \
|
||||
--hash=sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639 \
|
||||
--hash=sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf \
|
||||
--hash=sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d \
|
||||
--hash=sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f \
|
||||
--hash=sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c \
|
||||
--hash=sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc \
|
||||
--hash=sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4 \
|
||||
--hash=sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253 \
|
||||
--hash=sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade \
|
||||
--hash=sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858 \
|
||||
--hash=sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26 \
|
||||
--hash=sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96 \
|
||||
--hash=sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8 \
|
||||
--hash=sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249 \
|
||||
--hash=sha256:8b2bfab86aa71ae13aa41a6a26aab338e0db2b8bc75434b05aea89e011ff35a4 \
|
||||
--hash=sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13 \
|
||||
--hash=sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1 \
|
||||
--hash=sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03 \
|
||||
--hash=sha256:93223adc95033dd47133a46ccfc316a0139176fd79085762e27202ec56018f03 \
|
||||
--hash=sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e \
|
||||
--hash=sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364 \
|
||||
--hash=sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4 \
|
||||
--hash=sha256:9bde855991b7e362c146535e3136a50bfaffc0487d38b33ca7e5edefc6e23849 \
|
||||
--hash=sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0 \
|
||||
--hash=sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a \
|
||||
--hash=sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036 \
|
||||
--hash=sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3 \
|
||||
--hash=sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21 \
|
||||
--hash=sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3 \
|
||||
--hash=sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e \
|
||||
--hash=sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413 \
|
||||
--hash=sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21 \
|
||||
--hash=sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346 \
|
||||
--hash=sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429 \
|
||||
--hash=sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685 \
|
||||
--hash=sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45 \
|
||||
--hash=sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f \
|
||||
--hash=sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c \
|
||||
--hash=sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d \
|
||||
--hash=sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad \
|
||||
--hash=sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400 \
|
||||
--hash=sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb \
|
||||
--hash=sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c \
|
||||
--hash=sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc \
|
||||
--hash=sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c \
|
||||
--hash=sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74 \
|
||||
--hash=sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf \
|
||||
--hash=sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604 \
|
||||
--hash=sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f \
|
||||
--hash=sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105 \
|
||||
--hash=sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a \
|
||||
--hash=sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d \
|
||||
--hash=sha256:d4a7319f304a774bed22115bc891618e45f85065ab44ea6acd07d274e750519a \
|
||||
--hash=sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1 \
|
||||
--hash=sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5 \
|
||||
--hash=sha256:d913de495d90407cd859d263bee2e5d1a4ed3eb6573c04e70d9ec619a7cbed7f \
|
||||
--hash=sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e \
|
||||
--hash=sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709 \
|
||||
--hash=sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874 \
|
||||
--hash=sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5 \
|
||||
--hash=sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc \
|
||||
--hash=sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95 \
|
||||
--hash=sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd \
|
||||
--hash=sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0 \
|
||||
--hash=sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d \
|
||||
--hash=sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3 \
|
||||
--hash=sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c \
|
||||
--hash=sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3 \
|
||||
--hash=sha256:e80e6c2f55656b4824d72065abb4ddd6a525c74bd78a0aab5d9fc2cf4fb5af50 \
|
||||
--hash=sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491 \
|
||||
--hash=sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5 \
|
||||
--hash=sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5 \
|
||||
--hash=sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655 \
|
||||
--hash=sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288 \
|
||||
--hash=sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd \
|
||||
--hash=sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084 \
|
||||
--hash=sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d \
|
||||
--hash=sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4 \
|
||||
--hash=sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915 \
|
||||
--hash=sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1 \
|
||||
--hash=sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd \
|
||||
--hash=sha256:fc14a032f813bf5fe624d991960ea83e9715adc27e4c1830a2361eb1d02ac341 \
|
||||
--hash=sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424 \
|
||||
--hash=sha256:fd1fbe0f116b6e55da77aca2c6ddcddcfac2186cbf78bdebf40fc156efca389d \
|
||||
--hash=sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
click==8.5.0 \
|
||||
--hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \
|
||||
--hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34
|
||||
# via
|
||||
# mkdocs
|
||||
# neoteroi-mkdocs
|
||||
# properdocs
|
||||
colorama==0.4.6 \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via mkdocs-material
|
||||
editorconfig==0.17.1 \
|
||||
--hash=sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82 \
|
||||
--hash=sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745
|
||||
# via jsbeautifier
|
||||
essentials==1.1.9 \
|
||||
--hash=sha256:71ef161e0e27ef77cd6f5fc05e0b8688a575fcab870c01c95940f832e321dfbb \
|
||||
--hash=sha256:7fbea3a518cbeafe5374fb7e2ea2c15a109e8a7fd1eaab62ae87cbd1b3b1e8d0
|
||||
# via essentials-openapi
|
||||
essentials-openapi==1.4.0 \
|
||||
--hash=sha256:578c81501ccf6d18c0839d60636214fbd051f0ef37f1d207d4e3c92de2aac008 \
|
||||
--hash=sha256:86d879c32734248ad52482a90ee89a32883bce348b4edd323c01556b505b45b9
|
||||
# via neoteroi-mkdocs
|
||||
# zensical
|
||||
ghp-import==2.1.0 \
|
||||
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
|
||||
--hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
|
||||
# via
|
||||
# mkdocs
|
||||
# properdocs
|
||||
gitdb==4.0.12 \
|
||||
--hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \
|
||||
--hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf
|
||||
# via gitpython
|
||||
gitpython==3.2.1 \
|
||||
--hash=sha256:0f1229ec5c057bdabdeeda8bda4f17bda679ab33dbccc00f24e6a480553c8613 \
|
||||
--hash=sha256:d66b0832aa8755c1a84892937282df0befb16c97e5a64aeebd68be9c5e323f53
|
||||
# via mkdocs-git-revision-date-localized-plugin
|
||||
# via mkdocs
|
||||
griffelib==2.4.1 \
|
||||
--hash=sha256:40c028dd2e623f464423f0e11e3c553158f1cf4f1368239a370534d73e1407ff \
|
||||
--hash=sha256:76bc562f936ad1ce198d2cf6d0731bd5c92a7d934b69ef828ef22ee0ffdb62ce
|
||||
# via mkdocstrings-python
|
||||
h11==0.16.0 \
|
||||
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
||||
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
||||
# via httpcore
|
||||
hjson==3.1.0 \
|
||||
--hash=sha256:55af475a27cf83a7969c808399d7bccdec8fb836a07ddbd574587593b9cdcf75 \
|
||||
--hash=sha256:65713cdcf13214fb554eb8b4ef803419733f4f5e551047c9b711098ab7186b89
|
||||
# via
|
||||
# mkdocs-macros-plugin
|
||||
# super-collections
|
||||
httpcore==1.0.9 \
|
||||
--hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
|
||||
--hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
|
||||
# via httpx
|
||||
httpx==0.28.1 \
|
||||
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
||||
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
||||
# via neoteroi-mkdocs
|
||||
idna==3.20 \
|
||||
--hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
|
||||
--hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# anyio
|
||||
# httpx
|
||||
# requests
|
||||
jinja2==3.1.6 \
|
||||
--hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
|
||||
--hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs
|
||||
# mkdocs-macros-plugin
|
||||
# mkdocs-material
|
||||
# mkdocstrings
|
||||
# neoteroi-mkdocs
|
||||
# properdocs
|
||||
jsbeautifier==2.0.3 \
|
||||
--hash=sha256:9579d4e9dbaa00383f3efdff4c98c8140bb85ba319398e8b97cdaba27abd6ba3 \
|
||||
--hash=sha256:f0190e279a2cdb827556ada63f41c9c63c11f8116ee06e264b24aa50311cbee3
|
||||
# via mkdocs-mermaid2-plugin
|
||||
# zensical
|
||||
markdown==3.11.2 \
|
||||
--hash=sha256:87bf5d90f2d2882e2c02fcda182c2800456b985bff38913867c49706481514b0 \
|
||||
--hash=sha256:f80390747a2a0ff2ba54da074ce4f862f72a6685f608cd2637b646a6d83993f5
|
||||
@@ -301,14 +29,9 @@ markdown==3.11.2 \
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs
|
||||
# mkdocs-autorefs
|
||||
# mkdocs-material
|
||||
# mkdocstrings
|
||||
# properdocs
|
||||
# pymdown-extensions
|
||||
markdown-it-py==4.2.0 \
|
||||
--hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
|
||||
--hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
|
||||
# via rich
|
||||
# zensical
|
||||
markupsafe==3.0.4 \
|
||||
--hash=sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98 \
|
||||
--hash=sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002 \
|
||||
@@ -463,16 +186,10 @@ markupsafe==3.0.4 \
|
||||
--hash=sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# essentials-openapi
|
||||
# jinja2
|
||||
# mkdocs
|
||||
# mkdocs-autorefs
|
||||
# mkdocstrings
|
||||
# properdocs
|
||||
mdurl==0.1.2 \
|
||||
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
|
||||
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
|
||||
# via markdown-it-py
|
||||
mergedeep==1.3.4 \
|
||||
--hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \
|
||||
--hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307
|
||||
@@ -483,17 +200,8 @@ mkdocs==1.6.1 \
|
||||
--hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \
|
||||
--hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocs-autorefs
|
||||
# mkdocs-git-revision-date-localized-plugin
|
||||
# mkdocs-include-markdown-plugin
|
||||
# mkdocs-macros-plugin
|
||||
# mkdocs-material
|
||||
# mkdocs-mermaid2-plugin
|
||||
# mkdocs-redirects
|
||||
# mkdocs-simple-hooks
|
||||
# mkdocstrings
|
||||
# neoteroi-mkdocs
|
||||
mkdocs-autorefs==1.4.5 \
|
||||
--hash=sha256:68ee484e0800f3ba50cda1bc4a94293adbdfcc010243c322b3f5d66e15d06770 \
|
||||
--hash=sha256:856f75b327b7717923ce06267094650689a510a748c48657f5333965950c2788
|
||||
@@ -504,38 +212,6 @@ mkdocs-get-deps==0.2.2 \
|
||||
--hash=sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1 \
|
||||
--hash=sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650
|
||||
# via mkdocs
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.2 \
|
||||
--hash=sha256:9db79422039f856e582df62b360f9b9bde0f1b7c978d253bdb2a1b73a18b7774 \
|
||||
--hash=sha256:9f38e5cfef3d180f1c479a7190d72617267a5d79d8159b12e3ae39c80f6e7488
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-include-markdown-plugin==7.3.0 \
|
||||
--hash=sha256:2800126746452e31c2e321bbd43c8190b356e0de353e20cbc16a34a3c3d6796c \
|
||||
--hash=sha256:5b5c99b5d3c9b9ce0114a9e60353bbafb6be53a26c2d3b74ec6b767a7a8e55ca
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-macros-plugin==1.5.0 \
|
||||
--hash=sha256:12aa45ce7ecb7a445c66b9f649f3dd05e9b92e8af6bc65e4acd91d26f878c01f \
|
||||
--hash=sha256:c10fabd812bf50f9170609d0ed518e54f1f0e12c334ac29141723a83c881dd6f
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material==9.8.0 \
|
||||
--hash=sha256:19304a7263c5700428227122b3b2699aa73f432c9de8816876adaec7d7293ce3 \
|
||||
--hash=sha256:1ed4c5edaf405a3fb62e55288b8fdff592971729361548b1ffa757bf9849fb81
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-material-extensions==1.3.1 \
|
||||
--hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \
|
||||
--hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31
|
||||
# via mkdocs-material
|
||||
mkdocs-mermaid2-plugin==1.2.3 \
|
||||
--hash=sha256:33f60c582be623ed53829a96e19284fc7f1b74a1dbae78d4d2e47fe00c3e190d \
|
||||
--hash=sha256:fb6f901d53e5191e93db78f93f219cad926ccc4d51e176271ca5161b6cc5368c
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-redirects==1.2.3 \
|
||||
--hash=sha256:5e980330999299729a2d6a125347d1af78023d68a23681a4de3053ce7dfe2e51 \
|
||||
--hash=sha256:ec7312fff462d03ec16395d0c001006a418f8d0c21cdf2b47ff11cf839dc3ce0
|
||||
# via -r docs/requirements.in
|
||||
mkdocs-simple-hooks==0.1.5 \
|
||||
--hash=sha256:dddbdf151a18723c9302a133e5cf79538be8eb9d274e8e07d2ac3ac34890837c \
|
||||
--hash=sha256:efeabdbb98b0850a909adee285f3404535117159d5cb3a34f541d6eaa644d50a
|
||||
# via -r docs/requirements.in
|
||||
mkdocstrings[python]==1.0.8 \
|
||||
--hash=sha256:4b03ed2b863009f1153f0d04aacb22a85b92afc28a0636d85717c92eecca638c \
|
||||
--hash=sha256:b50fc326230906425c82ee9ec59b80e5db00892b2b593d4d0b841a0d4acf537b
|
||||
@@ -546,63 +222,40 @@ mkdocstrings-python==2.0.10 \
|
||||
--hash=sha256:4a3aaeebb25ba5ae57a8f9e75432fe4571984889602f2b2f7d672faa760e9062 \
|
||||
--hash=sha256:5e34ae56b2f20a57807ee479d603ddc5ec7cc6d2252c4c177754627ef2d3b833
|
||||
# via mkdocstrings
|
||||
neoteroi-mkdocs==1.2.0 \
|
||||
--hash=sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e \
|
||||
--hash=sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8
|
||||
# via -r docs/requirements.in
|
||||
packaging==26.3 \
|
||||
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
|
||||
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs
|
||||
# mkdocs-git-revision-date-localized-plugin
|
||||
# mkdocs-macros-plugin
|
||||
# properdocs
|
||||
paginate==0.5.7 \
|
||||
--hash=sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945 \
|
||||
--hash=sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591
|
||||
# via mkdocs-material
|
||||
pathspec==1.1.1 \
|
||||
--hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \
|
||||
--hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189
|
||||
# via
|
||||
# mkdocs
|
||||
# mkdocs-macros-plugin
|
||||
# properdocs
|
||||
# zensical
|
||||
platformdirs==4.14.5 \
|
||||
--hash=sha256:32e9921db7385aa8169bee4fe9501923e5715ba9fb7ea3b9c69b0d8e0639f7a1 \
|
||||
--hash=sha256:8e778c6fa421516f4d3369449f8df3b0e1b72c0e0005fe88e3ec772d0a14e750
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs-get-deps
|
||||
# properdocs
|
||||
properdocs==1.6.7 \
|
||||
--hash=sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd \
|
||||
--hash=sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e
|
||||
# via
|
||||
# -r docs/requirements.in
|
||||
# mkdocs-redirects
|
||||
pygments==2.21.0 \
|
||||
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
|
||||
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
|
||||
# via
|
||||
# mkdocs-material
|
||||
# rich
|
||||
# via zensical
|
||||
pymdown-extensions==12.3 \
|
||||
--hash=sha256:345493a8e3497e24382902903a1c43a28e34a52f2b7571a1c9641429b820ca74 \
|
||||
--hash=sha256:a7f2a4e0bc60c6e6babfa7922cfb3b01cd640eb00fb208f3c582e12bae00ea67
|
||||
# via
|
||||
# mkdocs-material
|
||||
# mkdocs-mermaid2-plugin
|
||||
# mkdocstrings
|
||||
# zensical
|
||||
python-dateutil==2.9.0.post0 \
|
||||
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
|
||||
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# ghp-import
|
||||
# mkdocs-macros-plugin
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
|
||||
@@ -679,72 +332,88 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# essentials-openapi
|
||||
# mkdocs
|
||||
# mkdocs-get-deps
|
||||
# mkdocs-macros-plugin
|
||||
# properdocs
|
||||
# pymdown-extensions
|
||||
# pyyaml-env-tag
|
||||
# zensical
|
||||
pyyaml-env-tag==1.1 \
|
||||
--hash=sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 \
|
||||
--hash=sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff
|
||||
# via
|
||||
# mkdocs
|
||||
# properdocs
|
||||
requests==2.34.2 \
|
||||
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
|
||||
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs-macros-plugin
|
||||
# mkdocs-material
|
||||
# mkdocs-mermaid2-plugin
|
||||
rich==15.0.0 \
|
||||
--hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \
|
||||
--hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36
|
||||
# via neoteroi-mkdocs
|
||||
setuptools==84.2.1 \
|
||||
--hash=sha256:3ff8b3b5492a0aa66960f4419365a86b84e013070d4313e4d4a6894f5d64d6bc \
|
||||
--hash=sha256:ca140b45d5325a9478f880e351484f62fef15f336462e449ca0c03c873d589c2
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# mkdocs-mermaid2-plugin
|
||||
# via mkdocs
|
||||
six==1.17.0 \
|
||||
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
||||
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# python-dateutil
|
||||
smmap==5.0.3 \
|
||||
--hash=sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c \
|
||||
--hash=sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f
|
||||
# via gitdb
|
||||
soupsieve==4.0 \
|
||||
--hash=sha256:5e7494bfce90e06676cfb4c5c2cf22243e98e5cf24646bb1ecdda46d318550a0 \
|
||||
--hash=sha256:ccd6470b256394ac44b08b86fd94b30a17d335a5335c8a3d15565f7b03d3c8cf
|
||||
# via beautifulsoup4
|
||||
super-collections==0.6.2 \
|
||||
--hash=sha256:0c8d8abacd9fad2c7c1c715f036c29f5db213f8cac65f24d45ecba12b4da187a \
|
||||
--hash=sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56
|
||||
# via mkdocs-macros-plugin
|
||||
termcolor==3.3.0 \
|
||||
--hash=sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5 \
|
||||
--hash=sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5
|
||||
# via mkdocs-macros-plugin
|
||||
typing-extensions==4.16.0 \
|
||||
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
|
||||
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# anyio
|
||||
# beautifulsoup4
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via
|
||||
# -c src/backend/requirements.txt
|
||||
# requests
|
||||
tomli==2.5.0 \
|
||||
--hash=sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea \
|
||||
--hash=sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd \
|
||||
--hash=sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0 \
|
||||
--hash=sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391 \
|
||||
--hash=sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df \
|
||||
--hash=sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9 \
|
||||
--hash=sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066 \
|
||||
--hash=sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f \
|
||||
--hash=sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57 \
|
||||
--hash=sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6 \
|
||||
--hash=sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b \
|
||||
--hash=sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3 \
|
||||
--hash=sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043 \
|
||||
--hash=sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01 \
|
||||
--hash=sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646 \
|
||||
--hash=sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859 \
|
||||
--hash=sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b \
|
||||
--hash=sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e \
|
||||
--hash=sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc \
|
||||
--hash=sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5 \
|
||||
--hash=sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0 \
|
||||
--hash=sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb \
|
||||
--hash=sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84 \
|
||||
--hash=sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6 \
|
||||
--hash=sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b \
|
||||
--hash=sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b \
|
||||
--hash=sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52 \
|
||||
--hash=sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd \
|
||||
--hash=sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75 \
|
||||
--hash=sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1 \
|
||||
--hash=sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b \
|
||||
--hash=sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142 \
|
||||
--hash=sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03 \
|
||||
--hash=sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea \
|
||||
--hash=sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885 \
|
||||
--hash=sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374 \
|
||||
--hash=sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3 \
|
||||
--hash=sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276 \
|
||||
--hash=sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b \
|
||||
--hash=sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc \
|
||||
--hash=sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68 \
|
||||
--hash=sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a \
|
||||
--hash=sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f \
|
||||
--hash=sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b \
|
||||
--hash=sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7 \
|
||||
--hash=sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0 \
|
||||
--hash=sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb \
|
||||
--hash=sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7 \
|
||||
--hash=sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545 \
|
||||
--hash=sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8 \
|
||||
--hash=sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980 \
|
||||
--hash=sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7 \
|
||||
--hash=sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105 \
|
||||
--hash=sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5 \
|
||||
--hash=sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56 \
|
||||
--hash=sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d \
|
||||
--hash=sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2 \
|
||||
--hash=sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4 \
|
||||
--hash=sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7 \
|
||||
--hash=sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef \
|
||||
--hash=sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1 \
|
||||
--hash=sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571 \
|
||||
--hash=sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a \
|
||||
--hash=sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442 \
|
||||
--hash=sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc
|
||||
# via zensical
|
||||
watchdog==7.0.0 \
|
||||
--hash=sha256:03a2c85c63cce3cbda9ab6ce21ca401e95b6f681cc6f70316f4f882b3c8f1315 \
|
||||
--hash=sha256:047f5f823286add71294ff1a186c9fc89fea4a2afa2a1d91513b64dbf43fd473 \
|
||||
@@ -786,10 +455,19 @@ watchdog==7.0.0 \
|
||||
--hash=sha256:ea19e8857af78d23b45f80c316e502159443fba2f382b1361ab7d96e3848bb01 \
|
||||
--hash=sha256:edbfd843840fa8a750d79ed69e65ab67965748f9ccfc56ff957e1cee4a8cf796 \
|
||||
--hash=sha256:f06a2d489adcbd6e1f1faa6eb74b167d1f78fc4dc3bccb309a09d0e2cea1fc8e
|
||||
# via
|
||||
# mkdocs
|
||||
# properdocs
|
||||
wcmatch==11.2 \
|
||||
--hash=sha256:1605bde546fbde4c063628c7d89eda486909fd4337e51d73a873987fbcb2554c \
|
||||
--hash=sha256:952bab83c75c233836007a69550a3a8753cf06964c1eb3b84ff11ad5bff1c272
|
||||
# via mkdocs-include-markdown-plugin
|
||||
# via mkdocs
|
||||
zensical==0.1.1 \
|
||||
--hash=sha256:0405d2891dca76a6fb42fae8141dba93a95f31905148940a9f13883bf1ccfe43 \
|
||||
--hash=sha256:1316ba8c5e18b9e46823b0a10105585a582a6e85d2df764b3b65f3de4d1a711f \
|
||||
--hash=sha256:267a8f038ca2c720414c0c5810e29962999fbf799369c06a9530b8c702a28f92 \
|
||||
--hash=sha256:332e74e3fe91cd8476369217e28ab5d4692486da32d0177c8bcb93c972f0cc8b \
|
||||
--hash=sha256:39f40e800f89ce9253839dcba1a06fdd160ff00ac600ab79ae6f18e6a42ee8f7 \
|
||||
--hash=sha256:547d2c5c68db7db1faa4394d99ab81f5e9eed6eefe528eb139ff4ea196b2a4fb \
|
||||
--hash=sha256:d5cff293282dd27bd511f7bd33db8088fd450de4c1611db842a411466140f637 \
|
||||
--hash=sha256:dd4082e317ac5784c50e21cbe209fd1881a351da6ae4e81cc700d6673d5312f3 \
|
||||
--hash=sha256:e11abaf2669c5ee780a145d114e8c3db4cd81cd3fdfc8a4513a324301fefd113
|
||||
# via -r docs/requirements.in
|
||||
zensical-ui==0.0.42 \
|
||||
--hash=sha256:db4f532b45d7f8200d08d5fb8e7945c69dd738eed49417008b847572a8e3ac8d \
|
||||
--hash=sha256:eb824d9c971debc2031837e8d287adf81f5d9ce06304cfbd33fc88ff833abbf3
|
||||
# via zensical
|
||||
+11
-13
@@ -1,19 +1,17 @@
|
||||
version: 2
|
||||
|
||||
mkdocs:
|
||||
configuration: docs/mkdocs.yml
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- requirements: src/backend/requirements.txt
|
||||
|
||||
# The documentation is built with Zensical, which ReadTheDocs does not support
|
||||
# natively, so a custom build is used. The output must be placed in
|
||||
# $READTHEDOCS_OUTPUT/html (see https://docs.readthedocs.com/platform/stable/build-customization.html)
|
||||
build:
|
||||
os: "ubuntu-24.04"
|
||||
tools:
|
||||
python: "3.12"
|
||||
jobs:
|
||||
post_install:
|
||||
- pip install -U invoke
|
||||
- echo "Generating API schema file"
|
||||
- invoke build-docs
|
||||
commands:
|
||||
- pip install -r docs/requirements.txt
|
||||
- pip install -r src/backend/requirements.txt
|
||||
- pip install -U invoke
|
||||
# Generate the API schema and settings definitions, build the site and run the post-build checks
|
||||
- invoke build-docs --build
|
||||
- mkdir -p $READTHEDOCS_OUTPUT/html
|
||||
- cp -r docs/site/. $READTHEDOCS_OUTPUT/html/
|
||||
@@ -111,10 +111,14 @@ ARGV_COMMANDS: dict[str, tuple[str, ...]] = {
|
||||
'schema',
|
||||
'spectactular',
|
||||
# schema adjacent calls
|
||||
'export_settings_definitions',
|
||||
'export_tags',
|
||||
'export_audited_models',
|
||||
'export_filters',
|
||||
'export_fsm',
|
||||
'export_report_context',
|
||||
'export_roles',
|
||||
'export_settings_definitions',
|
||||
'export_status_codes',
|
||||
'export_tags',
|
||||
),
|
||||
# Commands which must not touch the database during the app 'ready' phase
|
||||
'database_excluded': (
|
||||
@@ -188,14 +192,32 @@ def isCollectingPlugins():
|
||||
return cmd_context['collect_plugins']
|
||||
|
||||
|
||||
# Prefix of the RuntimeWarning Django emits when the database is queried before the app registry is ready
|
||||
APPS_NOT_READY_WARNING_MSG = (
|
||||
'Accessing the database during app initialization is discouraged'
|
||||
)
|
||||
|
||||
# This variable is used to cache the result of the isGeneratingSchema function, to prevent multiple executions of the same checks
|
||||
_IS_GENERATING_SCHEMA: bool | None = None
|
||||
|
||||
|
||||
def _setGeneratingSchema(value: bool):
|
||||
"""Set the value of the isGeneratingSchema variable."""
|
||||
"""Set the value of the isGeneratingSchema variable.
|
||||
|
||||
When schema generation is detected, the Django 'APPS_NOT_READY' database warning
|
||||
is suppressed for the remainder of the process. Schema generation (and the
|
||||
'schema adjacent' export commands) intentionally query the database during model
|
||||
import (e.g. to document custom status codes), so this warning is expected noise.
|
||||
These are one-shot CLI processes, so a process-wide filter is safe here.
|
||||
"""
|
||||
global _IS_GENERATING_SCHEMA
|
||||
_IS_GENERATING_SCHEMA = value
|
||||
|
||||
if value:
|
||||
warnings.filterwarnings(
|
||||
'ignore', message=APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
@@ -360,9 +382,7 @@ def ignore_ready_warning(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
message='Accessing the database during app initialization is discouraged',
|
||||
category=RuntimeWarning,
|
||||
'ignore', message=APPS_NOT_READY_WARNING_MSG, category=RuntimeWarning
|
||||
)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -2617,6 +2617,22 @@ def doc_schema(c):
|
||||
run(c, 'python docs/extract_schema.py docs/generated/schema.yml')
|
||||
|
||||
|
||||
# Configuration file for building the documentation with zensical
|
||||
DOCS_CONFIG = 'docs/mkdocs.yml'
|
||||
|
||||
|
||||
def doc_checks(c):
|
||||
"""Check that all settings and status codes are documented.
|
||||
|
||||
Zensical does not support mkdocs hooks, so the 'on_post_build' hook
|
||||
is run separately, after the documentation has been built.
|
||||
"""
|
||||
run(
|
||||
c,
|
||||
'python -c "import sys; sys.path.insert(0, \'docs/docs\'); import hooks; hooks.on_post_build()"',
|
||||
)
|
||||
|
||||
|
||||
@task(
|
||||
help={
|
||||
'address': 'Host and port to run the server on (default: localhost:8080)',
|
||||
@@ -2630,7 +2646,7 @@ def docs_server(
|
||||
compile_schema: bool = False,
|
||||
export_settings: bool = True,
|
||||
):
|
||||
"""Start a local properdocs server to view the documentation."""
|
||||
"""Start a local zensical server to view the documentation."""
|
||||
# Extract settings definitions
|
||||
if export_settings:
|
||||
export_definitions(c, basedir='docs')
|
||||
@@ -2638,23 +2654,27 @@ def docs_server(
|
||||
if compile_schema:
|
||||
doc_schema(c)
|
||||
|
||||
run(c, f'properdocs serve -a {address} -f docs/mkdocs.yml')
|
||||
run(c, f'zensical serve -a {address} -f {DOCS_CONFIG}')
|
||||
|
||||
|
||||
@task(
|
||||
help={'mkdocs': 'Build the documentation using mkdocs at the end (default: False)'}
|
||||
help={
|
||||
'build': 'Build the documentation using zensical at the end (default: False)',
|
||||
'clean': 'Clear the zensical build cache first (default: False)',
|
||||
}
|
||||
)
|
||||
def build_docs(c, mkdocs=False):
|
||||
"""Build the required documents for building the docs. Optionally build the documentation using mkdocs."""
|
||||
def build_docs(c, build=False, clean=False):
|
||||
"""Build the required documents for building the docs. Optionally build the documentation using zensical."""
|
||||
migrate(c)
|
||||
export_definitions(c, basedir='docs')
|
||||
doc_schema(c)
|
||||
|
||||
if mkdocs:
|
||||
run(c, 'properdocs build -f docs/mkdocs.yml')
|
||||
if build:
|
||||
run(c, f'zensical build {"--clean " if clean else ""}-f {DOCS_CONFIG}')
|
||||
doc_checks(c)
|
||||
info('Documentation build complete')
|
||||
else:
|
||||
info('Documentation build complete, but mkdocs not requested')
|
||||
info('Documentation build complete, but zensical build not requested')
|
||||
|
||||
|
||||
@task
|
||||
|
||||
Reference in new issue
Block a user