Skip to content

Commit f862e0d

Browse files
committed
Refactor component finding logic
This is preparing to implement a finding mechansim for the component openapi-specs.
1 parent 408266a commit f862e0d

3 files changed

Lines changed: 107 additions & 67 deletions

File tree

src/pulp_docs/cli.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import click
66
import git
77
from mkdocs.__main__ import cli as mkdocs_cli
8-
from mkdocs.config import load_config
98

109
from pulp_docs.context import ctx_blog, ctx_docstrings, ctx_draft, ctx_dryrun, ctx_path
11-
from pulp_docs.plugin import load_components
10+
from pulp_docs.plugin import load_components_from
1211

1312

1413
def blog_callback(ctx: click.Context, param: click.Parameter, value: bool) -> bool:
@@ -122,16 +121,11 @@ async def clone_repository(repo_url: str) -> None:
122121
def fetch(dest, config_file, path_exclude):
123122
"""Fetch repositories to destination dir."""
124123
dest_path = Path(dest)
125-
pulpdocs_plugin = load_config(config_file).plugins["PulpDocs"]
126-
all_components = pulpdocs_plugin.config.components
127-
all_repositories_set = {r.git_url for r in all_components if r.git_url}
128-
found_components = load_components(path_exclude, pulpdocs_plugin.config, draft=True)
129-
found_repositories_set = {r.git_url for r in found_components}
130-
final_repositories_set = all_repositories_set - found_repositories_set
131-
124+
_, missing_comps = load_components_from(config_file=config_file)
125+
missing_repos = {comp.git_url for comp in missing_comps}
132126
if not dest_path.exists():
133127
dest_path.mkdir(parents=True)
134-
asyncio.run(clone_repositories(final_repositories_set, dest_path))
128+
asyncio.run(clone_repositories(missing_repos, dest_path))
135129

136130

137131
main = mkdocs_cli

src/pulp_docs/openapi.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from mkdocs.config import load_config
1414

1515
from pulp_docs.cli import get_default_mkdocs
16-
from pulp_docs.plugin import ComponentOption
16+
from pulp_docs.plugin import ComponentDefinition
1717

1818
BASE_TMPDIR_NAME = "pulpdocs_tmp"
1919
CURRENT_DIR = Path(__file__).parent.absolute()
@@ -27,7 +27,7 @@ def filter_plugin(name: str) -> bool:
2727
return True
2828
return name in plugins_filter or name == "pulpcore"
2929

30-
def get_plugins() -> list[ComponentOption]:
30+
def get_plugins() -> list[ComponentDefinition]:
3131
mkdocs_yml = str(get_default_mkdocs())
3232
pulpdocs_plugin = load_config(mkdocs_yml).plugins["PulpDocs"]
3333
all_components = pulpdocs_plugin.config.components
@@ -49,7 +49,7 @@ class OpenAPIGenerator:
4949
dry_run: Whether it should execute the commands or just show them.
5050
"""
5151

52-
def __init__(self, plugins: list[ComponentOption], dry_run=False):
52+
def __init__(self, plugins: list[ComponentDefinition], dry_run=False):
5353
self.pulpcore = next(filter(lambda p: p.name == "pulpcore", plugins))
5454
self.plugins = plugins + [self.pulpcore]
5555
self.dry_run = dry_run
@@ -75,7 +75,7 @@ def generate(self, target_dir: Path):
7575
outfile,
7676
)
7777

78-
def setup_venv(self, plugin: ComponentOption):
78+
def setup_venv(self, plugin: ComponentDefinition):
7979
"""
8080
Creates virtualenv with plugin.
8181
"""

src/pulp_docs/plugin.py

Lines changed: 99 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
from __future__ import annotations
2+
13
import json
24
import sys
35
import tomllib
46
import typing as t
57
from collections import defaultdict
8+
from contextlib import suppress
69
from dataclasses import dataclass
710
from pathlib import Path
811

912
import httpx
1013
import yaml
1114
from git import GitCommandError, Repo
12-
from mkdocs.config import Config, config_options
15+
from mkdocs.config import Config, config_options, load_config
1316
from mkdocs.config.defaults import MkDocsConfig
1417
from mkdocs.exceptions import PluginError
1518
from mkdocs.plugins import BasePlugin, get_plugin_logger
@@ -39,22 +42,98 @@
3942

4043

4144
@config_options.SubConfig
42-
class ComponentOption(Config):
45+
class ComponentDefinition(Config):
4346
title = config_options.Type(str)
4447
path = config_options.Type(str)
4548
kind = config_options.Type(str)
4649
git_url = config_options.Type(str, default="")
4750
rest_api = config_options.Type(str, default="")
4851

4952
@property
50-
def name(self) -> str:
53+
def component_name(self) -> str:
5154
return self.path.rpartition("/")[-1]
5255

56+
@property
57+
def repository_name(self) -> str:
58+
return self.path.split("/")[0]
59+
5360
@property
5461
def label(self) -> str:
5562
return self.rest_api
5663

5764

65+
class ComponentFinder:
66+
def __init__(
67+
self, component_defs: list[ComponentDefinition] | None, lookup_paths: list[str] | None
68+
):
69+
# maps component names to its definition and lookup paths
70+
self.name_to_comp_def: dict[str, ComponentDefinition] = {}
71+
self.name_to_lookup_dirs: dict[str, list[Path]] = defaultdict(list)
72+
# initial population
73+
for comp_def in component_defs or []:
74+
self.add_comp_def(comp_def)
75+
for lookup_path in lookup_paths or []:
76+
self.add_lookup_path(lookup_path)
77+
78+
def add_lookup_path(self, lookup_path: str):
79+
component_name, _, lookup_dir = lookup_path.rpartition("@")
80+
self.name_to_lookup_dirs[component_name].append(lookup_dir)
81+
82+
def add_comp_def(self, comp_def: ComponentDefinition):
83+
self.name_to_comp_def[comp_def.component_name] = comp_def
84+
85+
def load_component(self, comp_name: str) -> Component | None:
86+
comp_def = self.name_to_comp_def[comp_name]
87+
comp_data = dict(comp_def)
88+
lookup_dirs = self.name_to_lookup_dirs[comp_def.component_name]
89+
repository_name = comp_def.repository_name
90+
for lookup_dir in lookup_dirs:
91+
comp_dir = lookup_dir / comp_def.path
92+
if comp_dir.exists():
93+
comp_data["version"] = self._get_comp_version(comp_dir)
94+
comp_data["repository_dir"] = lookup_dir / repository_name
95+
comp_data["component_dir"] = comp_dir
96+
return Component(**comp_data)
97+
return None
98+
99+
def load_all(self) -> tuple[list[Component], list[ComponentDefinition]]:
100+
loaded_comps = []
101+
missing_comps = []
102+
for comp_name, comp_opt in self.name_to_comp_def.items():
103+
component = self.load_component(comp_name)
104+
if component:
105+
loaded_comps.append(component)
106+
else:
107+
missing_comps.append(component)
108+
return missing_comps, loaded_comps
109+
110+
def _get_comp_version(self, comp_dir: Path) -> str:
111+
with suppress(Exception):
112+
pyproject = comp_dir / "pyproject.toml"
113+
return tomllib.loads(pyproject.read_text())["project"]["version"]
114+
return "unknown"
115+
116+
117+
def load_components_from(
118+
config_file: Path | None = None,
119+
pulpdocs_plugin: PulpDocsPlugin | None = None,
120+
lookup_paths: list[str] | None = None,
121+
) -> tuple[list[Component], list[ComponentDefinition]]:
122+
"""Load all components defined by pulp-docs using lookup_paths."""
123+
if bool(config_file) is bool(pulpdocs_plugin):
124+
raise ValueError("Provide exactly one of 'config_file' or 'pulpdocs_plugin'.")
125+
_lookup_paths = lookup_paths or [str(Path().cwd().parent)]
126+
if config_file:
127+
_pulpdocs_plugin = load_config(str(config_file)).plugins["PulpDocs"]
128+
else:
129+
_pulpdocs_plugin = pulpdocs_plugin
130+
131+
component_defs = _pulpdocs_plugin.config.components
132+
comp_finder = ComponentFinder(component_defs, _lookup_paths)
133+
loaded, missing = comp_finder.load_all()
134+
return loaded, missing
135+
136+
58137
@dataclass(frozen=True)
59138
class Component:
60139
title: str
@@ -67,32 +146,9 @@ class Component:
67146
repository_dir: Path
68147
component_dir: Path
69148

70-
@classmethod
71-
def build(cls, find_path: list[str], component_opt: ComponentOption):
72-
body = dict(component_opt)
73-
repository_name = component_opt.path.split("/")[0]
74-
for dir_spec in find_path:
75-
repo_filter, _, basedir = dir_spec.rpartition("@")
76-
if repo_filter and repo_filter != repository_name:
77-
continue
78-
basedir = Path(basedir)
79-
component_dir = basedir / component_opt.path
80-
if component_dir.exists():
81-
version = "unknown"
82-
try:
83-
pyproject = component_dir / "pyproject.toml"
84-
version = tomllib.loads(pyproject.read_text())["project"]["version"]
85-
except Exception:
86-
pass
87-
body["version"] = version
88-
body["repository_dir"] = basedir / repository_name
89-
body["component_dir"] = component_dir
90-
return cls(**body)
91-
return None
92-
93149

94150
class PulpDocsPluginConfig(Config):
95-
components = config_options.ListOfItems(ComponentOption, default=[])
151+
components = config_options.ListOfItems(ComponentDefinition, default=[])
96152

97153

98154
class ComponentNav:
@@ -272,23 +328,6 @@ def rss_items() -> list:
272328
return rss_feed["items"][:20]
273329

274330

275-
def load_components(find_path: list[str], config: PulpDocsPluginConfig, draft: bool):
276-
loaded_components = []
277-
for component_opt in config.components:
278-
component = Component.build(find_path, component_opt)
279-
if component:
280-
loaded_components.append(component)
281-
all_components = {o.path for o in config.components}
282-
missing_components = all_components.difference({o.path for o in loaded_components})
283-
if not missing_components:
284-
return loaded_components
285-
# handle missing_components case
286-
missing_components = sorted(missing_components)
287-
if not draft:
288-
raise PluginError(f"Components missing: {missing_components}.")
289-
return loaded_components
290-
291-
292331
def log_pulp_config(
293332
mkdocs_file: str, path: list[str], loaded_components: list[Component], site_dir: str
294333
):
@@ -317,25 +356,32 @@ def get_pulpdocs_git_url(config: PulpDocsPluginConfig):
317356
class PulpDocsPlugin(BasePlugin[PulpDocsPluginConfig]):
318357
def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
319358
# mkdocs may default to the installation dir
359+
self.mkdocs_yml_dir = Path(config.docs_dir).parent
320360
if "site-packages" in config.site_dir:
321361
config.site_dir = str(Path.cwd() / "site")
322362

323363
self.blog = ctx_blog.get()
324364
self.docstrings = ctx_docstrings.get()
325365
self.draft = ctx_draft.get()
326366
self.dryrun = ctx_dryrun.get()
327-
328-
self.mkdocs_yml_dir = Path(config.docs_dir).parent
329-
self.find_path = ctx_path.get() or [str(Path().cwd().parent)]
330-
self.loaded_components = load_components(self.find_path, self.config, self.draft)
331367
self.pulpdocs_git_url = get_pulpdocs_git_url(self.config)
332368

369+
# Load components
370+
lookup_paths = ctx_path.get() or None
371+
loaded_comps, missing_comps = load_components_from(
372+
pulpdocs_plugin=self, lookup_paths=lookup_paths
373+
)
374+
if missing_comps and not self.draft:
375+
missing_comps = sorted(missing_comps)
376+
raise PluginError(f"Components missing: {missing_comps}.")
377+
self.loaded_comps = loaded_comps
378+
333379
mkdocs_file = self.mkdocs_yml_dir / "mkdocs.yml"
334-
log_pulp_config(mkdocs_file, self.find_path, self.loaded_components, config.site_dir)
380+
log_pulp_config(mkdocs_file, self.find_path, self.loaded_comps, config.site_dir)
335381

336382
mkdocstrings_config = config.plugins["mkdocstrings"].config
337383
components_var = []
338-
for component in self.loaded_components:
384+
for component in self.loaded_comps:
339385
components_var.append(get_component_data(component))
340386
config.watch.append(str(component.component_dir / "docs"))
341387
component_dir = component.component_dir.resolve()
@@ -359,8 +405,8 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
359405
return config
360406

361407
def on_files(self, files: Files, /, *, config: MkDocsConfig) -> Files | None:
362-
log.info(f"Loading Pulp components: {self.loaded_components}")
363-
pulp_docs_component = [c for c in self.loaded_components if c.path == "pulp-docs"]
408+
log.info(f"Loading Pulp components: {self.loaded_comps}")
409+
pulp_docs_component = [c for c in self.loaded_comps if c.path == "pulp-docs"]
364410
if pulp_docs_component:
365411
pulp_docs_git = Repo(pulp_docs_component[0].repository_dir)
366412
else:
@@ -369,7 +415,7 @@ def on_files(self, files: Files, /, *, config: MkDocsConfig) -> Files | None:
369415

370416
user_nav: dict[str, t.Any] = {}
371417
dev_nav: dict[str, t.Any] = {}
372-
for component in self.loaded_components:
418+
for component in self.loaded_comps:
373419
component_dir = component.component_dir
374420

375421
log.info(f"Fetching docs from '{component.title}'.")

0 commit comments

Comments
 (0)