* The new version process

Providing a version on build to a python library is remarkably complex. By python convention, your module should contain a __version__ in the root module (e.g. `topologic.__version__`).

Separate from that, setuptools setup.py requests a version number as well - and you definitely want these to match. To make it more difficult, if one were to clone the repository and run
setuptools themselves without installing the requirements, they won't even be able to import topologic to get it that way.

What we ended up going with is a submodule for the version.py file to live in. This version.py looks in version.txt for a version number - and on pre-release and release, this file will contain
the actual version as part of the Github workflow action. If this file is empty, however, it will generate a version number based on the manually-adjusted semver in `version.py`, the `dev`
prerelease indicator, and the current timestamp.

Thus, local builds via setuptools or pip (which is using setuptools in the background) will get a fixed version number on build.

Lastly, if the repository is to be cloned and the package not installed, but a python interpreter opened in the root directory, it will generate a version number based on the manually-adjusted
semver in `version.py`, the `dev` prerelease indicator, and the current timestamp _at the time of importing topologic to the interpreter_.

* Slightly updated the build action file for validation, and renamed it to make a bit more sense (test_push could mean a few things, instead of 'test on push')

* Rogue prints

* Ignore any changes to version.txt - it should stay blank in version control, and it's easy to accidentally add fixed version numbers to that file and commit them. Also refer to version by relative path import not a pseudo-circular import within topologic's __init__.py file

* Also verify that the docs can be built without errors
This commit is contained in:
Dwayne Pryce 2020-02-12 12:28:53 -08:00 коммит произвёл GitHub
Родитель 97341e4df1
Коммит 2ea5e2a2db
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 73 добавлений и 93 удалений

Просмотреть файл

@ -3,7 +3,7 @@ name: Type check, lint, and test
on: [push]
jobs:
build:
validate:
runs-on: ubuntu-latest
@ -31,3 +31,9 @@ jobs:
- name: Test with pytest
run: |
pytest tests topologic --doctest-modules
- name: Build with setuptools
run: |
python setup.py build sdist
- name: Generate docs with Sphinx
run: |
sphinx-build -W -a docs/ docs/_build/html

3
.gitignore поставляемый
Просмотреть файл

@ -120,3 +120,6 @@ venv.bak/
.vscode/
*.code-workspace
# ignore any changes to topologic/version/version.txt (this is easy to do - we want this file to exist, but be empty and not include any changes)
topologic/version/version.txt

Просмотреть файл

@ -1,2 +1,2 @@
recursive-include topologic version.txt
recursive-include topologic/version version.txt
prune tests

Просмотреть файл

@ -35,9 +35,9 @@ copyright = '(C) Microsoft Corporation. All rights reserved.'
author = 'Microsoft Corporation'
# The short X.Y version
version = topologic.version.__semver
version = topologic.version.version.__semver
# The full version, including alpha/beta/rc tags
release = topologic.version.get_version()
release = topologic.version.version.version
# -- General configuration ---------------------------------------------------

Просмотреть файл

@ -1,15 +1,21 @@
# Copyright (C) Microsoft Corporation. All rights reserved.
import setuptools
import sys
import os
# Python 3 + build tools are required to install this library. To install the
# prerequisites on Ubuntu, run
# sudo apt-get install build-essential python3 python3-dev python3-venv
version_file_path = os.path.abspath("topologic/version.txt")
exec(open('topologic/version.py').read())
def handle_version() -> str:
sys.path.insert(0, os.path.join("topologic", "version"))
from version import version
sys.path.pop(0)
version = get_version(version_file_path)
version_path = os.path.join("topologic", "version", "version.txt")
with open(version_path, "w") as version_file:
b = version_file.write(f"{version}")
return version
version = handle_version()
setuptools.setup(
name="topologic",
@ -19,13 +25,20 @@ setuptools.setup(
"processing networkx graph objects and statistical analysis over these objects.",
version=version,
packages=setuptools.find_packages(exclude=["tests", "tests.*", "tests/*"]),
package_data={'': ['version.txt']},
package_data={'version': [os.path.join('topologic', 'version', 'version.txt')]},
include_package_data=True,
author="Dwayne Pryce",
author_email="dwpryce@microsoft.com",
license="MIT",
classifiers=[
"Programming Language :: Python :: 3"
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License"
],
project_urls = {
"Github": "https://github.com/microsoft/topologic",
"Documentation": "https://topologic.readthedocs.io",
},
url="https://github.com/microsoft/topologic",
install_requires=[
'networkx',
'python-louvain>=0.13',

Просмотреть файл

@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
from .version import get_version
from .version.version import name, version as __version__
# VITAL NOTE: ORDER MATTERS
from .exceptions import DialectException, InvalidGraphError, UnweightedGraphError
@ -58,8 +58,3 @@ __all__ = [
'self_loop_augmentation',
'UnweightedGraphError'
]
name = 'topologic'
# __build_version__ is defined by the VSTS build and put into version.py. Copying the variable here so that
# we define __version__ in the standard __init__.py instead of version.py
__version__ = get_version()

Просмотреть файл

@ -1,75 +0,0 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import datetime
from typing import List, Optional, Tuple
import pkg_resources
__all__: List[str] = ["get_version_and_type", "get_version"]
# manually updated
__semver = "0.1.0b1"
package_name = "topologic"
__version_file = "version.txt"
def _from_resource() -> str:
version_file = pkg_resources.resource_stream(package_name, __version_file)
version_file_contents = version_file.read()
return version_file_contents.decode("utf-8").strip()
def _from_path(path: str) -> str:
with open(path) as version_file:
version_line = version_file.readline()
return version_line.strip()
def version_file_as_str(version_file_path: Optional[str]) -> str:
# in most cases, we want to treat this as a package relative file
if version_file_path is None:
return _from_resource()
else:
# however, if we need to get the version prior to calling setuptools, we need to read from a file instead
return _from_path(version_file_path)
def local_build_number() -> str:
return datetime.datetime.today().strftime('%Y%m%d.0+local')
def version_from_file(version_file_path: Optional[str] = None) -> Tuple[str, str]:
build_type: str = ""
build_number: str = ""
try:
version_file_contents: str = version_file_as_str(version_file_path)
if len(version_file_contents) == 0:
raise ValueError("Empty file, fallback to local snapshot")
version_file_values = version_file_contents.strip().split(",")
if len(version_file_values) == 2:
temp_type = version_file_values[0].lower()
temp_build_number = version_file_values[1]
timestamp, daily_build_number = temp_build_number.split(".")
combined_build_number = f"{timestamp}{daily_build_number.rjust(3, '0')}"
if temp_type == "snapshot" or temp_type == "release":
build_type = temp_type
build_number = combined_build_number
else:
raise ValueError("Unknown build type, fallback to local snapshot")
else:
raise ValueError("Unknown version file format, fallback to local snapshot")
except (FileNotFoundError, ValueError):
build_type = "snapshot"
build_number = local_build_number()
return build_type, build_number
def get_version_and_type(version_file_path: Optional[str] = None) -> Tuple[str, str]:
build_type, build_number = version_from_file(version_file_path)
return (f"{__semver}.dev{build_number}", build_type) if build_type == "snapshot" else (__semver, build_type)
def get_version(version_file_path: Optional[str] = None) -> str:
version, _ = get_version_and_type(version_file_path)
return version

Просмотреть файл

@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

Просмотреть файл

@ -0,0 +1,36 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
import datetime
from typing import List
import pkg_resources
__all__: List[str] = ["version", "name"]
name = "topologic"
# 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()

Просмотреть файл