Skip to content

Commit

Permalink
Suitably dynamic versioning (graspologic-org#467)
Browse files Browse the repository at this point in the history
* Suitably dynamic versioning

The following versioning code bypasses a few problems with python module versions.  The following scenarios are plausible:
- A user clones `graspologic` and runs `pip install -r requirements.txt` then executes `python` in the project directory, accessing the graspologic library by python's local folder structure.
- A users clones `graspologic` and runs `python setup.py install` in the environment of their choice, accessing the graspologic library either by the local folder structure or the .egg in their site-packages, depending on their current working directory.
- A user clones no repository and wants to install the library solely via `pip` via the `pip install ...` command, which has 2 wings to consider:
  - The user wishes to try out the latest prerelease, which is going to be published with a X.Y.ZdevYYYYMMDDBUILDNUMBER style version and can be installed via `pip install graspologic --pre`
  - The user wishes to try out the latest release, which will be published as `X.Y.Z`.

This PR supports those 4 cases (notably, it does not support `pip install .` from the root project directory, which does some super weird stuff and I gave up on trying to solve it a long time ago)

The concept is this: the actual version upon a **build action**, which can be undertaken by:
- CI building a snapshot build
- CI building a release build
- Local user building a local build

These states all require the same thing: a materialized version in a file.  This version should be created at the time of this build action.

In the case of CI, we can populate the file in our CI build process and move on.  It's the case of not being in CI where we need to consider what to do next, which leaves Local user building a local build (and local user using the filesystem as the library).

In these cases, the solution is the following: if we have a populated version.txt file, we use it. If we do not, we materialize a new version based on the `__semver` in version.py and the current time in YYYYMMDDHHmmSS format. This means that if you are running on the filesystem, and you say `import graspy; print(graspy.__version__);`, it will actually tell you the version is `0.1.0dev20200926120000` as an example.  However, when you close the interpreter and do it again, it will tell you that the version is `0.1.0dev20200926120500` - because it will create a version for you at the time of import.

However, if you were to run `python setup.py install`, the setup.py file actually takes it on itself to either get a version number or use the materialized version described above, then to write it to version.txt.  Which means that installing the graspologic library from setuptools will actually lock in the version number in perpetuity.

Gotchas
- version.txt must always be empty in the source tree
- `pip install .` does some weird thing where it registers an entry in site-packages that is like a symlink to the local filesystem anyway so it doesn't actually make an egg which means you get a new version each time and I gave up caring at this point since we got the three primary use cases: developers, users of pre-releases, and users of releases all covered. Users who install by cloning and running pip install are just going to get a weird behavior that probably isn't that important to track down, and regardless they'll get a clear `X.Y.Zdev<timestamp>` in their `graspologic.__version__` which is enough for us to go on if there are any issues raised.

* My testing resulted in filling this file and committing it, like I said not to do

* Updated conf.py for sphinx to be able to find a version it likes.  Or kinda likes.  Maybe likes?

* Forgot I had to add the recursive-include for the version file.

* Making black happy
  • Loading branch information
Dwayne Pryce authored and Dwayne Pryce committed Feb 26, 2021
1 parent 5d0aa9a commit 020e451
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 24 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
recursive-include graspy/datasets *.csv
recursive-include graspy/version version.txt
exclude tests/*
17 changes: 8 additions & 9 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@
copyright = "2018"
authors = u"NeuroData"

# The short X.Y version
# Find GraSPy version.
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
for line in open(os.path.join(PROJECT_PATH, "..", "graspy", "__init__.py")):
if line.startswith("__version__ = "):
version = line.strip().split()[2][1:-1]

# The full version, including alpha/beta/rc tags
release = "alpha"
realpath = os.path.realpath(__file__)
dir_realpath = os.path.dirname(os.path.dirname(realpath))
sys.path.append(dir_realpath)

import graspy

version = graspy.version.version.__semver
release = graspy.version.version.version

# -- Extension configuration -------------------------------------------------
extensions = [
Expand Down
5 changes: 2 additions & 3 deletions graspy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) Microsoft Corporation and contributors.
# Licensed under the MIT License.

from .version.version import name, version as __version__

import warnings

import graspy.cluster
Expand All @@ -17,6 +19,3 @@
warnings.filterwarnings("ignore", category=RuntimeWarning)
warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
warnings.simplefilter("always", category=UserWarning)


__version__ = "0.3.0"
2 changes: 2 additions & 0 deletions graspy/version/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
38 changes: 38 additions & 0 deletions graspy/version/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import datetime
from typing import List
import pkg_resources

__all__: List[str] = ["version", "name"]

name = (
"graspy" # TODO: #454 Update in https://github.com/microsoft/graspologic/issues/454
)

# manually updated
__semver = "0.1.0"
# full version (may be same as __semver on release)
__version_file = "version.txt"


def _from_resource() -> str:
version_file = pkg_resources.resource_stream(__name__, __version_file)
version_file_contents = version_file.read()
return version_file_contents.decode("utf-8").strip()


def local_build_number() -> str:
return datetime.datetime.today().strftime("%Y%m%d%H%M%S")


def get_version() -> str:
version_file_contents = _from_resource()
if len(version_file_contents) == 0:
return f"{__semver}.dev{local_build_number()}"
else:
return version_file_contents


version = get_version()
Empty file added graspy/version/version.txt
Empty file.
33 changes: 21 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@
import os
import sys
from setuptools import setup, find_packages
from sys import platform
from typing import Tuple


def package_metadata() -> Tuple[str, str]:
sys.path.insert(0, os.path.join("graspy", "version")) # TODO: #454 Change path in https://github.com/microsoft/graspologic/issues/454
from version import name, version
sys.path.pop(0)

version_path = os.path.join("graspy", "version", "version.txt")
with open(version_path, "w") as version_file:
_b = version_file.write(f"{version}")
return name, version


PACKAGE_NAME, VERSION = package_metadata()

PACKAGE_NAME = "graspy"
DESCRIPTION = "A set of python modules for graph statistics"
with open("README.md", "r") as f:
LONG_DESCRIPTION = f.read()
AUTHOR = ("Eric Bridgeford, Jaewon Chung, Benjamin Pedigo, Bijan Varjavand",)
AUTHOR_EMAIL = "j1c@jhu.edu"
URL = "https://github.com/neurodata/graspy"
MINIMUM_PYTHON_VERSION = 3, 6 # Minimum of Python 3.5
MINIMUM_PYTHON_VERSION = 3, 6 # Minimum of Python 3.6
REQUIRED_PACKAGES = [
"networkx>=2.1",
"numpy>=1.8.1",
Expand All @@ -25,13 +38,6 @@
]


# Find GraSPy version.
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
for line in open(os.path.join(PROJECT_PATH, "graspy", "__init__.py")):
if line.startswith("__version__ = "):
VERSION = line.strip().split()[2][1:-1]


def check_python_version():
"""Exit when the Python version is too low."""
if sys.version_info < MINIMUM_PYTHON_VERSION:
Expand All @@ -48,18 +54,21 @@ def check_python_version():
long_description_content_type="text/markdown",
author=AUTHOR,
author_email=AUTHOR_EMAIL,
maintainer="Dwayne Pryce",
maintainer_email="dwpryce@microsoft.com",
install_requires=REQUIRED_PACKAGES,
url=URL,
license="MIT",
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Mathematics",
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
packages=find_packages(),
packages=find_packages(exclude=["tests", "tests.*", "tests/*"]),
include_package_data=True,
package_data={'version': [os.path.join('graspy', 'version', 'version.txt')]}, # TODO: #454 Also needs changed by https://github.com/microsoft/graspologic/issues/454,
)

0 comments on commit 020e451

Please sign in to comment.