Skip to content

Commit

Permalink
Add pydantic autodoc and pydocstyle
Browse files Browse the repository at this point in the history
  • Loading branch information
coretl committed Sep 13, 2024
1 parent dfbf759 commit 84ec66d
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/venv/bin/python"
"python.defaultInterpreterPath": "/venv/bin/python",
"remote.autoForwardPorts": false
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
Expand Down
33 changes: 10 additions & 23 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"sphinx.ext.autodoc",
# and making summary tables at the top of API docs
"sphinx.ext.autosummary",
# With an extension for pydantic models
"sphinxcontrib.autodoc_pydantic",
# This can parse google style docstrings
"sphinx.ext.napoleon",
# For linking to external sphinx documentation
Expand Down Expand Up @@ -69,22 +71,8 @@
# domain name if present. Example entries would be ('py:func', 'int') or
# ('envvar', 'LD_LIBRARY_PATH').
nitpick_ignore = [
("py:func", "int"),
("py:class", "Axis"),
("py:class", "~Axis"),
("py:class", "scanspec.core.Axis"),
("py:class", "AxesPoints"),
("py:class", "np.ndarray"),
("py:class", "NoneType"),
("py:class", "'str'"),
("py:class", "'float'"),
("py:class", "'int'"),
("py:class", "'bool'"),
("py:class", "'object'"),
("py:class", "'id'"),
("py:class", "typing_extensions.Literal"),
("py:class", "pydantic.config.BaseConfig"),
("py:class", "starlette.responses.JSONResponse"),
("py:class", "scanspec.core.C"),
("py:class", "pydantic.config.ConfigDict"),
]

# Both the class’ and the __init__ method’s docstring are concatenated and
Expand All @@ -94,15 +82,14 @@
# Order the members by the order they appear in the source code
autodoc_member_order = "bysource"

# Don't inherit docstrings from baseclasses
autodoc_inherit_docstrings = False
# For autodoc we want to document some additional optional modules
scanspec.__all__ += ["plot"]

# Insert inheritance links
autodoc_default_options = {"show-inheritance": True}
# Don't show config summary as it's not relevant
autodoc_pydantic_model_show_config_summary = False

# A dictionary for users defined type aliases that maps a type name to the
# full-qualified object name.
autodoc_type_aliases = {"AxesPoints": "scanspec.core.AxesPoints"}
# Show the fields in source order
autodoc_pydantic_model_summary_list_order = "bysource"

# Include source in plot directive by default
plot_include_source = True
Expand Down
13 changes: 6 additions & 7 deletions docs/explanations/why-stack-frames.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Why create a stack of Frames?
=============================

If a `Spec` tells you the parameters of a scan, `Frames` gives you the `Points`
that will let you actually execute the scan. A stack of Frames is interpreted as
nested from slowest moving to fastest moving, so each faster Frames object will
iterate once per position of the slower Frames object. When fly-scanning the
axis will traverse lower-midpoint-upper on the fastest Frames object for each
point in the scan.
If a `Spec` tells you the parameters of a scan, `Frames` gives you the `Points
<Point_>` that will let you actually execute the scan. A stack of Frames is
interpreted as nested from slowest moving to fastest moving, so each faster
Frames object will iterate once per position of the slower Frames object. When
fly-scanning the axis will traverse lower-midpoint-upper on the fastest Frames
object for each point in the scan.

An Example
----------
Expand Down Expand Up @@ -63,4 +63,3 @@ which point it destroys the performance of the VDS. For this reason, it is
advisable to `Squash` any snaking Specs with the first non-snaking axis above it
so that the HDF Dimension will not be snaking. See `./why-squash-can-change-path` for
some details on this.

22 changes: 16 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ dev = [
# https://github.com/pypa/pip/issues/10393
"scanspec[plotting]",
"scanspec[service]",
"autodoc_pydantic @ git+https://github.com/coretl/autodoc_pydantic.git@0b95311d8d10fce67a9ecd5830330364e31fa49c",
"copier",
"httpx",
"myst-parser",
"pipdeptree",
"pre-commit",
Expand All @@ -44,8 +46,6 @@ dev = [
"sphinxcontrib-openapi",
"tox-direct",
"types-mock",
"httpx",
"myst-parser",
]

[project.scripts]
Expand Down Expand Up @@ -115,6 +115,7 @@ line-length = 88
extend-select = [
"B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
"C4", # flake8-comprehensions - https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4
"D", # pydocstyle - https://docs.astral.sh/ruff/rules/#pydocstyle-d
"E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e
"F", # pyflakes rules - https://docs.astral.sh/ruff/rules/#pyflakes-f
"W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w
Expand All @@ -124,10 +125,19 @@ extend-select = [
]
ignore = [
"B008", # We use function calls in service arguments
"D105", # Don't document magic methods as they don't appear in sphinx autodoc pages
"D107", # We document the class, not the __init__ method
]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.per-file-ignores]
# By default, private member access is allowed in tests
# See https://github.com/DiamondLightSource/python-copier-template/issues/154
# Remove this line to forbid private member access in tests
"tests/**/*" = ["SLF001"]

"tests/**/*" = [
# By default, private member access is allowed in tests
# See https://github.com/DiamondLightSource/python-copier-template/issues/154
# Remove this line to forbid private member access in tests
"SLF001",
"D", # Don't check docstrings in tests
]
4 changes: 2 additions & 2 deletions src/scanspec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Version number as calculated by https://github.com/pypa/setuptools_scm
"""

from . import regions, specs
from . import core, regions, specs
from ._version import __version__

__all__ = ["__version__", "specs", "regions"]
__all__ = ["__version__", "core", "specs", "regions"]
2 changes: 2 additions & 0 deletions src/scanspec/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Interface for ``python -m scanspec``."""

from scanspec import cli

if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions src/scanspec/cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Interface for ``python -m scanspec``."""

import logging
import string

Expand Down
16 changes: 12 additions & 4 deletions src/scanspec/core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Core classes like `Frames` and `Path`."""

from __future__ import annotations

from collections.abc import Callable, Iterable, Iterator, Sequence
Expand Down Expand Up @@ -25,11 +27,10 @@
"StrictConfig",
]


#: Used to ensure pydantic dataclasses error if given extra arguments
StrictConfig: ConfigDict = {"extra": "forbid"}

C = TypeVar("C")
T = TypeVar("T", type, Callable)


def discriminated_union_of_subclasses(
Expand All @@ -44,8 +45,7 @@ def discriminated_union_of_subclasses(
Subclasses that extend this class must be Pydantic dataclasses, and types that
need their schema to be updated when a new type that extends super_cls is
created must be either Pydantic dataclasses or BaseModels, and must be decorated
with @uses_tagged_union.
created must be either Pydantic dataclasses or BaseModels.
Example::
Expand Down Expand Up @@ -106,6 +106,7 @@ def calculate(self) -> int:
Returns:
Type: decorated superclass with handling for subclasses to be added
to its discriminated union for deserialization
"""
tagged_union = _TaggedUnion(super_cls, discriminator)
_tagged_unions[super_cls] = tagged_union
Expand Down Expand Up @@ -217,6 +218,7 @@ class Frames(Generic[Axis]):
See Also:
`technical-terms`
"""

def __init__(
Expand Down Expand Up @@ -282,6 +284,7 @@ def extract(self, indices: np.ndarray, calculate_gap=True) -> Frames[Axis]:
>>> frames = Frames({"x": np.array([1, 2, 3])})
>>> frames.extract(np.array([1, 0, 1])).midpoints
{'x': array([2, 1, 2])}
"""
dim_indices = indices % len(self)

Expand Down Expand Up @@ -312,6 +315,7 @@ def concat(self, other: Frames[Axis], gap: bool = False) -> Frames[Axis]:
>>> frames2 = Frames({"y": np.array([3, 2, 1]), "x": np.array([4, 5, 6])})
>>> frames.concat(frames2).midpoints
{'x': array([1, 2, 3, 4, 5, 6]), 'y': array([6, 5, 4, 3, 2, 1])}
"""
assert set(self.axes()) == set(
other.axes()
Expand Down Expand Up @@ -411,6 +415,7 @@ def extract(self, indices: np.ndarray, calculate_gap=True) -> Frames[Axis]:
>>> frames = SnakedFrames({"x": np.array([1, 2, 3])})
>>> frames.extract(np.array([0, 1, 2, 3, 4, 5])).midpoints
{'x': array([1, 2, 3, 3, 2, 1])}
"""
# Calculate the indices
# E.g for len = 4
Expand Down Expand Up @@ -470,6 +475,7 @@ def squash_frames(stack: list[Frames[Axis]], check_path_changes=True) -> Frames[
>>> fy = Frames({"y": np.array([3, 4])})
>>> squash_frames([fy, fx]).midpoints
{'y': array([3, 3, 4, 4]), 'x': array([1, 2, 2, 1])}
"""
path = Path(stack)
# Consuming a Path through these Frames performs the squash
Expand Down Expand Up @@ -517,6 +523,7 @@ class Path(Generic[Axis]):
See Also:
`iterate-a-spec`
"""

def __init__(
Expand Down Expand Up @@ -607,6 +614,7 @@ class Midpoints(Generic[Axis]):
{'y': np.int64(3), 'x': np.int64(2)}
{'y': np.int64(4), 'x': np.int64(2)}
{'y': np.int64(4), 'x': np.int64(1)}
"""

def __init__(self, stack: list[Frames[Axis]]):
Expand Down
2 changes: 2 additions & 0 deletions src/scanspec/plot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""`plot_spec` to visualize a scan."""

from collections.abc import Iterator
from itertools import cycle
from typing import Any
Expand Down
Loading

0 comments on commit 84ec66d

Please sign in to comment.