Skip to content

Commit

Permalink
module_cache
Browse files Browse the repository at this point in the history
  • Loading branch information
daizutabi committed Feb 10, 2024
1 parent c7fc0e4 commit 5f35740
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 93 deletions.
6 changes: 3 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ nav:
- usage/config.md
- API: $api/mkapi.**
- Examples: $api/examples.**
# - Schemdraw: $api/schemdraw.***
# - Polars: $api/polars.***
# - Altair: $api/altair.***
- Schemdraw: $api/schemdraw.***
- Polars: $api/polars.***
- Altair: $api/altair.***
extra_css:
- stylesheets/extra.css
watch:
Expand Down
2 changes: 2 additions & 0 deletions src/mkapi/importlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@
def cache_clear() -> None:
"""Clear cache.
- mkapi.importlib.get_module_node_source
- mkapi.objects.objects
- mkapi.importlib.load_module
"""
get_module_node_source.cache_clear()
load_module.cache_clear()
objects.clear()

Expand Down
64 changes: 19 additions & 45 deletions src/mkapi/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from mkapi.importlib import get_object
from mkapi.objects import Module, is_empty, iter_objects, iter_objects_with_depth
from mkapi.renderers import get_object_filter_for_source
from mkapi.utils import split_filters, update_filters
from mkapi.utils import is_module_cache_dirty, split_filters

if TYPE_CHECKING:
from collections.abc import Callable
Expand All @@ -27,7 +27,8 @@ class Page:
path: Path
filters: list[str]
kind: str
markdown: str = field(init=False)
source: str = field(default="", init=False)
markdown: str = field(default="", init=False)

def __post_init__(self) -> None:
if not self.path.exists():
Expand All @@ -36,31 +37,25 @@ def __post_init__(self) -> None:
with self.path.open("w") as file:
file.write("")

self.set_markdown()

def set_markdown(self) -> None:
"""Set markdown."""
if self.kind == "object":
# filters = [*self.filters, "sourcelink"]
self.markdown = create_object_markdown(self.name, self.path, self.filters)
self.source = create_object_markdown(self.name, self.path, self.filters)
elif self.kind == "source":
self.markdown = create_source_markdown(self.name, self.path, self.filters)
self.source = create_source_markdown(self.name, self.path, self.filters)

def convert_markdown(self, markdown: str, anchor: str) -> str:
def convert_markdown(self, source: str, anchor: str) -> str:
"""Return converted markdown."""
# filters = self.filters
if self.kind in ["object", "source"]:
markdown = self.markdown

return convert_markdown(markdown, self.path, anchor)
if self.kind in ["object", "source"]: # noqa: SIM102
if self.markdown and not is_module_cache_dirty(self.name):
return self.markdown
if self.kind == "markdown":
self.source = source


NAME_PATTERN = re.compile(r"^(?P<name>.+?)(?P<maxdepth>\.\*+)?$")


def _split_name_maxdepth(name: str) -> tuple[str, int]:
if m := NAME_PATTERN.match(name):
name = m.group("name")
maxdepth = int(len(m.group("maxdepth") or ".")) - 1
else:
maxdepth = 0
return name, maxdepth
self.markdown = convert_markdown(self.source, self.path, anchor)
return self.markdown


object_paths: dict[str, Path] = {}
Expand All @@ -73,16 +68,13 @@ def create_object_markdown(
predicate: Callable[[str], bool] | None = None,
) -> str:
"""Create object page for an object."""
# name, maxdepth = _split_name_maxdepth(name)
maxdepth = 2

if not (obj := get_object(name)):
return f"!!! failure\n\n {name!r} not found."

filters_str = "|" + "|".join(filters) if filters else ""

markdowns = []
for child, depth in iter_objects_with_depth(obj, maxdepth, member_only=True):
for child, depth in iter_objects_with_depth(obj, 2, member_only=True):
if is_empty(child):
continue
if predicate and not predicate(child.fullname):
Expand All @@ -106,14 +98,11 @@ def create_source_markdown(
predicate: Callable[[str], bool] | None = None,
) -> str:
"""Create source page for a module."""
# name, maxdepth = _split_name_maxdepth(name)
maxdepth = 2

if not (obj := get_object(name)) or not isinstance(obj, Module):
return f"!!! failure\n\n module {name!r} not found.\n"

object_filters = []
for child in iter_objects(obj, maxdepth):
for child in iter_objects(obj, 2):
if predicate and not predicate(child.fullname):
continue
if object_filter := get_object_filter_for_source(child, obj):
Expand All @@ -124,10 +113,8 @@ def create_source_markdown(
return f"# ::: {name}{filters_str}\n"


# def convert_markdown(markdown: str, path: Path, anchor: str, filters: list[str]) -> str:
def convert_markdown(markdown: str, path: Path, anchor: str) -> str:
"""Return converted markdown."""
# replace_object = partial(_replace_object, filters=filters)
markdown = mkapi.markdown.sub(OBJECT_PATTERN, _replace_object, markdown)

replace_link = partial(_replace_link, directory=path.parent, anchor=anchor)
Expand All @@ -148,19 +135,6 @@ def _replace_object(match: re.Match) -> str:
return mkapi.renderers.render(obj, level, filters)


# def _replace_object(match: re.Match, filters: list[str]) -> str:
# heading, name = match.group("heading"), match.group("name")
# level = len(heading)
# name, filters_ = split_filters(name)

# if not (obj := get_object(name)):
# return f"!!! failure\n\n {name!r} not found."

# updated_filters = update_filters(filters, filters_)

# return mkapi.renderers.render(obj, level, updated_filters)


LINK_PATTERN = re.compile(r"(?<!`)\[([^[\]\s]+?)\]\[([^[\]\s]+?)\]")


Expand Down
30 changes: 15 additions & 15 deletions src/mkapi/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import mkapi
import mkapi.nav
from mkapi import renderers
from mkapi.importlib import cache_clear
from mkapi.nav import split_name_depth
from mkapi.pages import Page, convert_source
from mkapi.utils import get_module_path, is_package
from mkapi.utils import get_module_path, is_package, module_cache

if TYPE_CHECKING:
from collections.abc import Callable
Expand Down Expand Up @@ -59,9 +60,10 @@ class MkAPIPlugin(BasePlugin[MkAPIConfig]):

def __init__(self) -> None:
self.api_dirs = []
self.pages = {}

def on_config(self, config: MkDocsConfig, **kwargs) -> MkDocsConfig:
self.pages = {}
cache_clear()
self.bar = None
self.uri_width = 0
self.page_title = _get_function("page_title", self)
Expand Down Expand Up @@ -108,16 +110,15 @@ def on_nav(self, *args, **kwargs) -> None:
def on_page_markdown(self, markdown: str, page: MkDocsPage, **kwargs) -> str:
"""Convert Markdown source to intermediate version."""
uri = page.file.src_uri
page_ = self.pages[uri]
anchor = self.config.src_anchor

try:
return self.pages[uri].convert_markdown(markdown, anchor)
# path = page.file.abs_src_path
# filters = self.config.filters
# return convert_markdown(markdown, path, anchor, filters)
return page_.convert_markdown(markdown, anchor)
except Exception as e: # noqa: BLE001
if self.config.debug:
raise
msg = f"{page.file.src_uri}:{type(e).__name__}: {e}"
msg = f"{uri}:{type(e).__name__}: {e}"
logger.warning(msg)
return markdown

Expand All @@ -129,15 +130,14 @@ def on_page_content(
**kwargs,
) -> str:
"""Merge HTML and MkAPI's object structure."""
page_ = self.pages.get(page.file.src_uri)
if page_ and page_.kind == "object":
uri = page.file.src_uri
page_ = self.pages[uri]

if page_.kind == "object":
_replace_toc(page.toc, self.toc_title)
self._update_bar(page.file.src_uri)
if page_ and page_.kind == "source":
path = Path(config.docs_dir) / page.file.src_uri
# assert page.file.abs_src_path == str(path)
html = convert_source(html, path, self.config.docs_anchor)
self._update_bar(page.file.src_uri)
if page_.kind == "source":
html = convert_source(html, page_.path, self.config.docs_anchor)
self._update_bar(page.file.src_uri)
return html

def _update_bar(self, uri: str) -> None:
Expand Down
31 changes: 7 additions & 24 deletions src/mkapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import ast
import re
from dataclasses import dataclass

# from dataclasses import dataclass
from functools import cache
from importlib.util import find_spec
from pathlib import Path
Expand Down Expand Up @@ -73,48 +74,30 @@ def find_submodule_names(
return names


@dataclass
class ModuleCache:
"""Cache for module node and source."""

name: str
mtime: float
node: ast.Module
source: str


module_cache: dict[str, ModuleCache | None] = {}
module_cache: dict[str, float] = {}


def is_module_cache_dirty(name: str) -> bool:
"""Return True if `module_cache` is dirty."""
if not (path := get_module_path(name)):
return False
if not (cache := module_cache.get(name)):
if not (mtime := module_cache.get(name)):
return True
return cache.mtime != path.stat().st_mtime
return mtime != path.stat().st_mtime


@cache
def get_module_node_source(name: str) -> tuple[ast.Module, str] | None:
"""Return a tuple of ([ast.Module], source) from a module name."""
if name in module_cache and not module_cache[name]:
return None
if not (path := get_module_path(name)):
module_cache[name] = None
return None
mtime = path.stat().st_mtime
if (cache := module_cache.get(name)) and cache.mtime == mtime:
return cache.node, cache.source
with path.open("r", encoding="utf-8") as f:
source = f.read()
node = ast.parse(source)
module_cache[name] = ModuleCache(name, mtime, node, source)
module_cache[name] = path.stat().st_mtime
return node, source


get_module_node_source.cache_clear = lambda: module_cache.clear()


def get_module_node(name: str) -> ast.Module | None:
"""Return an [ast.Module] instance from a module name."""
if node_source := get_module_node_source(name):
Expand Down
6 changes: 0 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ def test_get_module_node():

def test_module_cache(tmpdir: Path):
module_cache.clear()
assert not get_module_node_source("___")
assert "___" in module_cache
assert not is_module_cache_dirty("a")

sys.path.insert(0, str(tmpdir))
Expand All @@ -77,9 +75,5 @@ def test_module_cache(tmpdir: Path):
with path.open("w") as f:
f.write(source)
assert is_module_cache_dirty("a")
z = get_module_node_source("a")
assert z
assert x[0] is not z[0]
assert z[1] == source

sys.path.pop(0)

0 comments on commit 5f35740

Please sign in to comment.