Skip to content

Commit

Permalink
Add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
brettcannon committed Nov 20, 2022
1 parent 818ad72 commit 40ed975
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 41 deletions.
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
68 changes: 68 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

import pathlib
import tomllib


pyproject_path = pathlib.Path(__file__).parent.parent / "pyproject.toml"
pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))

project = pyproject["project"]["name"]
author = ", ".join(author["name"] for author in pyproject["project"]["authors"])
copyright = f"2022, {author}"
version = release = pyproject["project"]["version"]

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

language = "en-ca"

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "furo"
html_static_path = ["_static"]

# -- Options for autodoc ----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration

autodoc_member_order = "bysource"
autodoc_preserve_defaults = True

# Automatically extract typehints when specified and place them in
# descriptions of the relevant function/method.
autodoc_typehints = "signature"
autodoc_typehints_format = "short"

# -- Options for extlinks -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html#configuration


extlinks = {
# "pep": ("https://peps.python.org/%s", "PEP %s"),
}

# -- Options for intersphinx ----------------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration


intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"pypa": ("https://packaging.python.org/", None),
}
12 changes: 12 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.. Mousebender documentation master file, created by
sphinx-quickstart on Sun Nov 20 14:18:05 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Mousebender
=======================================

.. toctree::
:maxdepth: 2

simple
35 changes: 35 additions & 0 deletions docs/make.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd
7 changes: 7 additions & 0 deletions docs/simple.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. currentmodule: mousebender.simple
``mousebender.simple`` -- Simple repository API
===============================================

.. automodule:: mousebender.simple
:members:
1 change: 0 additions & 1 deletion mousebender/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
"""A package for implementing various Python packaging standards."""
__version__ = "2.0.0"
66 changes: 36 additions & 30 deletions mousebender/simple.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Implement the Simple Repository API.
"""Utilities to help with Simple repository API responses.
This encompasses PEPs:
This module helps with the JSON-based Simple repository API by providing
:class:`~typing.TypedDict` definitions for API responses. For HTML-based
responses, functions are provided to convert the HTML to the equivalent JSON
response.
1. 503: Simple Repository API
2. 592: Adding “Yank” Support to the Simple API
3. 629: Versioning PyPI's Simple API
4. 658: Serve Distribution Metadata in the Simple Repository API
5. 691: JSON-based Simple API for Python Package Indexes
This module implements :pep:`503`, :pep:`592`, :pep:`658`, and :pep:`691` of the
:external:ref:`Simple repository API <simple-repository-api>` (it forgoes
:pep:`629` as :pep:`691` makes it obsolete).
"""
from __future__ import annotations
Expand All @@ -22,34 +23,20 @@
# Python 3.8+ only.
from typing_extensions import Literal, TypeAlias, TypedDict


def create_project_url(base_url: str, project_name: str) -> str:
"""Construct the project URL for a repository following PEP 503."""
if base_url and not base_url.endswith("/"):
base_url += "/" # Normalize for easier use w/ str.join() later.
# PEP 503:
# The format of this URL is /<project>/ where the <project> is replaced by
# the normalized name for that project, so a project named "HolyGrail" would
# have a URL like /holygrail/.
#
# All URLs which respond with an HTML5 page MUST end with a / and the
# repository SHOULD redirect the URLs without a / to add a / to the end.
return "".join([base_url, packaging.utils.canonicalize_name(project_name), "/"])


_Meta_1_0 = TypedDict("_Meta_1_0", {"api-version": Literal["1.0"]})


class ProjectIndex_1_0(TypedDict):
"""A TypedDict for API version 1.0 that represents a project index."""
"""A :class:`~typing.TypedDict` for a project index (JSON v1.0)."""

meta: _Meta_1_0
projects: List[Dict[Literal["name"], str]]


# Turn into a union when future API versions are supported.
ProjectIndex: TypeAlias = ProjectIndex_1_0
"""API version-agnostic type alias for a project index."""
"""A :data:`~typing.TypeAlias` for any version of the JSON project
index response."""


_HashesDict: TypeAlias = Dict[str, str]
Expand All @@ -67,15 +54,16 @@ class ProjectIndex_1_0(TypedDict):


class ProjectFileDetails_1_0(_OptionalProjectFileDetails):
"""A TypedDict for API version 1.0 that represents a project file."""
"""A :class:`~typing.TypedDict` for the ``files`` key of
:class:`ProjectDetails_1_0`."""

filename: str
url: str
hashes: _HashesDict


class ProjectDetails_1_0(TypedDict):
"""A TypedDict for API version 1.0 representing a project's details."""
"""A :class:`~typing.TypedDict` for a project details response (JSON v1.0)."""

meta: _Meta_1_0
name: packaging.utils.NormalizedName
Expand All @@ -84,12 +72,11 @@ class ProjectDetails_1_0(TypedDict):

# Turn into a union when future API versions are supported.
ProjectDetails: TypeAlias = ProjectDetails_1_0
"""API version-agnostic type alias for a project's details."""
"""A :data:`~typing.TypeAlias` for any version of the JSON project details
response."""


class _SimpleIndexHTMLParser(html.parser.HTMLParser):
"""Parse the HTML of a repository index page."""

# PEP 503:
# Within a repository, the root URL (/) MUST be a valid HTML5 page with a
# single anchor element per project in the repository.
Expand Down Expand Up @@ -117,7 +104,7 @@ def handle_data(self, data: str) -> None:


def from_project_index_html(html: str) -> ProjectIndex_1_0:
"""Parse the HTML of a repository index page."""
"""Convert the HTML response of a repository index page to a JSON v1.0 response."""
parser = _SimpleIndexHTMLParser()
parser.feed(html)
project_index: ProjectIndex = {
Expand Down Expand Up @@ -198,7 +185,26 @@ def handle_starttag(
self.archive_links.append(args)


def create_project_url(base_url: str, project_name: str) -> str:
"""Construct the URL for a project hosted on a server at *base_url*."""
if base_url and not base_url.endswith("/"):
base_url += "/" # Normalize for easier use w/ str.join() later.
# PEP 503:
# The format of this URL is /<project>/ where the <project> is replaced by
# the normalized name for that project, so a project named "HolyGrail" would
# have a URL like /holygrail/.
#
# All URLs which respond with an HTML5 page MUST end with a / and the
# repository SHOULD redirect the URLs without a / to add a / to the end.
return "".join([base_url, packaging.utils.canonicalize_name(project_name), "/"])


def from_project_details_html(html: str, name: str) -> ProjectDetails_1_0:
"""Convert the HTML response for a project details page to a JSON v1.0 response.
Due to HTML project details pages lacking the name of the project, it must
be specified via the *name* parameter to fill in the JSON data.
"""
parser = _ArchiveLinkHTMLParser()
parser.feed(html)
files: List[ProjectFileDetails_1_0] = []
Expand Down
18 changes: 8 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "mousebender"
version = "2022.0.0"
authors = [
{ name = "Brett Cannon", email = "brett@python.org" },
{ name = "Derek Keeler", email = "derek@suchcool.ca" },
Expand All @@ -21,17 +22,20 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"License :: OSI Approved :: BSD License",
]
dynamic = ["description"]

dependencies = ["packaging >= 20.9", "typing-extensions"]
optional-dependencies.test = [

[project.optional-dependencies]

test = [
"pytest >= 6.0.1",
"importlib-resources >= 1.4.0",
"coverage[toml] >= 5.0.4",
"pytest-cov >= 2.8.1",
]

dynamic = ["version", "description"]

doc = ["sphinx", "furo"]

[tool.coverage]
run.source = ["mousebender/"]
Expand All @@ -44,10 +48,4 @@ profile = "black"
[tool.ruff]
select = ["E", "F", "W", "D", "C", "B", "A", "ANN", "RUF", "M"]
ignore = ["E501", "D203", "D213", "ANN101"]
per-file-ignores = { "tests/*" = [
"D",
"ANN",
], "noxfile.py" = [
"ANN",
"A001",
] }
per-file-ignores = { "tests/*" = ["D", "ANN"], "noxfile.py" = ["ANN", "A001"] }

0 comments on commit 40ed975

Please sign in to comment.