clear-skies-doc-builder 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clear_skies_doc_builder-2.0.0/LICENSE +21 -0
- clear_skies_doc_builder-2.0.0/PKG-INFO +40 -0
- clear_skies_doc_builder-2.0.0/README.md +20 -0
- clear_skies_doc_builder-2.0.0/pyproject.toml +70 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/__init__.py +34 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/__init__.py +9 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/attribute_backend.py +90 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/class_backend.py +98 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/module_backend.py +139 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/python.py +0 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/build_callable.py +24 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/__init__.py +9 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/builder.py +136 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/module.py +68 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/single_class.py +78 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/single_class_to_section.py +38 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/__init__.py +17 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/any.py +38 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/attribute.py +34 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/attributes.py +34 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/base_classes.py +31 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/class_column.py +37 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/method.py +0 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/module.py +38 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/module_classes.py +27 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/__init__.py +13 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/arg.py +0 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/attribute.py +14 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/attribute_reference.py +7 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/class_model.py +24 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/class_reference.py +7 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/method.py +19 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/method_reference.py +7 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/module.py +17 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/module_reference.py +7 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/property.py +0 -0
- clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/prepare_doc_space.py +22 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 clearskies-py
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: clear-skies-doc-builder
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: The docbuilder for all 'official' clearskies plugins (as well as the main clearskies docs)
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Conor Mancone
|
|
7
|
+
Author-email: cmancone@gmail.com
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Dist: clear-skies (>=2.0.0,<3.0.0)
|
|
17
|
+
Project-URL: Repository, https://github.com/clearskies-py/docs
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# docs
|
|
21
|
+
|
|
22
|
+
The documentation builder for clearskies and related plugins
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
Each "official" clearskies module (including the core clearskies library itself) has the documentation primarily written in the codebase as docblocks. The documentation site is then built by extracting these docblocks and stitching them together. To be clear, this isn't about the low-level "API" documentation that describes every single class/method in the framework. Rather, this is about the primary documentation site itself (clearskies.info) which is focused on high-level use cases and examples of the primary configuration options. As a result, it's not a simple matter of just iterating over the classes/methods and building documentation. To build a coherent documentation site, each plugin has a configuration file that basically outlines the final "structure" or organization of the resulting documentation, as well as the name of a builder class that will combine that configuration information with the codebase itself to create the actual docs.
|
|
27
|
+
|
|
28
|
+
The docs themselves (in the source code) are all written with markdown. This documentation builder then takes that markdwon and adds the necessary headers/etc so to make them valid files for [Jekyll](https://jekyllrb.com/), the builder for the current documentation site. The site itself is hosted in S3, so building an actual documentation site means:
|
|
29
|
+
|
|
30
|
+
1. Properly documenting everything inside of the source code via markdown.
|
|
31
|
+
2. Creating a config file (`docs/python/config.json`) to map source code docs to Jekyll files.
|
|
32
|
+
3. Creating a skeleton of a Jekyll site in the `doc/jekyll` folder of the plugin.
|
|
33
|
+
4. Installing this doc builder via `poetry add clear-skies-doc-builder`.
|
|
34
|
+
5. Run the doc builder.
|
|
35
|
+
6. Build with Jekyll.
|
|
36
|
+
7. Push to the appropriate subfolder via S3.
|
|
37
|
+
8. (Only once) Update the main clearskies doc site to know about the new subfolder for this plugin.
|
|
38
|
+
|
|
39
|
+
Of course, we want the Jekyll sites to be consistent with eachother in terms of style/look. In the long run we'll probably have this doc builder also bootstrap the Jekyll site, but for now you just have to manually setup the Jekyll build using the main clearskies repo as a template.
|
|
40
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# docs
|
|
2
|
+
|
|
3
|
+
The documentation builder for clearskies and related plugins
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Each "official" clearskies module (including the core clearskies library itself) has the documentation primarily written in the codebase as docblocks. The documentation site is then built by extracting these docblocks and stitching them together. To be clear, this isn't about the low-level "API" documentation that describes every single class/method in the framework. Rather, this is about the primary documentation site itself (clearskies.info) which is focused on high-level use cases and examples of the primary configuration options. As a result, it's not a simple matter of just iterating over the classes/methods and building documentation. To build a coherent documentation site, each plugin has a configuration file that basically outlines the final "structure" or organization of the resulting documentation, as well as the name of a builder class that will combine that configuration information with the codebase itself to create the actual docs.
|
|
8
|
+
|
|
9
|
+
The docs themselves (in the source code) are all written with markdown. This documentation builder then takes that markdwon and adds the necessary headers/etc so to make them valid files for [Jekyll](https://jekyllrb.com/), the builder for the current documentation site. The site itself is hosted in S3, so building an actual documentation site means:
|
|
10
|
+
|
|
11
|
+
1. Properly documenting everything inside of the source code via markdown.
|
|
12
|
+
2. Creating a config file (`docs/python/config.json`) to map source code docs to Jekyll files.
|
|
13
|
+
3. Creating a skeleton of a Jekyll site in the `doc/jekyll` folder of the plugin.
|
|
14
|
+
4. Installing this doc builder via `poetry add clear-skies-doc-builder`.
|
|
15
|
+
5. Run the doc builder.
|
|
16
|
+
6. Build with Jekyll.
|
|
17
|
+
7. Push to the appropriate subfolder via S3.
|
|
18
|
+
8. (Only once) Update the main clearskies doc site to know about the new subfolder for this plugin.
|
|
19
|
+
|
|
20
|
+
Of course, we want the Jekyll sites to be consistent with eachother in terms of style/look. In the long run we'll probably have this doc builder also bootstrap the Jekyll site, but for now you just have to manually setup the Jekyll build using the main clearskies repo as a template.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "clear-skies-doc-builder"
|
|
3
|
+
description = "The docbuilder for all 'official' clearskies plugins (as well as the main clearskies docs)"
|
|
4
|
+
version = "v2.0.0"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
dynamic = ["classifiers"]
|
|
7
|
+
readme = "./README.md"
|
|
8
|
+
authors = [{name = "Conor Mancone", email = "cmancone@gmail.com"}]
|
|
9
|
+
requires-python = '>=3.11,<4.0'
|
|
10
|
+
dependencies = ['clear-skies (>=2.0.0,<3.0.0)']
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
repository = "https://github.com/clearskies-py/docs"
|
|
14
|
+
|
|
15
|
+
[tool.poetry]
|
|
16
|
+
packages = [
|
|
17
|
+
{ include = "clearskies_doc_builder", from = "src" }
|
|
18
|
+
]
|
|
19
|
+
exclude = [
|
|
20
|
+
"src/clearskies_doc_builder/*_test.py",
|
|
21
|
+
"src/clearskies_doc_builder/**/*_test.py",
|
|
22
|
+
"src/clearskies_doc_builder/integration_tests/*"
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 5 - Production/Stable",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Intended Audience :: Developers"
|
|
29
|
+
]
|
|
30
|
+
requires-poetry = '>=2.0,<3.0'
|
|
31
|
+
|
|
32
|
+
[[tool.poetry.source]]
|
|
33
|
+
name = "PyPI"
|
|
34
|
+
priority = "primary"
|
|
35
|
+
|
|
36
|
+
[tool.poetry.group.dev.dependencies]
|
|
37
|
+
black = "^25.1.0"
|
|
38
|
+
mypy = "^1.16.1"
|
|
39
|
+
pre-commit = "^3.8.0"
|
|
40
|
+
pytest = "^8.4.1"
|
|
41
|
+
ruff = "^0.12.1"
|
|
42
|
+
types-dateparser = "^1.2.2.20250627"
|
|
43
|
+
types-jwcrypto = "^1.5.0.20250516"
|
|
44
|
+
types-pymysql = "^1.1.0.20250516"
|
|
45
|
+
types-requests = "^2.32.4.20250611"
|
|
46
|
+
|
|
47
|
+
[tool.pytest]
|
|
48
|
+
addopts = "--ignore=src/clearskies/contexts/test.py --cache-clear"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ['poetry-core (>=2.0,<3.0)']
|
|
52
|
+
build-backend = "poetry.core.masonry.api"
|
|
53
|
+
|
|
54
|
+
[tool.black]
|
|
55
|
+
line-length = 120
|
|
56
|
+
# The following is Black's default, but it's good to be explicit
|
|
57
|
+
# to match your Ruff config.
|
|
58
|
+
skip-magic-trailing-comma = false
|
|
59
|
+
preview = true
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.11"
|
|
63
|
+
|
|
64
|
+
exclude = [
|
|
65
|
+
".*_test\\.py$",
|
|
66
|
+
"docs/.*"
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.poetry-git-version-plugin]
|
|
70
|
+
release_type = "tag"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import clearskies
|
|
6
|
+
from clearskies_doc_builder import models, backends
|
|
7
|
+
from clearskies_doc_builder.build_callable import build_callable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build(build_file_string: str) -> None:
|
|
11
|
+
# We assume a folder structure here where the repo root contains a `src/` folder and a `docs/python` folder.
|
|
12
|
+
# `build_file_string` should contain the absolute path to the file that kicked this off, which should
|
|
13
|
+
# live in the `docs/python` folder. This comes in as a string, which we convert to a path.
|
|
14
|
+
# We then also need to calculate the path to the `src/` folder and add that to our
|
|
15
|
+
# python path. We do this because we want to import the clearskies module in question, since the python
|
|
16
|
+
# code is where all of our documentation lives.
|
|
17
|
+
|
|
18
|
+
doc_python_path = pathlib.Path(build_file_string).parents[0]
|
|
19
|
+
project_root = doc_python_path.parents[1]
|
|
20
|
+
sys.path.append(str(project_root / "src"))
|
|
21
|
+
|
|
22
|
+
config_file = open(str(doc_python_path / "config.json"), "r")
|
|
23
|
+
config = json.loads(config_file.read())
|
|
24
|
+
config_file.close()
|
|
25
|
+
|
|
26
|
+
cli = clearskies.contexts.Cli(
|
|
27
|
+
build_callable,
|
|
28
|
+
modules=[models, backends],
|
|
29
|
+
bindings={
|
|
30
|
+
"config": config,
|
|
31
|
+
"project_root": project_root / "docs",
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
cli()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from clearskies_doc_builder.backends.attribute_backend import AttributeBackend
|
|
2
|
+
from clearskies_doc_builder.backends.class_backend import ClassBackend
|
|
3
|
+
from clearskies_doc_builder.backends.module_backend import ModuleBackend
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AttributeBackend",
|
|
7
|
+
"ClassBackend",
|
|
8
|
+
"ModuleBackend",
|
|
9
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import clearskies
|
|
5
|
+
import clearskies.model
|
|
6
|
+
import clearskies.column
|
|
7
|
+
import clearskies.query
|
|
8
|
+
from clearskies_doc_builder.backends.module_backend import ModuleBackend
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AttributeBackend(ModuleBackend):
|
|
12
|
+
_search_functions = {
|
|
13
|
+
"id": lambda attribute, name, value: id(attribute) == int(value), # type: ignore
|
|
14
|
+
"name": lambda attribute, name, value: name == value, # type: ignore
|
|
15
|
+
"type": lambda attribute, name, value: attribute.__class__ == value, # type: ignore
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def records(
|
|
19
|
+
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
20
|
+
) -> list[dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Returns a list of records that match the given query configuration
|
|
23
|
+
|
|
24
|
+
next_page_data is used to return data to the caller. Pass in an empty dictionary, and it will be populated
|
|
25
|
+
with the data needed to return the next page of results. If it is still an empty dictionary when returned,
|
|
26
|
+
then there is no additional data.
|
|
27
|
+
"""
|
|
28
|
+
disallowed = ["joins", "selects", "group_by"]
|
|
29
|
+
for attribute_name in disallowed:
|
|
30
|
+
if getattr(query, attribute_name):
|
|
31
|
+
raise ValueError(f"The AttributeBackend received {attribute_name} in a query but doesn't support this.")
|
|
32
|
+
|
|
33
|
+
for condition in query.conditions:
|
|
34
|
+
if condition.operator != "=":
|
|
35
|
+
raise ValueError("The AttributeBackend only supports searching with the equals operator")
|
|
36
|
+
|
|
37
|
+
if "parent_class" not in query.conditions_by_column:
|
|
38
|
+
raise ValueError("When searching for attributes you must include a condition on 'parent_class'")
|
|
39
|
+
|
|
40
|
+
parent_class = query.conditions_by_column["parent_class"][0].values[0]
|
|
41
|
+
matching_attributes = []
|
|
42
|
+
for name in dir(parent_class):
|
|
43
|
+
attribute = getattr(parent_class, name)
|
|
44
|
+
matches = True
|
|
45
|
+
for condition in query.conditions:
|
|
46
|
+
if condition.column_name not in self._search_functions:
|
|
47
|
+
continue
|
|
48
|
+
if not self._search_functions[condition.column_name](attribute, name, condition.values[0]): # type: ignore
|
|
49
|
+
matches = False
|
|
50
|
+
|
|
51
|
+
if not matches:
|
|
52
|
+
continue
|
|
53
|
+
matching_attributes.append(self.unpack(attribute, name, parent_class))
|
|
54
|
+
|
|
55
|
+
return self.paginate(matching_attributes, query)
|
|
56
|
+
|
|
57
|
+
def unpack(self, attribute: Any, name: str, parent_class: type) -> dict[str, Any]: # type: ignore
|
|
58
|
+
all_args = []
|
|
59
|
+
args = []
|
|
60
|
+
kwargs = []
|
|
61
|
+
defaults: dict[str, Any] = {}
|
|
62
|
+
argdata = None
|
|
63
|
+
try:
|
|
64
|
+
argdata = inspect.getfullargspec(attribute)
|
|
65
|
+
except:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
if argdata:
|
|
69
|
+
nargs = len(argdata.args)
|
|
70
|
+
nkwargs = len(argdata.defaults) if argdata.defaults else 0
|
|
71
|
+
npargs = nargs - nkwargs
|
|
72
|
+
all_args = argdata.args
|
|
73
|
+
kwargs = all_args[nargs - nkwargs :]
|
|
74
|
+
args = all_args[:nkwargs]
|
|
75
|
+
defaults = {}
|
|
76
|
+
if argdata.defaults:
|
|
77
|
+
defaults = {argdata.args[index + npargs]: default for (index, default) in enumerate(argdata.defaults)}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"id": id(attribute),
|
|
81
|
+
"name": name,
|
|
82
|
+
"type": attribute.__class__,
|
|
83
|
+
"doc": attribute.__doc__,
|
|
84
|
+
"parent_class": parent_class,
|
|
85
|
+
"attribute": attribute,
|
|
86
|
+
"all_args": all_args,
|
|
87
|
+
"args": args,
|
|
88
|
+
"kwargs": kwargs,
|
|
89
|
+
"defaults": defaults,
|
|
90
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
import clearskies
|
|
7
|
+
import clearskies.model
|
|
8
|
+
import clearskies.column
|
|
9
|
+
import clearskies.query
|
|
10
|
+
from clearskies_doc_builder.backends.module_backend import ModuleBackend
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ClassBackend(ModuleBackend):
|
|
14
|
+
_search_functions = {
|
|
15
|
+
"id": lambda module, value: id(module) == int(value),
|
|
16
|
+
"source_file": lambda module, value: (module.__file__ if hasattr(module, "__file__") else "") == value,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def records(
|
|
20
|
+
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
21
|
+
) -> list[dict[str, Any]]:
|
|
22
|
+
"""
|
|
23
|
+
Returns a list of records that match the given query configuration
|
|
24
|
+
|
|
25
|
+
next_page_data is used to return data to the caller. Pass in an empty dictionary, and it will be populated
|
|
26
|
+
with the data needed to return the next page of results. If it is still an empty dictionary when returned,
|
|
27
|
+
then there is no additional data.
|
|
28
|
+
"""
|
|
29
|
+
disallowed = ["joins", "selects", "group_by"]
|
|
30
|
+
for attribute_name in disallowed:
|
|
31
|
+
if getattr(query, attribute_name):
|
|
32
|
+
raise ValueError(f"The ClassBackend received {attribute_name} in a query but doesn't support this.")
|
|
33
|
+
|
|
34
|
+
for condition in query.conditions:
|
|
35
|
+
if condition.operator != "=":
|
|
36
|
+
raise ValueError("The ClassBackend only supports searching with the equals operator")
|
|
37
|
+
|
|
38
|
+
if "import_path" in query.conditions_by_column:
|
|
39
|
+
import_path = query.conditions_by_column["import_path"][0].values[0]
|
|
40
|
+
path_parts = import_path.split(".")
|
|
41
|
+
if len(path_parts) < 2:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
'In order to search for classes by import path you must provide the module and class name, e.g. `classes.find("import_path=clearskies.Endpoint")`'
|
|
44
|
+
)
|
|
45
|
+
class_name = path_parts[-1]
|
|
46
|
+
module_path = ".".join(path_parts[0:-1])
|
|
47
|
+
module = importlib.import_module(module_path)
|
|
48
|
+
if not hasattr(module, class_name):
|
|
49
|
+
raise ValueError(f"Module {import_path} has no class named {class_name}")
|
|
50
|
+
Class = getattr(module, class_name)
|
|
51
|
+
if not inspect.isclass(Class):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"I was asked to import the class named '{import_path}' but this doesn't actually reference a class"
|
|
54
|
+
)
|
|
55
|
+
return [self.unpack(Class, module)]
|
|
56
|
+
|
|
57
|
+
if "module" not in query.conditions_by_column:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"When searching for classes you must include a condition on either 'module' or 'import_path'"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
parent_module = query.conditions_by_column["module"][0].values[0]
|
|
63
|
+
matching_classes = []
|
|
64
|
+
for name in dir(parent_module):
|
|
65
|
+
attribute = getattr(parent_module, name)
|
|
66
|
+
if not inspect.isclass(attribute):
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
matches = True
|
|
70
|
+
for condition in query.conditions:
|
|
71
|
+
if condition.column_name not in self._search_functions:
|
|
72
|
+
continue
|
|
73
|
+
if not self._search_functions[condition.column_name](attribute, condition.values[0]):
|
|
74
|
+
matches = False
|
|
75
|
+
|
|
76
|
+
if not matches:
|
|
77
|
+
continue
|
|
78
|
+
matching_classes.append(self.unpack(attribute, parent_module))
|
|
79
|
+
|
|
80
|
+
return self.paginate(matching_classes, query)
|
|
81
|
+
|
|
82
|
+
def unpack(self, Class: type, module: ModuleType) -> dict[str, Any]: # type: ignore
|
|
83
|
+
source_file = ""
|
|
84
|
+
try:
|
|
85
|
+
# this fails for built ins
|
|
86
|
+
source_file = inspect.getfile(Class)
|
|
87
|
+
except TypeError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"id": id(Class),
|
|
92
|
+
"import_path": Class.__module__ + "." + Class.__name__,
|
|
93
|
+
"name": Class.__name__,
|
|
94
|
+
"source_file": source_file,
|
|
95
|
+
"doc": Class.__doc__,
|
|
96
|
+
"module": module,
|
|
97
|
+
"type": Class,
|
|
98
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from typing import Any, Callable
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
import sys
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
import clearskies
|
|
7
|
+
import clearskies.model
|
|
8
|
+
import clearskies.column
|
|
9
|
+
import clearskies.query
|
|
10
|
+
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
11
|
+
from clearskies.autodoc.schema import Integer as AutoDocInteger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ModuleBackend(clearskies.backends.Backend):
|
|
15
|
+
_search_functions = {
|
|
16
|
+
"id": lambda module, value: id(module) == int(value),
|
|
17
|
+
"is_builtin": lambda module, value: (0 if hasattr(module, "__file__") else 1) == int(value),
|
|
18
|
+
"source_file": lambda module, value: (module.__file__ if hasattr(module, "__file__") else "") == value,
|
|
19
|
+
"module": lambda module, value: id(module) == id(value),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def update(self, id: int | str, data: dict[str, Any], model: clearskies.model.Model) -> dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Updates the record with the given id with the information from the data dictionary
|
|
25
|
+
"""
|
|
26
|
+
raise Exception(f"The {self.__class__.__name__} only supports read operations: update is not allowed")
|
|
27
|
+
|
|
28
|
+
def create(self, data: dict[str, Any], model: clearskies.model.Model) -> dict[str, Any]:
|
|
29
|
+
"""
|
|
30
|
+
Creates a record with the information from the data dictionary
|
|
31
|
+
"""
|
|
32
|
+
raise Exception(f"The {self.__class__.__name__} only supports read operations: create is not allowed")
|
|
33
|
+
|
|
34
|
+
def delete(self, id: int | str, model: clearskies.model.Model) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Deletes the record with the given id
|
|
37
|
+
"""
|
|
38
|
+
raise Exception(f"The {self.__class__.__name__} only supports read operations: delete is not allowed")
|
|
39
|
+
|
|
40
|
+
def count(self, query: clearskies.query.Query) -> int:
|
|
41
|
+
"""
|
|
42
|
+
Returns the number of records which match the given query configuration
|
|
43
|
+
"""
|
|
44
|
+
return len(self.records(query))
|
|
45
|
+
|
|
46
|
+
def records(
|
|
47
|
+
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
48
|
+
) -> list[dict[str, Any]]:
|
|
49
|
+
"""
|
|
50
|
+
Returns a list of records that match the given query configuration
|
|
51
|
+
|
|
52
|
+
next_page_data is used to return data to the caller. Pass in an empty dictionary, and it will be populated
|
|
53
|
+
with the data needed to return the next page of results. If it is still an empty dictionary when returned,
|
|
54
|
+
then there is no additional data.
|
|
55
|
+
"""
|
|
56
|
+
disallowed = ["joins", "selects", "group_by"]
|
|
57
|
+
for attribute_name in disallowed:
|
|
58
|
+
if getattr(query, attribute_name):
|
|
59
|
+
raise ValueError(f"The ModuleBackend received {attribute_name} in a query but doesn't support this.")
|
|
60
|
+
|
|
61
|
+
for condition in query.conditions:
|
|
62
|
+
if condition.operator != "=":
|
|
63
|
+
raise ValueError("The ModuleBackend only supports searching with the equals operator")
|
|
64
|
+
|
|
65
|
+
module_name_condition = query.conditions_by_column.get("import_path", query.conditions_by_column.get("name"))
|
|
66
|
+
if module_name_condition:
|
|
67
|
+
module_name = module_name_condition[0].values[0]
|
|
68
|
+
module = importlib.import_module(module_name)
|
|
69
|
+
return [self.unpack(module)]
|
|
70
|
+
|
|
71
|
+
matching_modules = []
|
|
72
|
+
module_names = set(sys.modules) & set(globals())
|
|
73
|
+
for module_name in module_names:
|
|
74
|
+
if module_name not in sys.modules:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
module = sys.modules[module_name]
|
|
78
|
+
matches = True
|
|
79
|
+
for condition in query.conditions:
|
|
80
|
+
if condition.column_name not in self._search_functions:
|
|
81
|
+
continue
|
|
82
|
+
if not self._search_functions[condition.column_name](module, condition.values[0]):
|
|
83
|
+
matches = False
|
|
84
|
+
|
|
85
|
+
if not matches:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
matching_modules.append(self.unpack(module))
|
|
89
|
+
|
|
90
|
+
return self.paginate(matching_modules, query)
|
|
91
|
+
|
|
92
|
+
def unpack(self, module: ModuleType) -> dict[str, Any]:
|
|
93
|
+
return {
|
|
94
|
+
"id": id(module),
|
|
95
|
+
"import_path": module.__name__,
|
|
96
|
+
"name": module.__name__,
|
|
97
|
+
"is_builtin": not hasattr(module, "__file__"),
|
|
98
|
+
"source_file": module.__file__ if hasattr(module, "__file__") else "",
|
|
99
|
+
"doc": module.__doc__,
|
|
100
|
+
"module": module,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def paginate(self, records, query):
|
|
104
|
+
return records
|
|
105
|
+
|
|
106
|
+
def validate_pagination_data(self, data: dict[str, Any], case_mapping: Callable) -> str:
|
|
107
|
+
extra_keys = set(data.keys()) - set(self.allowed_pagination_keys())
|
|
108
|
+
if len(extra_keys):
|
|
109
|
+
key_name = case_mapping("start")
|
|
110
|
+
return "Invalid pagination key(s): '" + "','".join(extra_keys) + f"'. Only '{key_name}' is allowed"
|
|
111
|
+
if "start" not in data:
|
|
112
|
+
key_name = case_mapping("start")
|
|
113
|
+
return f"You must specify '{key_name}' when setting pagination"
|
|
114
|
+
start = data["start"]
|
|
115
|
+
try:
|
|
116
|
+
start = int(start)
|
|
117
|
+
except:
|
|
118
|
+
key_name = case_mapping("start")
|
|
119
|
+
return f"Invalid pagination data: '{key_name}' must be a number"
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
def allowed_pagination_keys(self) -> list[str]:
|
|
123
|
+
return ["start"]
|
|
124
|
+
|
|
125
|
+
def documentation_pagination_next_page_response(self, case_mapping: Callable[[str], str]) -> list[Any]:
|
|
126
|
+
return [AutoDocInteger(case_mapping("start"), example=0)]
|
|
127
|
+
|
|
128
|
+
def documentation_pagination_next_page_example(self, case_mapping: Callable[[str], str]) -> dict[str, Any]:
|
|
129
|
+
return {case_mapping("start"): 0}
|
|
130
|
+
|
|
131
|
+
def documentation_pagination_parameters(
|
|
132
|
+
self, case_mapping: Callable[[str], str]
|
|
133
|
+
) -> list[tuple[AutoDocSchema, str]]:
|
|
134
|
+
return [
|
|
135
|
+
(
|
|
136
|
+
AutoDocInteger(case_mapping("start"), example=0),
|
|
137
|
+
"The zero-indexed record number to start listing results from",
|
|
138
|
+
)
|
|
139
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from clearskies_doc_builder import models
|
|
4
|
+
from clearskies_doc_builder.prepare_doc_space import prepare_doc_space
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_callable(modules: models.Module, classes: models.Class, config: dict[str, Any], project_root: str):
|
|
8
|
+
doc_root = prepare_doc_space(project_root)
|
|
9
|
+
nav_order_parent_count = {}
|
|
10
|
+
|
|
11
|
+
for index, branch in enumerate(config["tree"]):
|
|
12
|
+
nav_order_title_tracker = branch.get("parent", branch["title"])
|
|
13
|
+
if nav_order_title_tracker not in nav_order_parent_count:
|
|
14
|
+
nav_order_parent_count[nav_order_title_tracker] = 0
|
|
15
|
+
nav_order_parent_count[nav_order_title_tracker] += 1
|
|
16
|
+
builder_class = classes.find("import_path=" + branch["builder"]).type
|
|
17
|
+
builder = builder_class(
|
|
18
|
+
branch,
|
|
19
|
+
modules,
|
|
20
|
+
classes,
|
|
21
|
+
doc_root,
|
|
22
|
+
nav_order=nav_order_parent_count[nav_order_title_tracker] if branch.get("parent") else index + 2,
|
|
23
|
+
)
|
|
24
|
+
builder.build()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from clearskies_doc_builder.builders.module import Module
|
|
2
|
+
from clearskies_doc_builder.builders.single_class import SingleClass
|
|
3
|
+
from clearskies_doc_builder.builders.single_class_to_section import SingleClassToSection
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Module",
|
|
7
|
+
"SingleClass",
|
|
8
|
+
"SingleClassToSection",
|
|
9
|
+
]
|