Skip to content

Commit

Permalink
Fix docs
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeNaccarato committed Sep 30, 2024
1 parent a7412ee commit 4bb0352
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 85 deletions.
46 changes: 11 additions & 35 deletions dev.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -120,41 +120,19 @@ function Invoke-Uv {
}
$Env:ENV_SYNCED = $True

# ? Track environment variables to update `.env` with later
$EnvVars = @{}
$EnvVars.Add('PYRIGHT_PYTHON_PYLANCE_VERSION', $PylanceVersion)
$EnvFile = $Env:GITHUB_ENV ? $Env:GITHUB_ENV : "$PWD/.env"
if (!(Test-Path $EnvFile)) { New-Item $EnvFile }

# ? Get environment variables from `pyproject.toml`
uv run --no-sync --python $PythonVersion dev init-shell |
Select-String -Pattern '^(.+)=(.+)$' |
ForEach-Object {
$Key, $Value = $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value
if ($EnvVars -notcontains $Key) { $EnvVars.Add($Key, $Value) }
}

# ? Get environment variables to update in `.env`
$Keys = @()
$Lines = Get-Content $EnvFile | ForEach-Object {
$_ -Replace '^(?<Key>.+)=(?<Value>.+)$', {
$Key = $_.Groups['Key'].Value
if ($EnvVars.ContainsKey($Key)) {
$Keys += $Key
return "$Key=$($EnvVars[$Key])"
}
return $_
}
}
# ? Sync environment variables and those in `.env`
$NewLines = $EnvVars.GetEnumerator() | ForEach-Object {
$Key, $Value = $_.Key, $_.Value
# ? Sync `.env` and set environment variables from `pyproject.toml`
$EnvVars = uv run --no-sync --python $PythonVersion dev 'sync-environment-variables' --pylance-version $PylanceVersion
$EnvVars | Set-Content ($Env:GITHUB_ENV ? $Env:GITHUB_ENV : "$PWD/.env")
$EnvVars | Select-String -Pattern '^(.+?)=(.+)$' | ForEach-Object {
$Key, $Value = $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value
Set-Item "Env:$Key" $Value
if ($Keys -notcontains $Key) { return "$Key=$Value" }
}
@($Lines, $NewLines) | Set-Content $EnvFile

# ? Environment-specific setup
if ($Devcontainer) {
if ($CI) {
uv run --no-sync --python $PythonVersion dev elevate-pyright-warnings
}
elseif ($Devcontainer) {
$Repo = Get-ChildItem '/workspaces'
$Packages = Get-ChildItem "$Repo/packages"
$SafeDirs = @($Repo) + $Packages
Expand All @@ -164,9 +142,7 @@ function Invoke-Uv {
}
}
}
elseif ($CI) {
uv run --no-sync --python $PythonVersion dev elevate-pyright-warnings
}

# ? Install pre-commit hooks
else {
$Hooks = '.git/hooks'
Expand Down
13 changes: 6 additions & 7 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,15 @@ def dpath(path: Path, rel: Path = DOCS) -> str:
mermaid_d3_zoom = False
# ! Autodoc2
nitpicky = True
autodoc2_packages = [
f"../src/{PACKAGE}",
f"{PACKAGE}_docs",
f"../tests/{PACKAGE}_tests",
f"../scripts/{PACKAGE}_tools",
]
autodoc2_packages = [f"../src/{PACKAGE}", "../packages/_dev/dev"]
autodoc2_render_plugin = "myst"
# ? Autodoc2 does not currently obey `python_display_short_literal_types` or
# ? `python_use_unqualified_type_names`, but `maximum_signature_line_length` makes it a
# ? bit prettier.
# ? https://github.com/sphinx-extensions2/sphinx-autodoc2/issues/58
maximum_signature_line_length = 1
# ? Parse Numpy docstrings
autodoc2_docstring_parser_regexes = [(".*", f"{PACKAGE}_docs.docstrings")]
autodoc2_docstring_parser_regexes = [(".*", "dev.docs.docstrings")]
# ! Intersphinx
intersphinx_mapping = ISPX_MAPPING
nitpick_ignore = [
Expand All @@ -204,6 +199,10 @@ def dpath(path: Path, rel: Path = DOCS) -> str:
),
(r"py:.*", r"ploomber_engine\.ipython\.+"),
(r"py:.*", r"pydantic\..+"), # ? https://github.com/pydantic/pydantic/issues/1339
(
r"py:.+",
r"pydantic_settings\..+",
), # ? https://github.com/pydantic/pydantic/issues/1339
# ? TypeAlias: https://github.com/sphinx-doc/sphinx/issues/10785
(r"py:class", rf"{PACKAGE}.*\.types\..+"),
]
Expand Down
31 changes: 31 additions & 0 deletions packages/_dev/dev/modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Module names."""

from importlib.machinery import ModuleSpec
from pathlib import Path
from types import ModuleType


def get_package_dir(package: ModuleType) -> Path:
"""Get the directory of a package given the top-level module."""
return Path(package.__spec__.submodule_search_locations[0]) # type: ignore


def get_module_name(module: ModuleType | ModuleSpec | Path | str) -> str:
"""Get an unqualified module name.
Example: `get_module_name(__spec__ or __file__)`.
"""
if isinstance(module, ModuleType | ModuleSpec):
return get_qualified_module_name(module).split(".")[-1]
path = Path(module)
return path.parent.name if path.stem in ("__init__", "__main__") else path.stem


def get_qualified_module_name(module: ModuleType | ModuleSpec) -> str: # type: ignore
"""Get a fully-qualified module name.
Example: `get_module_name(__spec__ or __file__)`.
"""
if isinstance(module, ModuleType):
module: ModuleSpec = module.__spec__ # type: ignore
return module.name
26 changes: 17 additions & 9 deletions packages/_dev/dev/tools/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@
from pathlib import Path
from re import finditer, sub
from shlex import join, split
from sys import version_info
from tomllib import loads

from cyclopts import App
from pydantic import BaseModel

from dev.tools import add_changes, environment
from dev.tools.environment import escape, run
from dev.tools.types import ChangeType

from tomllib import loads

class Constants(BaseModel):
"""Constants for {mod}`~dev.tools.environment`."""

pylance_version: str = Path(".pylance-version").read_text(encoding="utf-8").strip()
"""Pylance version."""


const = Constants()


APP = App(help_format="markdown")
"""CLI."""
Expand All @@ -24,9 +34,9 @@ def main(): # noqa: D103


@APP.command
def init_shell():
def sync_environment_variables(pylance_version: str = const.pylance_version):
"""Initialize shell."""
log(environment.init_shell())
log(environment.sync_environment_variables(pylance_version=pylance_version))


@APP.command
Expand Down Expand Up @@ -100,12 +110,10 @@ def elevate_pyright_warnings():
@APP.command()
def build_docs():
"""Build docs."""
run([
"sphinx-autobuild",
"--show-traceback",
"docs _site",
run(
"sphinx-autobuild --show-traceback docs _site",
*[f"--ignore **/{p}" for p in ["temp", "data", "apidocs", "*schema.json"]],
])
)


def log(obj):
Expand Down
99 changes: 65 additions & 34 deletions packages/_dev/dev/tools/environment.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,90 @@
"""Contributor environment."""
"""Contributor environment setup."""

import subprocess
from collections.abc import Iterable
from contextlib import chdir, nullcontext
from io import StringIO
from pathlib import Path
from shlex import quote
from sys import executable

from dotenv import load_dotenv
from dotenv import dotenv_values, load_dotenv
from pydantic import BaseModel, Field
from pydantic_settings import (
BaseSettings,
PyprojectTomlConfigSettingsSource,
SettingsConfigDict,
)

import dev
from dev.modules import get_module_name

def init_shell(path: Path | None = None) -> str:
"""Initialize shell."""
with chdir(path) if path else nullcontext():
environment = Environment().model_dump()
dotenv = "\n".join(f"{k}={v}" for k, v in environment.items())
load_dotenv(stream=StringIO(dotenv))
return dotenv

class Constants(BaseModel):
"""Constants for {mod}`~dev.tools.environment`."""

def run(args: str | Iterable[str] | None = None):
dev_tool_config: tuple[str, ...] = ("tool", get_module_name(dev))
"""Path to `dev` tool configuration in `pyproject.toml`."""
pylance_version_source: str = ".pylance-version"
"""Path to Pylance version file."""
shell: list[str] = ["pwsh", "-Command"]
"""Shell invocation for running arbitrary commands."""
uv_run_wrapper: str = "./Invoke-Uv.ps1"
"""Wrapper of `uv run` with extra setup."""
env: str = ".env"
"""Name of environment file."""


const = Constants()


def sync_environment_variables(
path: Path | None = None, pylance_version: str = "", setenv: bool = True
) -> str:
"""Sync `.env` with `pyproject.toml`, optionally setting environment variables."""
path = Path(path) if path else Path.cwd() / ".env"
config_env = Config().env
if pylance_version:
config_env["PYRIGHT_PYTHON_PYLANCE_VERSION"] = pylance_version
dotenv = dotenv_values(const.env)
keys_set: list[str] = []
for key in dotenv:
if override := config_env.get(key):
keys_set.append(key)
dotenv[key] = override
for k, v in config_env.items():
if k not in keys_set:
dotenv[k] = v
if setenv:
load_dotenv(stream=StringIO("\n".join(f"{k}={v}" for k, v in dotenv.items())))
return "\n".join(f"{k}={v}" for k, v in dotenv.items())


def run(*args: str):
"""Run command."""
sep = " "
subprocess.run(
check=True,
args=[
"pwsh",
"-Command",
sep.join([
f"& {quote(executable)} -m",
*(([args] if isinstance(args, str) else args) or []),
]),
],
)


class Environment(BaseSettings):
"""Get environment variables from `pyproject.toml:[tool.env]`."""

model_config = SettingsConfigDict(
extra="allow", pyproject_toml_table_header=("tool", "env")
)
with nullcontext() if Path(const.uv_run_wrapper).exists() else chdir(".."):
subprocess.run(
check=True, args=[*const.shell, sep.join([const.uv_run_wrapper, *args])]
)

@classmethod
def settings_customise_sources(cls, settings_cls, **_): # pyright: ignore[reportIncompatibleMethodOverride]
"""Customize so that all keys are loaded despite not being model fields."""
return (PyprojectTomlConfigSettingsSource(settings_cls),)

def run_dev(*args: str):
"""Run command from `dev` CLI."""
run(f"& {quote(executable)} -m", *args)


def escape(path: str | Path) -> str:
"""Escape a path, suitable for passing to e.g. {func}`~subprocess.run`."""
return quote(Path(path).as_posix())


class Config(BaseSettings):
"""Get tool config from `pyproject.toml`."""

model_config = SettingsConfigDict(pyproject_toml_table_header=const.dev_tool_config)
env: dict[str, str] = Field(default_factory=dict)

@classmethod
def settings_customise_sources(cls, settings_cls, **_): # pyright: ignore[reportIncompatibleMethodOverride]
"""Only load from `pyproject.toml`."""
return (PyprojectTomlConfigSettingsSource(settings_cls),)
12 changes: 12 additions & 0 deletions packages/_dev/dev/tools/warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Warnings."""

from boilercore.warnings import filter_boiler_warnings


def filter_boilercv_warnings():
"""Filter certain warnings for `boilercv`."""
filter_boiler_warnings(other_warnings=WARNING_FILTERS)


WARNING_FILTERS = []
"""Warning filters."""

0 comments on commit 4bb0352

Please sign in to comment.