Add sphinx / rtd documentation
This commit is contained in:
parent
8007b02dcc
commit
4fb3e9ea7f
8 changed files with 215 additions and 14 deletions
14
.github/workflows/test.yml
vendored
14
.github/workflows/test.yml
vendored
|
|
@ -13,6 +13,20 @@ on:
|
|||
types: [rerequested]
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install deps
|
||||
run: python -mpip install -r requirements-dev.txt
|
||||
|
||||
- name: Build HTML docs
|
||||
run: make html
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
/build
|
||||
/_build
|
||||
/.coverage
|
||||
/dist
|
||||
/*.egg-info
|
||||
|
|
|
|||
24
Makefile
24
Makefile
|
|
@ -13,6 +13,30 @@ coverage:
|
|||
.PHONY: mypy
|
||||
mypy:
|
||||
mypy --strict *.py
|
||||
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?= -a -E -j auto
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
.PHONY: help
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
|
||||
# Route particular targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
.PHONY: html
|
||||
html:
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
|
||||
# Copyright (C) 2021 Jeff Epler <jepler@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
||||
#
|
||||
|
|
|
|||
0
_static/.empty
Normal file
0
_static/.empty
Normal file
65
conf.py
Normal file
65
conf.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# pylint: disable=all
|
||||
# fmt: off
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'leapseconddata'
|
||||
copyright = '2021, Jeff Epler'
|
||||
author = 'Jeff Epler'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '1.1.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
autodoc_typehints = "description"
|
||||
autodoc_class_signature = "separated"
|
||||
|
||||
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
26
index.rst
Normal file
26
index.rst
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.. SPDX-FileCopyrightText: 2021 Jeff Epler
|
||||
..
|
||||
.. SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
leapseconddata
|
||||
==============
|
||||
|
||||
.. image:: https://github.com/jepler/leapseconddata/actions/workflows/test.yml/badge.svg
|
||||
:target: https://github.com/jepler/leapseconddata/actions/workflows/test.yml
|
||||
:alt: Test leapseconddata
|
||||
|
||||
.. image:: https://codecov.io/gh/jepler/leapseconddata/branch/main/graph/badge.svg?token=Exx0c3Gp65
|
||||
:target: https://codecov.io/gh/jepler/leapseconddata
|
||||
:alt: codecov
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/leapseconddata
|
||||
:target: https://pypi.org/project/leapseconddata
|
||||
:alt: PyPI
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: leapseconddata
|
||||
:members:
|
||||
|
|
@ -4,7 +4,21 @@
|
|||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
"""Use the list of known and scheduled leap seconds"""
|
||||
"""Use the list of known and scheduled leap seconds
|
||||
|
||||
For example, to retrieve the UTC-TAI offset on January 1, 2011:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 2,3,5
|
||||
|
||||
>>> import datetime
|
||||
>>> import leapseconddata
|
||||
>>> ls = leapseconddata.LeapSecondData.from_standard_source()
|
||||
>>> when = datetime.datetime(2011, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
>>> ls.tai_offset(when).total_seconds()
|
||||
34.0
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
|
|
@ -60,10 +74,24 @@ well in the past. Use `valid_until` to determine validity."""
|
|||
|
||||
|
||||
class LeapSecondData(_LeapSecondData):
|
||||
"""Represent the list of known and scheduled leapseconds"""
|
||||
"""Represent the list of known and scheduled leapseconds
|
||||
|
||||
:param List[LeapSecondInfo] leap_seconds: A list of leap seconds
|
||||
:param Optional[datetime.datetime] valid_until: The expiration of the data, if available
|
||||
:param Optional[datetime.datetime] updated: The last update time of the data, if available
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
leap_seconds: List[LeapSecondInfo]
|
||||
"""All known and scheduled leap seconds"""
|
||||
|
||||
valid_until: Optional[datetime.datetime]
|
||||
"""The list is valid until this UTC time"""
|
||||
|
||||
last_updated: Optional[datetime.datetime]
|
||||
"""The last time the list was updated to add a new upcoming leap second"""
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
leap_seconds: List[LeapSecondInfo],
|
||||
|
|
@ -82,7 +110,10 @@ class LeapSecondData(_LeapSecondData):
|
|||
return None
|
||||
|
||||
def valid(self, when: Optional[datetime.datetime] = None) -> bool:
|
||||
"""Return True if the data is valid at given datetime (or the current moment, if None is passed)"""
|
||||
"""Return True if the data is valid at given datetime (or the current moment, if None is passed)
|
||||
|
||||
:param when: Moment to check for validity
|
||||
"""
|
||||
return self._check_validity(when) is None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -96,10 +127,14 @@ class LeapSecondData(_LeapSecondData):
|
|||
) -> datetime.timedelta:
|
||||
"""For a given datetime, return the TAI-UTC offset
|
||||
|
||||
:param when: Moment in time to find offset for
|
||||
:param check_validity: Check whether the database is valid for the given moment
|
||||
|
||||
For times before the first leap second, a zero offset is returned.
|
||||
For times after the end of the file's validity, an exception is raised
|
||||
unless `check_validity=False` is passed. In this case, it will return
|
||||
the offset of last list entry."""
|
||||
the offset of last list entry.
|
||||
"""
|
||||
|
||||
is_tai = when.tzinfo is tai
|
||||
if not is_tai:
|
||||
|
|
@ -124,7 +159,12 @@ class LeapSecondData(_LeapSecondData):
|
|||
def to_tai(
|
||||
self, when: datetime.datetime, check_validity: bool = True
|
||||
) -> datetime.datetime:
|
||||
"""Convert the given datetime object to TAI"""
|
||||
"""Convert the given datetime object to TAI.
|
||||
|
||||
:param when: Moment in time to convert. If naive, it is assumed to be in UTC.
|
||||
:param check_validity: Check whether the database is valid for the given moment
|
||||
|
||||
Naive timestamps are assumed to be utc. A TAI timestamp is returned unchanged."""
|
||||
if when.tzinfo is tai:
|
||||
return when
|
||||
when = self._utc_datetime(when)
|
||||
|
|
@ -133,7 +173,11 @@ class LeapSecondData(_LeapSecondData):
|
|||
def tai_to_utc(
|
||||
self, when: datetime.datetime, check_validity: bool = True
|
||||
) -> datetime.datetime:
|
||||
"""Convert the given datetime object (which is assumed to be in TAI) to UTC"""
|
||||
"""Convert the given datetime object to UTC
|
||||
|
||||
:param when: Moment in time to convert. If naive, its ``tzinfo`` must be `tai`.
|
||||
:param check_validity: Check whether the database is valid for the given moment
|
||||
"""
|
||||
if when.tzinfo is not None and when.tzinfo is not tai:
|
||||
raise ValueError("Input timestamp is not TAI or naive")
|
||||
if when.tzinfo is None:
|
||||
|
|
@ -148,6 +192,9 @@ class LeapSecondData(_LeapSecondData):
|
|||
) -> bool:
|
||||
"""Return True if the given timestamp is the leap second.
|
||||
|
||||
:param when: Moment in time to check. If naive, it is assumed to be in UTC.
|
||||
:param check_validity: Check whether the database is valid for the given moment
|
||||
|
||||
For a TAI timestamp, it returns True for the leap second (the one that
|
||||
would be shown as :60 in UTC). For a UTC timestamp, it returns True
|
||||
for the :59 second, since the :60 second cannot be represented."""
|
||||
|
|
@ -161,9 +208,16 @@ class LeapSecondData(_LeapSecondData):
|
|||
|
||||
@classmethod
|
||||
def from_standard_source(
|
||||
cls, when: Optional[datetime.datetime] = None
|
||||
cls,
|
||||
when: Optional[datetime.datetime] = None,
|
||||
check_hash: bool = True,
|
||||
) -> "LeapSecondData":
|
||||
"""Using a list of standard sources, including network sources, find a
|
||||
"""Get the list of leap seconds from a standard source.
|
||||
|
||||
:param when: Check that the data is valid for this moment
|
||||
:param check_hash: Whether to check the embedded hash
|
||||
|
||||
Using a list of standard sources, including network sources, find a
|
||||
leap-second.list data valid for the given timestamp, or the current
|
||||
time (if unspecified)"""
|
||||
|
||||
|
|
@ -174,7 +228,7 @@ class LeapSecondData(_LeapSecondData):
|
|||
]:
|
||||
logging.debug("Trying leap second data from %s", location)
|
||||
try:
|
||||
candidate = cls.from_url(location)
|
||||
candidate = cls.from_url(location, check_hash)
|
||||
except InvalidHashError: # pragma no cover
|
||||
logging.warning("Invalid hash while reading %s", location)
|
||||
continue
|
||||
|
|
@ -195,8 +249,10 @@ class LeapSecondData(_LeapSecondData):
|
|||
) -> "LeapSecondData":
|
||||
"""Retrieve the leap second list from a local file.
|
||||
|
||||
The default location is the standard location for the file on
|
||||
Debian systems."""
|
||||
:param filename: Local filename to read leap second data from. The
|
||||
default is the standard location for the file on Debian systems.
|
||||
:param check_hash: Whether to check the embedded hash
|
||||
"""
|
||||
with open(filename, "rb") as open_file: # pragma no cover
|
||||
return cls.from_open_file(open_file, check_hash)
|
||||
|
||||
|
|
@ -208,7 +264,10 @@ class LeapSecondData(_LeapSecondData):
|
|||
) -> "LeapSecondData":
|
||||
"""Retrieve the leap second list from a local file
|
||||
|
||||
The default location is the official copy of the data from IETF"""
|
||||
:param filename: URL to read leap second data from. The
|
||||
default is maintained by the IETF
|
||||
:param check_hash: Whether to check the embedded hash
|
||||
"""
|
||||
with urllib.request.urlopen(url) as open_file:
|
||||
return cls.from_open_file(open_file, check_hash)
|
||||
|
||||
|
|
@ -218,7 +277,12 @@ class LeapSecondData(_LeapSecondData):
|
|||
data: Union[bytes, str],
|
||||
check_hash: bool = True,
|
||||
) -> "LeapSecondData":
|
||||
"""Retrieve the leap second list from local data"""
|
||||
"""Retrieve the leap second list from local data
|
||||
|
||||
:param filename: URL to read leap second data from. The
|
||||
default is maintained by the IETF
|
||||
:param check_hash: Whether to check the embedded hash
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode("ascii", "replace")
|
||||
return cls.from_open_file(io.BytesIO(data), check_hash)
|
||||
|
|
@ -236,7 +300,11 @@ class LeapSecondData(_LeapSecondData):
|
|||
def from_open_file(
|
||||
cls, open_file: BinaryIO, check_hash: bool = True
|
||||
) -> "LeapSecondData":
|
||||
"""Retrieve the leap second list from an open file-like object"""
|
||||
"""Retrieve the leap second list from an open file-like object
|
||||
|
||||
:param filename: Readable file containing the leap second data
|
||||
:param check_hash: Whether to check the embedded hash
|
||||
"""
|
||||
leap_seconds: List[LeapSecondInfo] = []
|
||||
valid_until = None
|
||||
last_updated = None
|
||||
|
|
|
|||
|
|
@ -6,5 +6,8 @@ coverage
|
|||
mypy; implementation_name=="cpython"
|
||||
pre-commit
|
||||
setuptools>=45
|
||||
sphinx
|
||||
sphinx-autodoc-typehints
|
||||
sphinx-rtd-theme
|
||||
twine
|
||||
wheel
|
||||
|
|
|
|||
Loading…
Reference in a new issue