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.
Files changed (37) hide show
  1. clear_skies_doc_builder-2.0.0/LICENSE +21 -0
  2. clear_skies_doc_builder-2.0.0/PKG-INFO +40 -0
  3. clear_skies_doc_builder-2.0.0/README.md +20 -0
  4. clear_skies_doc_builder-2.0.0/pyproject.toml +70 -0
  5. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/__init__.py +34 -0
  6. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/__init__.py +9 -0
  7. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/attribute_backend.py +90 -0
  8. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/class_backend.py +98 -0
  9. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/module_backend.py +139 -0
  10. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/backends/python.py +0 -0
  11. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/build_callable.py +24 -0
  12. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/__init__.py +9 -0
  13. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/builder.py +136 -0
  14. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/module.py +68 -0
  15. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/single_class.py +78 -0
  16. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/builders/single_class_to_section.py +38 -0
  17. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/__init__.py +17 -0
  18. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/any.py +38 -0
  19. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/attribute.py +34 -0
  20. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/attributes.py +34 -0
  21. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/base_classes.py +31 -0
  22. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/class_column.py +37 -0
  23. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/method.py +0 -0
  24. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/module.py +38 -0
  25. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/columns/module_classes.py +27 -0
  26. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/__init__.py +13 -0
  27. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/arg.py +0 -0
  28. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/attribute.py +14 -0
  29. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/attribute_reference.py +7 -0
  30. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/class_model.py +24 -0
  31. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/class_reference.py +7 -0
  32. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/method.py +19 -0
  33. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/method_reference.py +7 -0
  34. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/module.py +17 -0
  35. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/module_reference.py +7 -0
  36. clear_skies_doc_builder-2.0.0/src/clearskies_doc_builder/models/property.py +0 -0
  37. 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
+ ]
@@ -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
+ ]