diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..95cb3ee --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1 @@ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..00a7b00 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ed460d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, sot +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 742390e..6629e7f 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ # ska_helpers + +This is a collection of utilities for ACA packages. +It currently includes: + +- get_version. A function to get the version from installed package information ot git. + + from ska_helpers import get_version + version = get_version('chandra_aca') + + import ska_helpers.version + print(ska_helpers.version.parse_version(version)) diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..e67e56a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = ska_helpers +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) \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..d1e5a4f --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ska_helpers documentation build configuration file, created by +# sphinx-quickstart on Fri Dec 13 09:48:53 2019. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# 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 sys +import os +sys.path.insert(0, os.path.abspath('..')) +from ska_helpers import __version__ + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# 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', + 'sphinx.ext.doctest', + 'sphinx.ext.coverage'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'ACA Helpers' +copyright = '2019, Javier Gonzalez' +author = 'Javier Gonzalez' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = __version__ +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- 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 = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +# html_sidebars = {} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ska_helpers_doc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'ska_helpers.tex', 'aca\\_helpers Documentation', + 'Javier Gonzalez', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'ska_helpers', 'ska_helpers Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'ska_helpers', 'ska_helpers Documentation', + author, 'ska_helpers', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..de328fb --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,65 @@ +.. ska_helpers documentation master file, created by + sphinx-quickstart on Fri Dec 13 09:48:53 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +ACA Package Helpers +=================== + +.. automodule:: ska_helpers + :members: + +Get Version +----------- + +.. automodule:: ska_helpers.get_version + :members: + + +Default versioning scheme +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +What follows is the scheme as described in `setuptools_scm's documentation `. + +In the standard configuration ``setuptools_scm`` takes a look at three things: + +1. latest tag (with a version number) +2. the distance to this tag (e.g. number of revisions since latest tag) +3. workdir state (e.g. uncommitted changes since latest tag) + +and uses roughly the following logic to render the version: + +no distance and clean: + ``{tag}`` +distance and clean: + ``{next_version}.dev{distance}+{scm letter}{revision hash}`` +no distance and not clean: + ``{tag}+dYYYMMMDD`` +distance and not clean: + ``{next_version}.dev{distance}+{scm letter}{revision hash}.dYYYMMMDD`` + +The next version is calculated by adding ``1`` to the last numeric component of +the tag. + +For Git projects, the version relies on `git describe `_, +so you will see an additional ``g`` prepended to the ``{revision hash}``. + + +Due to the default behavior it's necessary to always include a +patch version (the ``3`` in ``1.2.3``), or else the automatic guessing +will increment the wrong part of the SemVer (e.g. tag ``2.0`` results in +``2.1.devX`` instead of ``2.0.1.devX``). So please make sure to tag +accordingly. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..50d303a --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst +from setuptools import setup + +try: + from testr.setup_helper import cmdclass +except ImportError: + cmdclass = {} + +setup(name='ska_helpers', + description='Utilities for ska packages', + author='Javier Gonzalez', + author_email='javier.gonzalez@cfa.harvard.edu', + url='http://cxc.harvard.edu/mta/ASPECT/tool_doc/ska_helpers.html', + packages=['ska_helpers'], + tests_require=['pytest'], + use_scm_version=True, + setup_requires=['setuptools_scm', 'setuptools_scm_git_archive'], + cmdclass=cmdclass, + ) diff --git a/ska_helpers/__init__.py b/ska_helpers/__init__.py new file mode 100644 index 0000000..b52b0b9 --- /dev/null +++ b/ska_helpers/__init__.py @@ -0,0 +1,11 @@ +""" +Ska_helpers is a collection of utilities for the Ska3 runtime environment. + +ska_helpers.version +------------------- +- get_version: get the version from installed package information or git. +- parse_version: parse the version into a dict of components. +""" +from .version import get_version + +__version__ = get_version(__package__) diff --git a/ska_helpers/version.py b/ska_helpers/version.py new file mode 100644 index 0000000..f418a31 --- /dev/null +++ b/ska_helpers/version.py @@ -0,0 +1,101 @@ +""" +This module provides utilities to handle package versions. The version of a +package is determined using pkg_resources if it is installed, and +`setuptools_scm `_ otherwise. +""" + +import re +import os +from pathlib import Path +import importlib +from pkg_resources import get_distribution, DistributionNotFound + + +def get_version(package, distribution=None): + """ + Get version string for ``package`` with optional ``distribution`` name. + + If the package is not from an installed distribution then get version from + git using setuptools_scm. + + :param package: package name, typically __package__ + :param distribution: name of distribution if different from ``package`` + + :return: str + Version string + """ + # Get module for package. When called from /__init__.py this is + # not circular because that package is already in sys.modules. If the + # package does not import then ImportError is raised as normal. + module = importlib.import_module(package) + + # From this point guarantee tha a version string is returned. + try: + try: + # Get a distribution, which may or may not correspond to the package + # # that gets imported (we check that next). For some packages, e.g. + # cheta or pyyaml, the distribution will be different from the + # package. + dist_info = get_distribution(distribution or package) + version = dist_info.version + + # Check if the imported package __init__.py file has the same location + # as the distribution that was found. If working in a local git repo + # that does not have a .egg-info directory, get_distribution() + # will find an installed version. Windows does not necessarily + # respect the case so downcase everything. + assert module.__file__.lower().startswith(dist_info.location.lower()) + + # If the dist_info.location appears to be a git repo, then + # get_distribution() has gotten a "local" distribution and the + # dist_info.version just corresponds to whatever version was the + # last run of "setup.py sdist" or "setup.py bdist_wheel", i.e. + # unrelated to current version, so ignore in this case. + git_dir = Path(dist_info.location, '.git') + if git_dir.exists() and git_dir.is_dir(): + raise AssertionError + print(f'** Using DIST_INFO ({package}, {distribution}):', dist_info.location) + + except (DistributionNotFound, AssertionError): + # Get_distribution failed or found a different package from this + # file, try getting version from source repo. + from setuptools_scm import get_version + + # Define root as N directories up from location of __init__.py based + # on package name. + roots = ['..'] * len(package.split('.')) + if os.path.basename(module.__file__) != '__init__.py': + roots = roots[:-1] + version = get_version(root=Path(*roots), relative_to=module.__file__) + print(f'** Using setuptools_scm ({package}, {distribution})') + + except Exception: + # Something went wrong. The ``get_version` function should never block + # import but generate a lot of output indicating the problem. + import warnings + import traceback + warnings.warn(traceback.format_exc() + '\n\n') + warnings.warn('Failed to find a package version, setting to 0.0.0') + version = '0.0.0' + + return version + + +def parse_version(version): + """ + Parse version string and return a dictionary with version information. + This only handles the default scheme. + + :param version: str + :return: dict + """ + fmt = r'(?P[0-9]+)(.(?P[0-9]+))?(.(?P[0-9]+))?' \ + r'(.dev(?P[0-9]+))?'\ + r'(\+(?P\S)g?(?P\S+)\.(d(?P[0-9]+))?)?' + m = re.match(fmt, version) + if not m: + raise RuntimeError(f'version {version} could not be parsed') + result = m.groupdict() + for k in ['major', 'minor', 'patch', 'distance']: + result[k] = eval(f'{result[k]}') + return result