Polyvers¶
version: | 0.1.1a1+6.gbb90aa2 |
---|---|
updated: | 20/05/2019 13:08:06 |
Documentation: | https://polyvers.readthedocs.io |
repository: | https://github.com/ankostis/polyvers |
pypi-repo: | https://pypi.org/project/polyvers/, https://pypi.org/project/polyversion/ |
keywords: | version-management, configuration-management, versioning, git, monorepos, tool, library |
copyright: | 2018 JRC.C4(STU), European Commission (JRC) |
license: | EUPL 1.2 |
A Python 3.6+ command-line tool to manage PEP-440 version-ids of dependent sub-projects hosted in a Git monorepos, independently.
The key features are:
- setuptools integration,
- x2 repo schemes (monorepo, mono-project),
- configurable version scheme,
- leaf release scheme,
- intuitive version-bump algebra (TODO),
- configurable engravings.
The leaf version scheme feature departs from the logic of Similar Tools. Specifically, when bumping the version of sub-project(s), this tool adds +2 tags and +1 commits:
one Version tag in-trunk like
foo-proj-v0.1.0
,and another Release tag on a new out-of-trunk commit (leaf) like
foo-proj-r0.1.0
(the new version-ids are engraved only in this release-commit):![]()
Leaf-commits & version/release-tags for the two sub-project’s, as shown in this repo’s git history.
Note
The reason for this feature is to allow exchange code across branches (for the different sub-projects) without engravings getting in your way as merge-conflicts.
Additional capabilities and utilities:
- It is still possible to use plain version tags (vtags) like
v0.1.0
, assuming you have a single project (called hereinafter a mono-project) - A separate Python 2.7+ polyversion project, which contains API to extract
sub-project’s version from past tags (provided as a separate subproject
so client programs do not get
polyvers
commands transitive dependencies). The library functions as a setuptools plugin.
Usage: polyvers cmdline-tool¶
Tutorial¶
Install the tool¶
And you get the polyvers
command:
$ pip install polyvers
...
$ polyvers --version
0.0.0
$ polyvers --help
...
$ polyvers status
polyvers: Neither `setup.py` nor `.polyvers(.json|.py|.salt)` config-files found!
Note
Actually two projects are installed:
- polyvers cmd-line tool, for developing python monorepos,
- polyversion: the base python library used by projects developed with polyvers tool, so that their sources can discover their subproject-version on runtime from Git.
Prepare project¶
Assuming our monorepo project /monorepo.git/
contains two sub-projects,
then you need enter the following configurations into your build files:
/monorepo.git/
+--setup.py # see below for contents
+--mainprog/__init__.py
| from polyversion import polyversion, polytime
| __version__ = polyversion()
| __updated__ = polytime()
| ...
|
+--core-lib/
+--setup.py: # like above
+--core/__init__.py # like above
+--...
Tip
You may see different sample approaches for your setup-files by looking into both polyvers & polyversion subprojects of this repo (because they eat their own dog food).
The polyversion library function as a setuptools “plugin”, and
adds a new setup()
keyword polyversion = (bool | dict)
(see polyversion.init_plugin_kw()
for its content), which you can use it
like this:
from setuptools import setup
setup(
project='myname',
version='' # omit (or None) to abort if cannot auto-version
polyversion={ # dict or bool
'version_scheme: 'mono-project',
... # See `polyversion.init_plugin_kw()` for more keys.
},
setup_requires=[..., 'polyversion'],
...
)
Hint
The setup_requires=['polyvers']
keyword (only available with setuptools,
and not distutils), enables the new polyversion=
setup-keyword.
Alternatively, a subproject may use PEP 0518 to pre-install polyversion
library before pip-installing or launching setup.py
script.
To do that, add the pyproject.toml
file below next to your setup script:
[build-system]
requires = ["setuptools", "wheel", "polyversion"]
and then you can simply import polyversion
from your setup.py
:
from setuptools import setup
from polyversion import polyversion
setup(
project='myname',
version=polyversion(mono_project=True) # version implied empty string.
Attention
To properly install a PEP 0518 project you need pip-v10+
version.
Initialize polyvers¶
…we let the tool auto-discover the mapping of project folders ↔ project-names
and create a traitlets configuration YAML-file
named as /monorepo.git/.polyvers.py
:
$ cd monorepo.git
$ polyvers init --monorepo
Created new config-file '.polyvers.yaml'.
$ cat .polyvers.yaml
...
PolyversCmd:
projects:
- pname: mainprog # name extracted from `setup.py`.
basepath: . # path discovered by the location of `setup.py`
- pname: core
basepath: core-lib
...
$ git add .polyvers.yaml
$ git commit -m 'add polyvers config-gile'
And now we can use the polyvers
command to inspect the versions of all
sub-projects:
$ polyvers status
- mainprog
- core
Indeed there are no tags in in git-history for the tool to derive and display
project-versions, so only project-names are shown. With --all
option
more gets displayed:
$ polyvers status -a
- pname: mainprog
basepath: .
gitver:
history: []
- pname: core
basepath: core-lib
gitver:
history: []
..where gitver
would be the result of git-describe
.
Bump versions¶
We can now use tool to set the same version to all sub-projects:
$ polyvers bump 0.0.0 -f noengraves # all projects implied, if no project-name given
00:52:06 |WARNI|polyvers.bumpcmd.BumpCmd|Ignored 1 errors while checking if at least one version-engraving happened:
ignored (--force=noengraves): CmdException: No version-engravings happened, bump aborted.
00:52:07 |NOTIC|polyvers.bumpcmd.BumpCmd|Bumped projects: mainprog-0.0.0 --> 0.0.0, core-0.0.0 --> 0.0.0
The --force=noengraves
disables a safety check that requires at least one
file modification for engraveing the current version in the leaf “Release” commit
(see next step).
$ polyvers status
- mainprog-v0.0.0
- core-v0.0.0
$ git lg # Ok, augmented `lg` output a bit here...HEAD --> UPPER branch.
COMMITS BRANCH TAGS REMARKS
======= ====== ==================== ========================================
O latest mainprog-r0.0.0 - x2 tags on "Release" leaf-commit
/ core-r0.0.0 outside-of-trunk (not in HEAD).
O MASTER mainprog-v0.0.0 - x2 tags on "Version" commit
| core-v0.0.0 for bumping both projects to v0.0.0
O - Previous commit, before version bump.
.. Hint::
Note the difference between ``ABC-v0.0.0`` vs ``ABC-r0.0.0`` tags.
In the source code, it's only the "release" commit that has :term:`engrave`\d* version-ids:
.. code-block:: console
$ cat mainprog/mainprog/__init__.py # Untouched!
import polyvers
__title__ = "mainprog"
__version__ = polyvers.version('mainprog')
...
$ git checkout latest
$ cat mainprog/mainprog/__init__.py
import polyvers
__title__ = "mainprog"
__version__ = '0.0.0'
...
$ git checkout - # to return to master.
Engrave version in the sources¶
Usually programs report their version somehow when run, e.g. with `cmd --version
.
With polyvers we can derive the latest from the tags created in the previous step,
using a code like this, usually in the file /mainprog/mainprog/__init__.py:
:
import polyvers
__title__ = "mainprog"
__version__ = polyvers.version('mainprog')
...
…and respectively /core-lib/core/__init__.py:
:
__version__ = polyvers.version('core')
Bump sub-projects selectively¶
Now let’s add another dummy commit and then bump ONLY ONE sub-project:
$ git commit --allow-empty -m "some head work"
$ polyvers bump ^1 mainprog
00:53:07 |NOTIC|polyvers.bumpcmd.BumpCmd|Bumped projects: mainprog-0.0.0 --> 0.0.1
$ git lg
COMMITS BRANCH TAGS REMARKS
======= ====== ==================== ========================================
O latest mainprog-r0.0.1.dev0 - The latest "Release" leaf-commit.
/ branch `latest` was reset non-ff.
O MASTER mainprog-v0.0.1.dev0 - The latest "Version" commit.
O - some head work
| O mainprog-r0.0.0 - Now it's obvious why "Release" commits
|/ core-r0.0.0 are called "leafs".
O mainprog-v0.0.0
| core-v0.0.0
O
$ git checkout latest
$ cat mainprog/mainprog/__init__.py
import polyvers
__title__ = "mainprog"
__version__ = '0.0.1.dev0'
...
$ cat core/core/__init__.py
import polyvers
__title__ = "core"
__version__ = '0.0.0+mainprog.0.0.1.dev0'
...
$ git checkout -
Notice how the the “local” part of PEP-440 (statring with +...
)
is used by the engraved version of the un-bumped core
project to signify
the correlated version of the bumped mainprog
. This trick is not necessary
for tags because they apply repo-wide, to all sub-projects.
Features¶
- PEP 440 version ids
While most versioning tools use Semantic versioning, python’s
distutils
native library supports the quasi-superset, but more versatile, PEP-440 version ids, like that:Pre-releases: when working on new features:
X.YbN # Beta release X.YrcN or X.YcN # Release Candidate X.Y # Final release
Post-release:
X.YaN.postM # Post-release of an alpha release X.YrcN.postM # Post-release of a release candidate
Dev-release:
X.YaN.devM # Developmental release of an alpha release X.Y.postN.devM # Developmental release of a post-release
- version-bump algebra
- When bumping, the increment over the base-version can be specified with a
“relative version”, which is a combination of PEP 0440 segments and
one of these modifiers:
+^~=
Seepolyvers.vermath
for more. - repo scheme
- monorepo
- mono-project
- monorepo
whether a git repo hosts a single or multiple subprojects
Rational:
When your single project succeeds, problems like these are known only too well:
Changes in web-server part depend on core features that cannot go public because the “official” wire-protocol is freezed.
While downstream projects using core as a library complain about its bloated transitive dependencies (asking why flask library is needed??).
So the time to “split the project” has come. But from Lerna:
«Splitting up large codebases into separate independently versioned packages is extremely useful for code sharing. However, making changes across many repositories is messy and difficult to track, and testing across repositories gets complicated really fast.»So a monorepo [1] [2] is the solution. But as Yarn put it:
«OTOH, splitting projects into their own folders is sometimes not enough. Testing, managing dependencies, and publishing multiple packages quickly gets complicated and many such projects adopt tools such as …»Polyvers is such a tool.
[1] <https://medium.com/@maoberlehner/monorepos-in-the-wild-33c6eb246cb9 [2] http://www.drmaciver.com/2016/10/why-you-should-use-a-single-repository-for-all-your-companys-projects/ - version scheme
the pattern for version-tags. 2x2 versioning schemes are pre-configured, for mono-project and monorepo repositories, respectively:
- v1.2.3 (and r1.2.3 applied on leaf commits)
- project-v1.2.3 (and project-r1.2.3 for leaf commits)
- release scheme
- out-of-trunk commit
- leaf commit
- release tag
- r-tag
- version tag
- v-tag
- out-of-trunk commit
Even in single-project repos, sharing code across branches may cause merge-conflicts due to the version-ids engraved” in the sources. In monorepos, the versions proliferate, and so does the conflicts.
Contrary to Similar Tools, static version-ids are engraved only in out-of-trunk (leaf) commits, and only when the sub-projects are released. In-trunk code is never touched, and version-ids are reported, on runtime, based on Git tags (like
git-describe
), so they are always up-to-date.- engrave
- engravings
- the search-n-replace in files, to substitute the new version.
Default grep-like substitutions are included, which can be re-configured
in the
.polyvers.yaml
config file. - setuptools
- setuptools plugin
- setuptools integration
- setuptools plugin
- The polyversion library function as a setuptools “plugin”, and
adds a new
setup()
keywordpolyversion = (bool | dict)
(seepolyversion.init_plugin_kw()
for its content). - bdist-check
When the
setuptools:setup()
keywordpolyversion_check_bdist_enabled = True
the setuptools plugin aborts any bdist… commands if they are not run from engraved sources, (ie from an r-tag).To enable this check without editing the sources, add the following into your
$CWD/setup.cfg
file:[global] polyversion_check_bdist_enabled = true ...
- Marking dependent versions across sub-projects
- [TODO] When bumping the version of a sub-project the “local” part of PEP-440 on all other the dependent sub-projects in the monorepo signify their relationship at the time of the bump.
- Lock release trains as “developmental”
- [TODO] Specific branches can be selected always to be published into PyPi only as
PEP-440’s “Developmental” releases, meanining that
users need
pip install --pre
to install from such release-trains. This is a safeguard to avoid accidentally landing half-baked code to users. - default version env-var
- From which env-var to read a project’s version if git cmd fail.
It does not override any value given as
default_version
keyword forpolyversion.polyversion()
. Alsopolyversion.polytime()
assumes keywordno_raise=True
if such env-var is found. [Default var-name:<pname>_VERSION
] - Other Features
- Highly configurable using traitlets,
with sensible defaults; it should be possible to start using the tool
without any config file (see init cmd), or by adding one of the flags
--monorepo
/--mono-project
in all commands, in the face of conflicting tags. - Always accurate version reported on runtime when run from git repos (never again wonder with which version your experimental-data were produced).
- Highly configurable using traitlets,
with sensible defaults; it should be possible to start using the tool
without any config file (see init cmd), or by adding one of the flags
Known Limitations, Drawbacks & Workarounds¶
PEP440 Epoch handling is not yet working.
Version-bump’s grammar is not yet as described in “GRAMMAR” section of command’s doc:
$ polyvers config desc --class BumpCmd BumpCmd(_SubCmd) ---------------- Increase or set the version of project(s) to the (relative/absolute) version. SYNTAX: polyvers config desc [OPTIONS] <version> [<project>]... - If no project(s) specified, increase the versions on all projects. - Denied if version for some projects is backward-in-time (or has jumped parts?); use --force if you might. VERSION: - A version specifier, either ABSOLUTE, or RELATIVE to the current version og each project: - *ABSOLUTE* PEP-440 version samples: - Pre-releases: when working on new features: X.YbN # Beta release X.YrcN or X.YcN # Release Candidate X.Y # Final release ...
(not related to this tool) In
setup.py
script, the kw-argumentpackage_dir={'': <sub-dir>}
arg is needed for py_modules to work when packaging sub-projects (also useful withfind_packages()
, check this project’s sources). But<sub-dir>
must be relative to launch cwd, or else,pip install -e <subdir>
and/orpython setup.py develop
break.(not related to this tool) When building projects with
python setup.py bdist_XXX
, you have to clean up your build directory (e.g.python setup.py clean --all
) or else, the distribution package will contain the sources from all previous subprojects built. That applies also when rebuilding a project between versions.Installing directly from git-repos needs an engraved branch (e.g.
latest
):pip install git+https://github.com/ankostis/polyvers@latest
If you still want to install non-engraved branhces (e.g.
master
), set the default version env-var; for example, since polyvers subproject has not customized the name of its env-var, you may install the very latest like this:polyvers_VERSION=1.2.3 pip install git+https://github.com/ankostis/polyvers
Attention
The version given in the env-var is irrelevant. The installed version will still derive from git tags, and the local-part from the actual git-commit.
(not related to this tool) If you don’t place a
setup.py
file at the root of your git-repo (usingpackage_dir
argument tosetup()
function or infind_packages()
, according to setuptools-docs), then in order topip install git+https://...
directly from remote URLs you have to use this official trick. For example, to install polyversion subproject:pip install "git+https://github.com/ankostis/polyvers@latest#egg=polyversion&subdirectory=pvlib"
Notice that the quotes are needed to escape the
&
char from bash. Respectively, use this to install from the very latest:polyversion_VERSION=1.2.3 pip install git+https://github.com/ankostis/polyvers#egg=polyversion&subdirectory=pvlib"
Set branch
latest
as default in GitHub to show engraved sub-project version-ids.See TODOs.
Usage: polyversion library¶
version: | 0.1.1a1+6.gbb90aa2 |
---|---|
updated: | 20/05/2019 13:08:10 |
Documentation: | http://polyvers.readthedocs.io/en/latest/usage-pvlib.html |
repository: | https://github.com/ankostis/polyvers |
pypi-repo: | https://pypi.org/project/polyversion/ |
copyright: | 2018 JRC.C4(STU), European Commission (JRC) |
license: | MIT License |
The python 2.7+ library needed by (sub-)projects managed by polyvers cmd to derive their version-ids on runtime from Git.
Specifically, the configuration file .polyvers.yaml
is NOT read -
you have to repeat any non-default configurations as function/method keywords
when calling this API.
Here only a very rudimentary documentation is provided - consult polyvers documents provided in the link above.
Note
Only this library is (permissive) MIT-licensed, so it can be freely vendorized by any program - the respective polyvers command-line tool is “copylefted” under EUPLv1.2.
Quickstart¶
- There are 4 ways to use this library:
- As a setuptools plugin;
- through its Python-API (to dynamically version your project);
- through its barebone cmdline tool:
polyversion
(installation required); - through the standalone executable wheel:
bin/pvlib.run
(no installation, but sources required; behaves identically topolyversion
command).
setuptools usage¶
The polyversion library function as a setuptools “plugin”, and
adds two new setup()
keywords for deriving subproject versions
from PKG-INFO or git tags (see polyversion.init_plugin_kw()
):
- keyword:
polyversion --> (bool | dict)
When a dict, its keys roughly mimic those in
polyversion()
, and can be used like this:from setuptools import setup setup( project='myname', version='' # omit (or None) to abort if cannot auto-version polyversion={ # dict or bool 'mono_project': True, # false by default ... # See `polyversion.init_plugin_kw()` for more keys. }, setup_requires=[..., 'polyversion'], ... )
- keyword:
- keyword:
polyversion_check_bdist_enabled --> bool
When it is true, the bdist-check is enabled, and any bdist_* setup-commands (e.g.
bdist_wheel
) will abort if not run from engraved sources (ie from an release tag).To enable this check without editing the sources, add the following into your
$CWD/setup.cfg
file:[global] polyversion_check_bdist_enabled = true ...
- keyword:
API usage¶
An API sample of using also polytime()
from within your
myproject.git/myproject/__init__.py
file:
from polyversion import polyversion, polytime # no hack, dependency already installed
__version__ = polyversion() # project assumed equal to this module-name: 'myproject'
__updated__ = polytime()
...
Tip
Depending on your repo’s versioning scheme (eg you have a mono-project repo,
with version-tags simply like vX.Y.Z
), you must add in both invocations
of polyversion.polyversion()
above the kw-arg mono_project=True
.
Console usage¶
The typical command-line usage of this library (assuming you don’t want to install the full blown polyvers command tool) is given below:
user@host:~/ $ polyversion --help
Describe the version of a *polyvers* projects from git tags.
USAGE:
polyversion [PROJ-1] ...
polyversion [-v | -V ] # print my version information
user@host:~/ $ polyversion polyversion # fails, not in a git repo
b'fatal: not a git repository (or any of the parent directories): .git\n'
cmd: ['git', 'describe', '--match=cf-v*']
Traceback (most recent call last):
File "/pyenv/site-packages/pvlib/polyversion/__main__.py", line 18, in main
polyversion.run(*sys.argv[1:])
File "/pyenv/site-packages/pvlib/polyversion/__init__.py", line 340, in run
res = polyversion(args[0], repo_path=os.curdir)
File "/pyenv/site-packages/pvlib/polyversion/__init__.py", line 262, in polyversion
pvtag = _my_run(cmd, cwd=repo_path)
File "/pyenv/site-packages/pvlib/polyversion/__init__.py", line 106, in _my_run
raise sbp.CalledProcessError(proc.returncode, cmd)
subprocess.CalledProcessError: Command '['git', 'describe', '--match=cf-v*']' returned non-zero exit status 128.
user@host:~/ $ cd polyvers.git
user@host:~/polyvers.git (dev) $ polyversion polyvers polyversion
polyvers: 0.0.2a10
polyversion: 0.0.2a9
Standalone wheel¶
Various ways to use the standalone wheel from bash (these will still work without having installed anything):
user@host:~/polyvers.git (master) $
user@host:~/polyvers.git (master) $ ./bin/pvlib.run polyversion
polyversion: 0.0.2a9
user@host:~/polyvers.git (master) $ python ./bin/pvlib.run --help
...
user@host:~/polyvers.git (master) $ python ./bin/pvlib.run -m polyversion -v
version: 0.0.2a9
user@host:~/polyvers.git (master) $ PYTHONPATH=./bin/pvlib.run python -m polyversion -V
version: 0.0.2a9
updated: Thu, 24 May 2018 02:47:37 +0300
For the rest, consult the polyvers project: https://polyvers.readthedocs.io
Changes¶
- . contents:: Releases
local:
TODOs¶
Parse
git describe
like setuptools_scm plugin does.Drop pvcmd/pvtags.py, and replace it with polyversion?
Engravings: Not easy to extend!
Configurable hooks - refactor engravings as one of them. to run, for example, housekeeping commands on all subprojects like
pip install -e <project>
and immediately start working in “develop mode”.This would allow housekeeping commands and validate tests before/after every bump:
## Pre-release hook # pytest tests ## Post-release hook # rm -r dist/* build/*; python setup.py sdist bdist_wheel twine upload dist/*whl -s
Add top-level engrave glob-excludes.
Use astor grafter.
Refactor version-bump algebra to support a single modifier per segment (see
multivermath
branch).Lock release-trains as “alpha/beta”.., specific branches can be selected Based on version-bump algebra), this will force users to always use
pip install --pre
to fetch such release-trains. This is a safeguard to avoid accidentally landing half-baked code to users.Retrofit polyversion library as a plugin of polyvers command.
Function as plugin for other 3rd-party projects, bake a cookiecutter
Check what happens with no-commit, e.g. to temporarily package a wheel.
2018-08-07: polyversion-v0.2.2a1, polyvers-v0.1.1a1¶
Maintenance release.
- enh: polyversion now logs which was cmd not found on Windows
(usually it is executing
git
). - chore: Merge all pyupd requests for dependencies.
2018-07-08: polyversion-v0.2.2a0¶
FIX: git < 2.15.0 was buggy with multiple match-patterns in command:
git describe --match=... --match=...
(see https://github.com/git/git/blob/master/Documentation/RelNotes/2.15.0.txt)
The
polyvers
cmd still affected by this.
2018-07-06: polyvers-v0.1.1a0¶
FIX: engravings were applied unsorted (based on their start-point) and offsets were miss-located when switching graft in a same file.
2018-07-05: polyversion-v0.2.1a0¶
- Feature: The
POLYVERSION_LOG_LEVEL
control polyversion verbosity. Runpolyversion -h
fo help. - Change: minor reordering when searching version from package-metadata.
- fix: add standalone
bin/pvlib.run
from last release. - fix:
polyversion()
/polytime()
are guessingbasepath
keyword from the path of caller’s top-package (not just from caller’s fpath).
2018-07-04: polyversion-v0.2.0a2¶
- Version v0.2.0a0 not in pypi, consumed for standalone
bin/pvlib.run
. - Version v0.2.0a1 were not finding sbling-dir versions if
pip install git+...
, and had not replaced all skip-bdist flags.
Features¶
- Teach non-engraved projects how to retrieve polyversion when pip-installed:
- The functions
polyversion()
&polytime()
now attempt to fetch version from package/site-package infos. - And the function doing this
polyversion.pkg_metadata_version()
retrofitted to:- search for <pname-<version>.egg-info/PKG-INFO in baspath sibling folder (before searching PKG-INFO, METADATA in basepath),
- so now basepath always needed in
polyversion()/polytime()
functions to locate sibling dir.
- The functions
Breaking Changes¶
- Rename setuptools flag from
skip_polyversion_check --> polyversion_check_bdist_enabled
to flip its default logic (not checking by default), since non-engraved wheels install just fine now. - Rename the keyword of
polyversion()
/polytime()
functions fromrepo_path --> basepath
to denote its importance for retrieving the version of installed projects from sibling dirs insidePYTHONPATH/site-packages/
.
2018-06-29: polyversion-v0.1.1a3¶
(change actually done in v0.1.1a1, just a fixes & doc-msg in a2)
- FIX: teach setuptools plugin about default version env-var.
Now can
pip install git+https://some.git.repo/but-from/non-engraved-branch
.
2018-06-27: polyversion-v0.1.1a0¶
- FEAT: Introduce configurable default version env-var to fall-back
to
<pname>_VERSION
if it exists, in case of errors (e.g. no git). The presence of such a variable also setspolytime(no_raise=True)
, which now also support thepname
anddefault_version_env_var
kwds.
2018-06-06: polyvers-v0.1.0a1, polyversion-v0.1.0a7¶
Mostly docs, combined release.
- FEAT: reinstated engravings on
_version.py
(see previous release for rational).
2018-06-05: polyvers-v0.1.0a0, polyversion-v0.1.0a6: co2mpas-ready¶
- FEAT: reinstated engravings on
setup.py
(dropped only for a while in 2018-06-03: polyversion-v0.1.0a3: setuptools ), since, assuming clients have adopted the new setuptools plugin keyword, it is the default_version that will be engraved, which is fine. - fix: report any version matched both from v-tags and r-tag’s.
- fix:
bump
command does not engrave egg-related files. polyversion
command got a bit more civilized (with logging to explain problems with related stacktraces.- dev: don’t test building wheel on travis…too much fuzzz.
2018-06-05: polyversion-v0.1.0a5¶
- Disable standalone-wheel hack from
pvlib/setup.py
and rely on setuptools plugin even for polyversion ONCE MORE. (but no need to update standalone, which is a wheel, unaffected by that)
2018-06-05: polyversion-v0.1.0a4¶
Bugfixing polyversion (and generate a non-buggy standalone wheel):
FIX polyversion where it ignored
setup(default_version
keyword. (6519a1ba)fix: polyversion stop eating half of its own dog food: cannot reliably use setuptools plugin for its installation. (56a894cde)
Monkeypatching distutils for bdist-check was failing in PY2 due to being an “old class”. (1f72baec)
doc: fixed recommendation about how to bypass bdist-check to this:
… You may bypass this check and create a package with non-engraved sources (although it might not work correctly) by adding skip_polyversion_check option in your
$CWD/setup.cfg
file, like this:[global] skip_polyversion_check = true ...
2018-06-03: polyversion-v0.1.0a3: setuptools¶
v0.1.0a2`Canceled (like the previous 2), cannot release from r-tags because ``setup()` reports version from v-tag.
- Q: Is a new setup-keyword needed
--is-polyversion-release
? - A: no, just search both.
- Q: Is a new setup-keyword needed
v0.1.0a0 had been canceled for the same reason, but somewhere down the road, the fix was reverted (bdist-check works for r-tag only).
v0.1.0a1 just marked that our
setup.py
files ate our dog food.
Breaking changes¶
Dropped all positional-arguments from
polyversion.polyversion()
; was error-prone. They have all been converted to keyword-arguments.Renamed data in
polyversion
(also applied forpolyvers.pvproject.Project()
):pvtag_frmt --> pvtag_format vtag_frmt --> vtag_format
Changed arguments in
polyversion.polyversion()
(affect alsopolyvers.pvproject.Project()
):default --> default_version tag_frmt --> tag_format --> vprefixes (new) --> is_release (new)
REVERTED again the 0.0.2a9 default logic to raise when it version/time cannot be derived. Now by default it raises, unless default-version or
no_raise
forpolyversion.polytime()
.Stopped engraving
setup.py
files ; clients should use setuptools plugin to derive version for those files (see new features, below)). For reference, this is the removed element from defaultProject
’s configuration (in YAML):globs: [setup.py] grafts: - regex: -| (?xm) \bversion (\ *=\ *) .+?(, \ *[\n\r])+
polyversion library searches both v-tags and r-tags (unless limited). Previously, even checked-out on an r-tag, both
polyversion
command andpolyvers bump
would ignore it, and report +1 from the v-tag!
Features¶
The polyversion library function as a setuptools “plugin”, and adds two new
setup()
keywords for deriving subproject versions from PKG-INFO or git tags (seepolyversion.init_plugin_kw()
):- keyword:
polyversion --> (bool | dict)
When a dict, its keys roughly mimic those in
polyversion()
, and can be used like this:from setuptools import setup setup( project='myname', version='' # omit (or None) to abort if cannot auto-version polyversion={ # dict or bool 'mono_project': True, # false by default ... # See `polyversion.init_plugin_kw()` for more keys. }, setup_requires=[..., 'polyversion'], ... )
- keyword:
keyword:
skip_polyversion_check --> bool
When true, disable bdist-check, when false (default), any bdist_* (e.g.bdist_wheel
), commands will abort if not run from a release tag. You may bypass this check and create a package with non-engraved sources (although it might not work correctly) by invoking the setup-script from command-line like this:$ python setup.py bdist_wheel --skip-polyversion-check
- bump cmd: engrave also non-bumped projects with their
git describe
-derived version (controlled by
--BumpCmd.engrave_bumped_only
flag).
- bump cmd: engrave also non-bumped projects with their
Assign names to engraves & grafts for readable printouts, and for refering to them from the new Project.enabled_engarves list. (namengraves)
polyversion -t
command-line tool prints the full tag (not the version) to make it easy to know if it is a v-tag or r-tag.
Documentation changes¶
- Adopt towncrier for compiling CHANGES. So now each code change can describe its change in the same commit, without conflicts. (towncrier)
- usage: explain how to set your projects PEP 0518
pyproject.toml
file &setup_requires
keyword insetup.py
in your script. - add pbr, incremental and Zest.release in Similar Tools section as setuptools plugins.
- re-wrote and shrinked opening section using glossary terms.
- Chore development:
- deps: don’t pin packaging==17.1, any bigger +17 is fine for parsing version correctly.
2018-05-24: 0.0.2a10: polyvers¶
- fix: slight change of default engraving for
setup.py:version=...
. - Remove default versions from the sources of our-own-dog-food (affects installations for developing this tool).
- refact: merged
`pvlib.whl
andpvlib.run
into a single executable and importable standalone wheel inbin/pvlib.run
, generated frompolyversion-0.0.2a9
, release below. - doc: expand section for installing and contributing into this project.
- chore: tighten various test harnesses.
2018-05-24: 0.0.2a9: polyversion¶
2nd interim release to embed new bin/pvlib.run
.
- INVERT by default
polyversion()/polytime()
functions not to raise if vtags missing. - fix: pvlib.run shebang to use
#!/usr/bin/env python
to work on linux.
2018-05-23: 0.0.2a8: polyversion¶
Interim release to embed new bin/pvlib.run
.
- FIX
polyversion
barebone command (a utility for when not installing the full polyvers tool). - feat: make project-name optional in
polyversion.polyversion()
; if not given, defaults to caller’s last segment of the module. - doc: rudimentary explanation of how to use the lib on its own README.
2018-05-23: 0.0.2a9.post0: polyvers¶
- feat: add
-C
option to change project dir before running command. init
command:- fix: were creating invalid
.polyvers.yaml
configuration-file unless--monorepo/--mono-project
flags were given. - feat: include config-help in generated file only if
the new
--doc
flag given. - feat: inform user of the projects auto-discovered and what type of config-file was generated.
- fix: were creating invalid
- various fixes.
2018-05-19: 0.0.2a8: polyvers¶
- FIX(bump): was engraving all projects and not limiting to those specified in the command-line - command’s syntax slightly changed.
- chore: Stop increasing polyversion version from now on.
- doc: fix all sphinx errors and API reference.
2018-05-18: 0.0.2a7¶
Interim release to embed re-LICENSED pvlib/bin/pvlib.whl
,
from EUPLv1.2–>MIT
2018-05-18: 0.0.2a6: bump
!¶
bump
command:- feat:
--amend
now works - feat:
--engrave-only
. - feat: log
PRETEND
while doing actions. - feat: Log which files where engraved in the final message.
- feat:
- fix(engrave): don’t waste cycles/log-messages on empty-matches (minor).
2018-05-18: 0.0.2a5¶
Actually most changes happened in “interim” release v0.0.2a2, below.
- feat: make a standalone polyversion-lib wheel to facilitate bootstrap when installing & building from sources (and the lib is not yet installed).
- Add
bin/package.sh
that create the pvlib wheel as executabledist/pvlib.run
. - doc: fix rtd & pypi sites.
2018-05-18: 0.0.2a4¶
doc: bad PyPi landing page.
2018-05-17: 0.0.2a3¶
The pvcmd was actually broken so far; was missing polyversion lib dependency!
2018-05-17: 0.0.2a2¶
Interim release to produce executable wheel needed by next release.
2018-05-17: 0.0.2a1: monorepo!¶
- 2nd release, own “mono-project” splitted into 2-project “monorepo”: - polyvers: cmdline tool - polyversion: library code for program-sources to derive version from git-tags
- init, status, bump and config commands work.
- Read/write YAML config file
.polyvers.yaml
at the git-root, and can automatically discover used configuration (from existing git tags or projects files). - Support both
--monorepo
and--mono-project
configurations. - By default
__init__.py
,setup.py
andREADME.rst
files are engraved with bumped version.
2018-05-16: 0.0.2a0¶
broken
2018-01-29: 0.0.1a0: mono-project¶
- First release on PyPI as mono-project
Contributing¶
Contributions and issue-reports are welcome, with no strings attached; project is at a very early stage.
Installation¶
Install both sub-projects in develop mode using pip
$ git clone https://github.com/ankostis/polyvers polyvers.git
$ cd polyvers.git
$ pip install -e pvlib[test] -e .[test]
Caution
Installing egg with python setup.py install develop
is not tested,
and may fail to work correctly.
Tests & integrations¶
test-suite: | |
---|---|
linters: | |
integration-services: | |
|
|
commit-messages: | |
guidelines from angular repo: https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines |
|
changes-aggregation: | |
Similar Tools¶
Bumped across these projects while building it…
- bumpversion
- The original bumpversion project; development stopped after 2015: (recomended also by python guide) https://github.com/peritus/bumpversion
- bump2version
- active clone of the original: https://github.com/c4urself/bump2version
- releash
- another monorepos managing tool, that publishes also to PyPi: https://github.com/maartenbreddels/releash
- Git Bump
- bump version using git-hooks: https://github.com/arrdem/git-bump
- Lerna
- A tool for managing JavaScript projects with multiple packages. https://lernajs.io/
- Pants
- a build system designed for codebases that: - Are large and/or growing rapidly. - Consist of many subprojects that share a significant amount of code. - Have complex dependencies on third-party libraries. - Use a variety of languages, code generators and frameworks. - https://www.pantsbuild.org/
- pbr
- a
setup_requires
library that injects sensible default and behaviors into your setuptools. Crafted for Semantic Versioning, maintained for OpenStack projects. https://docs.openstack.org/pbr/ - Zest.releaser
- easy releasing and tagging for Python packages; make easy, quick and neat releases of your Python packages. You need to change the version number, add a new heading in your changelog, record the release date, svn/git/bzr/hg tag your project, perhaps upload it to pypi… zest.releaser takes care of the boring bits for you. (recomended also by python guide) http://zestreleaser.readthedocs.io/
- incremental
- a small setuptools plugin library that versions Python projects. https://github.com/twisted/incremental
- changes
Manages the release of a Python Library (intuitive logo, recomended also by python guide):
- Auto generates changelog entries from commit messages
- CLI that follows Semantic Versioning principles to auto-increment the library version
- Runs the library tests
- Checks the package installation from a tarball and PyPi
- Uploads the distribution to PyPi
- Tags the GitHub repository
- setuptools_scm
- managing versions in scm metadata instead of declaring them as the version argument or in a scm managed file, apart from handling file finders for the supported scm’s. (recomended also by python guide) https://pypi.org/project/setuptools_scm/
- Dunamai
Library and command line tool for producing dynamic, standards-compliant version strings, derived from tags in your version control system. Suitable for projects written in any programming language (i.e. no need for setup.py), many VCSs(Git; Mercurial, Darcs, Subversion, Bazaar), and many Version styles (PEP 440, Semantic Versioning, Haskell Package Versioning Policy, Custom)
- setupmeta
- Helper for simple setup.py files with setuptools_scm like tag-based versioning plus commands for bumping version, explain meta infos, cleanall, twine, etc https://github.com/zsimic/setupmeta
- python guide
- There is a dedicated guide for this problem in pythons docs: https://packaging.python.org/guides/single-sourcing-package-version/
Find more than 34 similar projects in GitHub: and in awesome: https://github.com/korfuri/awesome-monorepo.
Credits¶
- Contains a function from the BSD-tool :term`pbr` to fetch version from pkg-metadata when invoked as a setuptools plugin from inside an egg.
- This package was created with Cookiecutter and the audreyr/cookiecutter-pypackage project template.
- Using towncrier for generating CHANGES.
Reference¶
Polyversion (library)¶
polyversion |
Python-2.7-safe, no-deps code to discover sub-project versions in Git polyvers monorepos. |
polyversion.polyversion (**kw) |
Report the pvtag of the pname in the git repo hosting the source-file calling this. |
polyversion.polytime (**kw) |
The timestamp of last commit in git repo hosting the source-file calling this. |
polyversion.pkg_metadata_version (pname[, …]) |
Get the version from package metadata if present. |
polyversion.setuplugin |
A setuptools plugin with x2 setup() kwds and monkeypatch all bdist... cmds. |
polyversion.setuplugin.init_plugin_kw (dist, …) |
A setuptools kwd for deriving subproject versions from PKG-INFO or git tags. |
polyversion.setuplugin.check_bdist_kw (dist, …) |
A setuptools kwd for aborting bdist… commands if not on r-tag. |
Module: polyversion
¶
Python-2.7-safe, no-deps code to discover sub-project versions in Git polyvers monorepos.
The polyvers version-configuration tool is generating pvtags like:
proj-foo-v0.1.0
And assuming polyversion()
is invoked from within a Git repo, it may return
either 0.1.0
or 0.1.0+2.gcaffe00
, if 2 commits have passed since
last pvtag.
Also, this library function as a setuptools “plugin” (see setuplugin
).
Finally, the wheel can be executed like that:
python polyversion-*.whl --help
-
polyversion.
polyversion
(**kw)[source]¶ Report the pvtag of the pname in the git repo hosting the source-file calling this.
Parameters: - pname (str) –
The project-name, used as the prefix of pvtags when searching them. If not given, defaults to the last segment of the module-name of the caller.
Attention
when calling it from
setup.py
files, auto-deduction above will not work; you must supply a project name. - default_version (str) –
What version to return if git cmd fails. Set it to None to raise if no vtag found.
Tip
For cases where a shallow git-clone does not finds any vtags back in history, or simply because the project is new, and there are no vtags, we set default-version to empty-string, to facilitate pip-installing these projects from sources.
- default_version_env_var (str) – Override which env-var to read version from, if git cmd fails
[Default:
<pname>_VERSION
] - mono_project (bool) –
- false: (default) monorepo, ie multiple sub-projects per git-repo.
Tags formatted by pvtags
pvtag_format
&pvtag_regex
(likepname-v1.2.3
). - true: mono-project, ie only one project in git-repo
Tags formatted as vtags
vtag_format
&vtag_regex
. (likev1.2.3
).
- false: (default) monorepo, ie multiple sub-projects per git-repo.
Tags formatted by pvtags
- tag_format (str) –
The PEP 3101 pattern for creating pvtags (or vtags).
- It receives 3 parameters to interpolate:
{pname}, {vprefix}, {version} = '*'
. - It is used also to generate the match patterns for
git describe --match <pattern>
command. - It overrides mono_project arg.
- See
pvtag_format
&vtag_format
- It receives 3 parameters to interpolate:
- tag_regex (regex) –
The regex pattern breaking apart pvtags, with 3 named capturing groups:
pname
,version
(without the ‘v’),descid
(optional) anything following the dash(‘-‘) after the version ingit-describe
result.- It is given 2 PEP 3101 parameters
{pname}, {vprefix}
to interpolate. - It overrides mono_project arg.
- See PEP 0426 for project-name characters and format.
- See
pvtag_regex
&vtag_regex
- vprefixes (str) – a 2-element array of str -
tag_vprefixes
assumed when not specified - is_release –
a 3-state boolean used as index into
tag_vprefixes
:- false: v-tags searched;
- true: r-tags searched;
- None: both tags searched.
- basepath (str) – The path of the outermost package inside the git repo hosting the project; if missing, assumed as the dirname of the calling code’s package.
- git_options – a str or an iterator of (converted to str) options to pass
to
git describe
command (empty by default). If a string, it is splitted by spaces. - return_all – when true, return the 3-tuple (tag, version, desc-id) (not just version)
Returns: The version-id (or 3-tuple) derived from the pvtag, or default if command failed/returned nothing, unless None, in which case, it raises.
Raises: CalledProcessError – if it cannot find any vtag and default_version is None (e.g. no git cmd/repo, no valid tags)
Tip
It is to be used, for example, in package
__init__.py
files like this:__version__ = polyversion()
Or from any other file:
__version__ = polyversion('myproj')
Note
This is a python==2.7 & python<3.6 safe function; there is also the similar function with elaborate error-handling
polyvers.pvtags.describe_project()
in the full-blown tool polyvers.- pname (str) –
-
polyversion.
polytime
(**kw)[source]¶ The timestamp of last commit in git repo hosting the source-file calling this.
Parameters: - no_raise (str) – If true, never fail and return current-time. Assumed true if a default version env-var is found.
- basepath (str) – The path of the outermost package inside the git repo hosting the project; if missing, assumed as the dirname of the calling code’s package.
- pname (str) –
The project-name used only as the prefix for default version env-var. If not given, defaults to the last segment of the module-name of the caller. Another alternative is to use directly the default_version_env_var kwd.
Attention
when calling it from
setup.py
files, auto-deduction above will not work; you must supply a project name. - default_version_env_var (str) – Override which env-var to read version from, if git cmd fails
[Default:
<pname>_VERSION
]
Returns: the commit-date if in git repo, or now; RFC 2822 formatted
-
polyversion.
decide_vprefixes
(vprefixes, is_release)[source]¶ Decide v-tag, r-tag or both; no surprises params, return always an array.
-
polyversion.
vtag_format
= '{vprefix}{version}'¶ Like
pvtag_format
but for mono-project version-tags.
-
polyversion.
vtag_regex
= '(?xmi)\n ^(?P<pname>)\n {vprefix}(?P<version>\\d[^-]*)\n (?:-(?P<descid>\\d+-g[a-f\\d]+))?$\n'¶ Like
pvtag_format
but for mono-project version-tags.
Module: polyversion.setuplugin
¶
A setuptools plugin with x2 setup()
kwds and monkeypatch all bdist...
cmds.
Tip
Set envvar[DISTUTILS_DEBUG] to debug it. From https://docs.python.org/3.7/distutils/setupscript.html#debugging-the-setup-script
-
polyversion.setuplugin.
init_plugin_kw
(dist, attr, kw_value)[source]¶ A setuptools kwd for deriving subproject versions from PKG-INFO or git tags.
Parameters: - dist – class:distutils.Distribution
- attr (str) – the name of the keyword
- kw_value –
The content of the new
setup(polyversion=...)
keyword.SYNTAX:
'polyversion': (<bool> | <dict>)
When it is a dict, its keys roughly mimic those in
polyversion()
except those differences:param pname: absent; derived from setup(name=...)
keywordparam default_version: absent; derived from setup(version=...)
keyword:- if None/not given, any problems will be raised,
and
setup.py
script wil abort - if
version
is a (possibly empty) string, this will be used in case version cannot be auto-retrieved.
param is_release: absent; always False when deriving the version, and True when bdist-checking param basepath: if not given, derived from setup(package_dirs={...})
keyword or ‘.’ (and never from caller-stack).See
polyversion()
for keyword-dict’s content. - if None/not given, any problems will be raised,
and
It tries first to see if project contained in a distribution-archive (e.g. a “wheel”), and tries to derive the version from egg-infos. Then it falls through retrieving it from git tags.
Tip
For cases where a shallow git-clone does not finds any vtags back in history, or simply because the project is new, and there are no vtags, we set default-version to empty-string, to facilitate pip-installing these projects from sources.
-
polyversion.setuplugin.
check_bdist_kw
(dist, _attr, kw_value)[source]¶ A setuptools kwd for aborting bdist… commands if not on r-tag.
SYNTAX:
'polyversion_check_bdist_enabled': <any>
When <any> evaluates to false (default), any bdist… (e.g.
bdist_wheel
), setuptools commands will abort if not run from a release tag.By default it this check is bypassed. To enable it, without editing your sources add this in your
$CWD/setup.cfg
file:[global] polyversion_check_bdist_enabled = true ...
- Ignored, if polyversion kw is not enabled.
- Registered in distutils.setup_keywords entry_point of this project’s
setup.py
file.
Polyvers (command-tool)¶
polyvers |
Top-level package for polyvers version-configuration tool. |
polyvers.cli |
The code of polyvers shell-commands. |
polyvers.bumpcmd |
The command that actually bumps versions. |
polyvers.pvproject |
The main structures of polyvers. |
polyvers.pvtags |
Git code to make/inspect sub-project “(p)vtags” and respective commits in (mono)repos. |
polyvers.engrave |
Search and replace version-ids in files. |
polyvers.vermath |
Validate absolute versions or add relative ones on top of a base absolute. |
polyvers.cmdlet.cfgcmd |
Commands to inspect configurations and other cli infos. |
polyvers.cmdlet.cmdlets |
Utils for building elaborate Commands/Sub-commands with traitlets [1] Application. |
polyvers.cmdlet.errlog |
Suppress or ignore exceptions collected in a nested contexts. |
polyvers.cmdlet.interpctxt |
Enable Unicode-trait to pep3101-interpolate {key} patterns from “context” dicts. |
polyvers.utils.mainpump |
Utils pumping results out of yielding functions for main(). |
polyvers.utils.logconfutils |
Utils for configuring and using elaborate logs and handling main() failures. |
polyvers.utils.oscmd |
Utility to call OS commands through subprocess.run() with logging. |
polyvers.utils.fileutil |
Generic utils. |
Module: polyvers.cli
¶
The code of polyvers shell-commands.
-
class
polyvers.cli.
InitCmd
(*args, **kw)[source]¶ Generate configurations based on directory contents.
-
initialize
(argv=None)[source]¶ Invoked after __init__() by make_cmd() to apply configs and build subapps.
Parameters: argv – If undefined, they are replaced with sys.argv[1:]
!It parses cl-args before file-configs, to detect sub-commands and update any
config_paths
, then it reads all file-configs, and then re-apply cmd-line configs as overrides (trick copied from jupyter-core).
-
-
class
polyvers.cli.
LogconfCmd
(*args, **kw)[source]¶ Write a logging-configuration file that can filter logs selectively.
-
class
polyvers.cli.
PolyversCmd
(**kwargs)[source]¶ Bump independently PEP-440 versions of sub-project in Git monorepos.
- SYNTAX:
- {cmd_chain} <sub-cmd> …
-
bootstrapp_projects
() → None[source]¶ Ensure valid configuration exist for monorepo/mono-project(s).
Raises: CmdException – if cwd not inside a git repo
-
class
polyvers.cli.
StatusCmd
(*args, **kw)[source]¶ List the versions of project(s).
- SYNTAX:
- {cmd_chain} [OPTIONS] [<project>]…
-
polyvers.cli.
merge_dict
(dct, merge_dct)[source]¶ Recursive dict merge. Inspired by :meth:
dict.update()
, instead of updating only top-level keys, dict_merge recurses down into dicts nested to an arbitrary depth, updating keys. Themerge_dct
is merged intodct
. :param dct: dict onto which the merge is executed :param merge_dct: dct merged into dct :return: NoneAdapted from: https://gist.github.com/angstwad/bf22d1822c38a92ec0a9
Module: polyvers.bumpcmd
¶
The code of polyvers shell-commands.
-
class
polyvers.cli.
InitCmd
(*args, **kw)[source] Generate configurations based on directory contents.
-
initialize
(argv=None)[source] Invoked after __init__() by make_cmd() to apply configs and build subapps.
Parameters: argv – If undefined, they are replaced with sys.argv[1:]
!It parses cl-args before file-configs, to detect sub-commands and update any
config_paths
, then it reads all file-configs, and then re-apply cmd-line configs as overrides (trick copied from jupyter-core).
-
run
(*args)[source] Leaf sub-commands must inherit this instead of
start()
without invokingsuper()
.Parameters: args – Invoked by start()
withextra_args
.By default, screams about using sub-cmds, or about doing nothing!
-
-
class
polyvers.cli.
LogconfCmd
(*args, **kw)[source] Write a logging-configuration file that can filter logs selectively.
-
run
(*args)[source] Leaf sub-commands must inherit this instead of
start()
without invokingsuper()
.Parameters: args – Invoked by start()
withextra_args
.By default, screams about using sub-cmds, or about doing nothing!
-
-
class
polyvers.cli.
PolyversCmd
(**kwargs)[source] Bump independently PEP-440 versions of sub-project in Git monorepos.
- SYNTAX:
- {cmd_chain} <sub-cmd> …
-
bootstrapp_projects
() → None[source] Ensure valid configuration exist for monorepo/mono-project(s).
Raises: CmdException – if cwd not inside a git repo
-
collect_app_infos
()[source] Provide extra infos to config infos subcommand.
-
parse_command_line
(argv=None)[source] Parse the command line arguments.
-
class
polyvers.cli.
StatusCmd
(*args, **kw)[source] List the versions of project(s).
- SYNTAX:
- {cmd_chain} [OPTIONS] [<project>]…
-
run
(*pnames)[source] Leaf sub-commands must inherit this instead of
start()
without invokingsuper()
.Parameters: args – Invoked by start()
withextra_args
.By default, screams about using sub-cmds, or about doing nothing!
-
polyvers.cli.
merge_dict
(dct, merge_dct)[source] Recursive dict merge. Inspired by :meth:
dict.update()
, instead of updating only top-level keys, dict_merge recurses down into dicts nested to an arbitrary depth, updating keys. Themerge_dct
is merged intodct
. :param dct: dict onto which the merge is executed :param merge_dct: dct merged into dct :return: NoneAdapted from: https://gist.github.com/angstwad/bf22d1822c38a92ec0a9
-
polyvers.cli.
run
(argv=(), cmd_consumer=None, **app_init_kwds)[source] Handle some exceptions politely and return the exit-code.
Parameters: - argv – Cmd-line arguments, nothing assumed if nohing given.
- cmd_consumer – Specify a different main-mup,
mpu.PrintConsumer
by default. Seempu.pump_cmd()
.
Module: polyvers.pvproject
¶
The main structures of polyvers:
Project 1-->* Engrave 1-->* Graft
-
class
polyvers.pvproject.
Graft
(**kwargs)[source]¶ Instructions on how to search’n replace some text.
-
class
polyvers.pvproject.
Project
(**kwargs)[source]¶ Configurations for projects, in general, and specifically for each one.
-
git_describe
(*git_args, include_lightweight=False, is_release=None, **git_flags)[source]¶ Gets sub-project’s version as derived from
git describe
on its pvtag.Parameters: - include_lightweight – Consider also non-annotated tags when derriving description;
equivalent to
git describe --tags
flag. - is_release –
a 3-state boolean used as index into
tag_vprefixes
:- false: v-tags searched;
- true: r-tags searched;
- None: both tags searched.
- git_args – CLI options passed to
git describe
command. Seeoscmd.PopenCmd
on how to specify cli options using python functions, e.g.('*-v*', '*-r*')
- git_flags –
CLI flags passed to
git describe
command.- See
oscmd.PopenCmd
on how to specify cli flags using python functions, e.g.(all=True)
. - See https://git-scm.com/docs/git-describe
- See
Returns: the pvtag of the current project, or raise
Raises: - GitVoidError – if sub-project is not pvtagged.
- NoGitRepoError – if CWD not within a git repo.
- sbp.CalledProcessError – for any other error while executing git.
Tip
There is also the python==2.7 & python<3.6 safe
polyvers.polyversion()`()
for extracting just the version part from a pvtag; use this one from within project sources.Tip
Same results can be retrieved by this git command:
git describe --tags --match <PROJECT>-v*
where
--tags
is needed to consider also non-annotated tags, asgit tag
does.- include_lightweight – Consider also non-annotated tags when derriving description;
equivalent to
-
last_commit_tstamp
()[source]¶ Report the timestamp of the last commit of the git repo.
Returns: last commit’s timestamp in RFC 2822 format
Raises: - GitVoidError – if there arn’t any commits yet or CWD not within a git repo.
- sbp.CalledProcessError – for any other error while executing git.
Note
Same results can be retrieved by this git command:
git describe --tags --match <PROJECT>-v*
where
--tags
is needed to consider also unannotated tags, asgit tag
does.
Return the full pvtag history for the project, if any.
Raises: AssertionError – If used before populate_pvtags_history()
applied on this project.
-
tag_fnmatch
(is_release=False)[source]¶ The glob-pattern finding pvtags with
git describe --match <pattern>
cmd.Parameters: is_release – False for version-tags, True for release-tags By default, it is interpolated with two PEP 3101 parameters:
{pname} <-- this Project.pname {version} <-- '*'
-
tag_regex
(is_release=False) → Pattern[~AnyStr][source]¶ Interpolate and compile as regex.
Parameters: is_release – False for version-tags, True for release-tags
-
Module: polyvers.pvtags
¶
Git code to make/inspect sub-project “(p)vtags” and respective commits in (mono)repos.
There are 3 important methods/functions calling Git:
Project.git_describe()
that fetches the same version-id thatpolyversion.polyversion()
would return, but with more options.Project.last_commit_tstamp()
, same as above.populate_pvtags_history()
that populates pvtags on the given project instances; certain pvtag-related Project methods would fail if this function has not been applies on a project instance.
A (maybe benign) git-related error
Sub-project has not yet been version with a pvtag.
Command needs a git repo in CWD.
Report pname involved to the user in case tags are missing.
Restored checked out branch to previous state in case of errors (or if forced).
Parameters: restore – if true, force restore at exit, otherwise, restore only on errors
Make a
Project
capturing any pvtag.Useful as a “catch-all” last project in
populate_pvtags_history()
, to capture pvtags not captured by any other project.
Make a
Project
capturing any simple vtag (e.g.v0.1.0
).Useful as a “catch-all” last project in
populate_pvtags_history()
, to capture vtags not captured by any other project.
Make a
Project
for a subprojects hosted at git monorepos.- Project versioned with pvtags like
foo-project-v0.1.0
.
- Project versioned with pvtags like
Make a
Project
for a single project hosted at git repos root (not “monorepos”).- Project versioned with tags simple vtags (not pvtags) like
v0.1.0
.
- Project versioned with tags simple vtags (not pvtags) like
Updates
pvtags_history
on given projects (if any) in ascending order.Parameters: - projects – the projects to search pvtags for
- include_lightweight – fetch also non annotated tags; note that by default,
git-describe
does consider lightweight tags unless--tags
given. - is_release – False for version-tags, True for release-tags
Raises: sbp.CalledProcessError – if git executable not in PATH
Note
Internally, pvtags are populated in
_pvtags_collected
which by default it isNone
. After this call, it will be a (possibly empty) list. Any pre-existing pvtags are removed from all projects before collecting them anew.Tip
To collect all pvtags OR vtags only, use pre-defined projects generated by
make_project_matching_*()
functions.
Module: polyvers.engrave
¶
Search and replace version-ids in files.
-
polyvers.engrave.
glob_files
(patterns: List[str], mybase: Union[str, pathlib.Path] = '.', other_bases: Sequence[Union[str, pathlib.Path]] = None) → List[pathlib.Path][source]¶ Glob files in mybase but not in other_bases (unless bases coincide).
- Supports exclude patterns:
!foo
. - If mybase is in other_bases, it doesn’t change the results.
- Supports exclude patterns:
Module: polyvers.vermath
¶
Validate absolute versions or add relative ones on top of a base absolute.
Cmdlets¶
Module: polyvers.cmdlet.cfgcmd
¶
Commands to inspect configurations and other cli infos.
-
class
polyvers.cmdlet.cfgcmd.
ConfigCmd
(**kwds)[source]¶ Commands to manage configurations and other cli infos.
-
class
polyvers.cmdlet.cfgcmd.
DescCmd
(**kwds)[source]¶ List and print help for configurable classes and parameters.
- SYNTAX
- {cmd_chain} [-l] [-c] [-t] [-v] [<search-term> …]
- If no search-terms provided, returns all.
- Search-terms are matched case-insensitively against ‘<class>.<param>’, or against ‘<class>’ if –class.
- Use –verbose (-v) to view config-params from the whole hierarchy, that is, including those from intermediate classes.
- Use –class (-c) to view just the help-text of classes.
- Results are sorted in “application order” (later configurations override previous ones); use –sort for alphabetical order.
-
class
polyvers.cmdlet.cfgcmd.
InfosCmd
(**kwargs)[source]¶ List paths and other intallation infos.
- Some of the environment-variables affecting configurations:
- HOME, USERPROFILE, : where configs & DICE projects are stored
- (1st one defined wins)
<APPNAME>_CONFIG_PATHS : where to read configuration-files.
-
class
polyvers.cmdlet.cfgcmd.
ShowCmd
(**kwds)[source]¶ Print configurations (defaults | files | merged) before any validations.
- SYNTAX
- {cmd_chain} [OPTIONS] [–source=(merged | default)] [<search-term-1> …] {cmd_chain} [OPTIONS] –source file
- Search-terms are matched case-insensitively against ‘<class>.<param>’.
- Use –verbose to view values for config-params as they apply in the whole hierarchy (not
- Results are sorted in “application order” (later configurations override previous ones); use –sort for alphabetical order.
- Warning: Defaults/merged might not be always accurate!
- Tip: you may also add –show-config global option on any command to view configured values accurately on runtime.
Module: polyvers.cmdlet.cmdlets
¶
Utils for building elaborate Commands/Sub-commands with traitlets [1] Application.
## Examples:
To run a base command, use this code:
cd = MainCmd.make_cmd(argv, **app_init_kwds) ## `sys.argv` used if `argv` is `None`!
cmd.start()
To run nested commands and print its output, use baseapp.chain_cmds()
like that:
cmd = chain_cmds([MainCmd, Sub1Cmd, Sub2Cmd], argv) ## `argv` without sub-cmds
sys.exit(baseapp.pump_cmd(cmd.start()) and 0)
Of course you can mix’n match.
## Configuration and Initialization guidelines for Spec and Cmd classes
- The configuration of
HasTraits
instance gets stored in itsconfig
attribute. - A
HasTraits
instance receives its configuration from 3 sources, in this order:
- code specifying class-properties or running on constructors;
- configuration files (json or
.py
files);- command-line arguments.
- Constructors must allow for properties to be overwritten on construction; any class-defaults
must function as defaults for any constructor
**kwds
. - Some utility code depends on trait-defaults (i.e. construction of help-messages), so for certain properties (e.g. description), it is preferable to set them as traits-with-defaults on class-properties.
- Listen Good Bait after 1:43.
[1] | (1, 2) http://traitlets.readthedocs.io/ |
-
class
polyvers.cmdlet.cmdlets.
CfgFilesRegistry
(supported_cfg_extensions=['.json', '.py'])[source]¶ Locate and account extensioned files (by default
.json|.py
).- Collects a Locate and (
.json|.py
) files present in the path_list, or - Invoke this for every “manually” visited config-file, successful or not.
- Files collected earlier should override next ones.
-
collect_fpaths
(path_list)[source]¶ Collects all (
.json|.py
) files present in the path_list, (descending order).Parameters: path_list (List[Text]) – A list of paths (absolute, relative, dir or folders). Returns: fully-normalized paths, with ext
-
config_tuples
¶ The consolidated list of loaded 2-tuples
(folder, fname(s))
.Sorted in descending order (1st overrides later).
- Collects a Locate and (
-
class
polyvers.cmdlet.cmdlets.
Cmd
(**kwargs)[source]¶ Common machinery for all (sub)commands.
-
emit_examples
()[source]¶ Yield lines with the usage and examples.
This usage string goes at the end of the command line help string and should contain examples of the application’s usage.
-
emit_help_epilogue
(classes=None)[source]¶ Yield the very bottom lines of the help message.
If classes=False (the default), print –help-all msg.
-
initialize
(argv=None)[source]¶ Invoked after __init__() by make_cmd() to apply configs and build subapps.
Parameters: argv – If undefined, they are replaced with sys.argv[1:]
!It parses cl-args before file-configs, to detect sub-commands and update any
config_paths
, then it reads all file-configs, and then re-apply cmd-line configs as overrides (trick copied from jupyter-core).
-
classmethod
instance
(*args, **kwargs)[source]¶ Returns a global instance of this class.
This method create a new instance if none have previously been created and returns a previously created instance is one already exists.
The arguments and keyword arguments passed to this method are passed on to the
__init__()
method of the class upon instantiation.Create a singleton class using instance, and retrieve it:
>>> from traitlets.config.configurable import SingletonConfigurable >>> class Foo(SingletonConfigurable): pass >>> foo = Foo.instance() >>> foo == Foo.instance() True
Create a subclass that is retrived using the base class instance:
>>> class Bar(SingletonConfigurable): pass >>> class Bam(Bar): pass >>> bam = Bam.instance() >>> bam == Bar.instance() True
-
classmethod
make_cmd
(argv=None, **kwargs)[source]¶ Instanciate, initialize and return application.
Parameters: argv – Like initialize()
, if undefined, replaced withsys.argv[1:]
.
-
read_config_files
(config_paths=None)[source]¶ Load
config_paths
and maintainconfig_registry
.Parameters: config_paths – optional paths to override those in config_paths trait, in descending order (1st overrides the rest). Returns: the static_config loaded - Configuration files are read and merged from
.json
and/or.py
files inconfig_paths
. - Adapted from
load_config_file()
&_load_config_files()
but without applying configs on the app, just returning them.
- Configuration files are read and merged from
-
-
class
polyvers.cmdlet.cmdlets.
CmdletsInterpolation
(*args, **kw)[source]¶ Adds cmdlets_map into interp-manager for for help & cmd mechanics.
Client-code may add more dicts in interpolation_context.maps list.
-
class
polyvers.cmdlet.cmdlets.
Forceable
[source]¶ Mixin to facilitate “forcing” actions by ignoring/delaying their errors.
-
errlogged
(*exceptions, token: Union[bool, str] = None, doing=None, raise_immediately=None, warn_log: Callable = None, info_log: Callable = None)[source]¶ A context-man for nesting
ErrLog
instances.See
ErrLog
for other params.The pre-existing errlog is searched in
_current_errlog
attribute.The _current_errlog on entering context, is restored on exit; original is None.
The returned errlog always has its
ErrLog.parent
set to this enforceable.Example of using this method for multiple actions in a loop:
with self.errlogged(OSError, doing="loading X-files", token='fread'): for fpath in file_paths: with self.errlogged(doing="reading '%s'" % fpath): fbytes.append(fpath.read_bytes()) # Any errors collected above, will be raised/WARNed here.
-
is_forced
(token: Union[str, bool] = True)[source]¶ Whether some action ided by token is allowed to go thorugh in case of errors.
Parameters: token – an optional string/bool to search for in
force
according to the following table:token: |NONE | |FALSE|TRUE|"str"| force-element: |-----|----|-----| [], FALSE| X | X | X | TRUE| X | O | X | '*'| X | X | O | "str"| X | X | = |
- Rows above, win; columns to the left win.
- To catch all tokens, use
--force=true, --force='*'
Tip
prefer using it via
ErrLog
contextman.
-
-
class
polyvers.cmdlet.cmdlets.
PathList
(*args, **kwargs)[source]¶ Trait that splits unicode strings on os.pathsep to form a the list of paths.
-
class
polyvers.cmdlet.cmdlets.
Printable
[source]¶ A
HasTraits
mixin makingstr()
returnclass(trait=value, ...)
from traits.Which traits to print are decided is decided by
TraitSelector
, forprintable_traits
class-property andprintable=True
tag.
-
class
polyvers.cmdlet.cmdlets.
Replaceable
[source]¶ A mixin to make
HasTraits
instances clone like namedtupple’sreplace()
.Parameters: changes – a dict of values keyed be their trait-name. Works nicely with read-only traits.
-
class
polyvers.cmdlet.cmdlets.
Spec
(**kwargs)[source]¶ Common properties for all configurables.
-
classmethod
class_get_trait_help
(trait, inst=None, helptext=None)[source]¶ Get the helptext string for a single trait.
Parameters: - inst – If given, it’s current trait values will be used in place of the class default.
- helptext – If not given, uses the help attribute of the current trait.
-
ikeys
(*maps, **kwds) → ContextManager[polyvers.cmdlet.cmdlets.CmdletsInterpolation][source]¶ Temporarily place self before the given maps and kwds in interpolation-cntxt.
- Self has the least priority, kwds the most.
- For params, see
interp.InterpolationContext.interp()
.
Attention
Must use
str.format_map()
when _stub_keys is true; otherwise,format()
will clone all existing keys in a static map.
-
classmethod
-
polyvers.cmdlet.cmdlets.
build_sub_cmds
(*subapp_classes)[source]¶ Builds an ordered-dictionary of
cmd-name --> (cmd-class, help-msg)
.
-
polyvers.cmdlet.cmdlets.
chain_cmds
(app_classes, argv=None, **root_kwds)[source]¶ Instantiate a list of
[cmd, subcmd, ...]
, linking children to parents.Parameters: - app_classes – A list of cmd-classes:
[root, sub1, sub2, app]
Note: you have to “know” the correct nesting-order of the commands ;-) - argv – cmdline args are passed to all cmds; make sure they do not contain
any sub-cmds, or chain will be broken.
Like
initialize()
, if undefined, replaced withsys.argv[1:]
.
Returns: The root(1st) cmd to invoke
Aplication.start()
Apply the
pump_cmd()
or collect_cmd() on the return instance.- Normally argv contain any sub-commands, and it is enough to invoke
initialize(argv)
on the root cmd. This function shortcuts arg-parsing for subcmds with explict cmd-chaining in code. - This functions is the 1st half of
Cmd.launch_instance()
.
- app_classes – A list of cmd-classes:
-
polyvers.cmdlet.cmdlets.
class_config_yaml
(cls, outer_cfg, classes=None, config: polyvers._vendor.traitlets.config.loader.Config = None)[source]¶ Get the config section for this Configurable.
Parameters: - classes (list) – (optional) The list of other classes in the config file, used to reduce redundant help descriptions. If given, only params from these classes reported.
- config – If given, only what is contained there is included in generated yaml, with help-descriptions from classes, only where class default-values differ from the values contained in this dictionary.
-
polyvers.cmdlet.cmdlets.
class_help_description_lines
(app_class)[source]¶ “Note: Reverse doc/description order bc classes do not have dynamic default
_desc()
, below.
-
polyvers.cmdlet.cmdlets.
cmd_line_chain
(cmd)[source]¶ Utility returning the cmd-line(str) that launched a
Cmd
.
-
polyvers.cmdlet.cmdlets.
cmdlets_interpolations
= CmdletsInterpolation({}, {'now': <polyvers.cmdlet.interpctxt.Now object>, 'utcnow': <polyvers.cmdlet.interpctxt.Now object>, 'ikeys': <polyvers.cmdlet.interpctxt._KeysDumper object>}, {'$HOSTNAME': 'build-9095687-project-192541-polyvers', '$APPDIR': '/app', '$HOME': '/home/docs', '$OLDPWD': '/', '$CONDA_VERSION': '4.5.11', '$READTHEDOCS': 'True', '$READTHEDOCS_PROJECT': 'polyvers', '$PATH': '/home/docs/checkouts/readthedocs.org/user_builds/polyvers/envs/master/bin:/home/docs/.pyenv/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/docs/.conda/bin:/home/docs/.pyenv/bin', '$LANG': 'C.UTF-8', '$DEBIAN_FRONTEND': 'noninteractive', '$BIN_PATH': '/home/docs/checkouts/readthedocs.org/user_builds/polyvers/envs/master/bin', '$PYTHON_VERSION_35': '3.5.5', '$READTHEDOCS_VERSION': 'master', '$PYTHON_VERSION_27': '2.7.14', '$PYTHON_VERSION_36': '3.6.4', '$PWD': '/home/docs/checkouts/readthedocs.org/user_builds/polyvers/checkouts/master/docs', '$PYTHON_VERSION_37': '3.7.1', '$PYENV_ROOT': '/home/docs/.pyenv', '$DOCUTILSCONFIG': '/home/docs/checkouts/readthedocs.org/user_builds/polyvers/checkouts/master/docs/docutils.conf'}, {'appname': '<APP>', 'cmd_chain': '<CMD>'})¶ That’s the singleton interp-manager used by all cmdlet configurables.
-
polyvers.cmdlet.cmdlets.
generate_config_file_yaml
(self, classes=None, config: polyvers._vendor.traitlets.config.loader.Config = None)[source]¶ generate default config file from Configurables
Parameters: config – If given, only what is contained there is included in generated yaml, with help-descriptions from classes, only where class default-values differ from the values contained in this dictionary.
Module: polyvers.cmdlet.errlog
¶
Suppress or ignore exceptions collected in a nested contexts.
To get a “nested” ErrLog
instance either use nesterrlog()
, or
call ErrLog.__call__()
on the enclosing one.
- FIXME: possible to have different node-hierarchies in contextvars-nesting!!
- (unless
ErrLog()
constructor is never called)
-
class
polyvers.cmdlet.errlog.
ErrLog
(parent: polyvers.cmdlet.cmdlets.Forceable, *exceptions, token: Union[bool, str, None] = None, doing=None, raise_immediately=None, warn_log: Callable = None, info_log: Callable = None)[source]¶ Collects errors in “stacked” contexts and delays or ignores (“forces”) them.
Note
To nest errlogs, prefer
nesterrlog()
instead of this constructor, or else you must keep a reference on the last enclosing errlog and explicitly callErrLog.__call__()
on it.- Unknown errors (not in exceptions) always bubble up immediately.
- Any “forced” errors are collected and logged in warn_log on context-exit,
forcing is enabled when the token given exists in spec’s
force
attribute. - Non-“forced” errors are either raise_immediately, or raised collectively
in a
CollectedErrors
, on context-exit. - Collected are always logged on DEBUG immediately.
- Instances of this class are callable, and the call will return a clone with provided properties updated.
- A clone is also returned when acquiring the context in a with statement.
Variables: - spec – the spec instance to search in its
Spec.force
for the token - exceptions – the exceptions to delay or forced; others are left to bubble immediately
If none given,
Exception
is assumed. - token –
the
force
token to respect, likeSpec.is_forced()
. Resets on each new instance from__call__()
. Possible values:- false: (default) completely ignore force trait
- collected are just delayed);
- <a string>: “force” if this token is in force trait;
- True: “force” if True is in
force``force
.
- doing –
A description of the running activity for the current stacked-context, in present continuous tense, e.g. “readind X-files”.
Resets on each new instance from
__call__()
. Assuming doing = “having fun”, it may generate one of those 3 error messages:Failed having fun due to: EX ...and collected 21 errors (3 ignored): - Err 1: ... Collected 21 errors (3 ignored) while having fun: - Err 1: ... LOG: Ignored 9 errors while having fun: - Err 1: ...
- raise_immediately – if not forced, do not wait for report() call to raise them;
suggested use when a function decorator. Also when –debug.
Resets on each new instance from
__call__()
. - warn_log – the logging method to report forced errors; if none given, use searched the log ov the parent or falls back to this’s modules log. Note that all failures are always reported immediately on DEBUG.
- info_log – the logging method to report completed “doing” tasks; if none given does not report them.
PRIVATE FIELDS:
Variables: - _root_node – The root of the tree of nodes, populated when entering contexts recursively.
- _anchor – the parent node in the tree where on
__enter__()
a new _active child-node is attached, and the tree grows. - _active – the node created in _anchor
Example of using this method for multiple actions in a loop:
with ErrLog(enforeceable, OSError, doing="loading X-files", token='fread') as erl1: for fpath in file_paths: with erl1(doing="reading '%s'" % fpath) as erl2: fbytes.append(fpath.read_bytes()) # Any errors collected will raise/WARN here (root-context exit).
-
coords
¶ Return my anchor’s coordinate-indices from the root of the tree.
-
is_armed
¶ Is context
__enter__()
currently under process?
-
is_forced
¶ Try force in parent first.
-
is_good
¶ An errlog is “good” if its anchor has not captured any exception yet.
If it does, it cannot be used anymore.
-
pdebug
¶ Search debug property in parent, or False.
-
plog
¶ Search log property in parent, or use this module’s logger.
-
report_root
(ex_raised: Optional[Exception]) → Optional[polyvers.cmdlet.errlog.CollectedErrors][source]¶ Raise or log the errors collected from
Parameters: ex_raised – the cntxt-body exception __exit__()
is about to raiseReturns: a CollectedErrors
in case catured errors contain non-forced errors BUT ex_raised given.Raises: CollectedErrors – any non-forced exceptions captured in tree (if any), unless ex_raised given
-
polyvers.cmdlet.errlog.
errlogged
(*errlog_args, **errlog_kw)[source]¶ Decorate functions/methods with a
ErrLog
instance.The errlog-contextman is attached on the wrapped function/method as the errlog attribute.
-
polyvers.cmdlet.errlog.
nesterrlog
(parent, *exceptions, token: Union[bool, str] = None, doing=None, raise_immediately=None, warn_log: Callable = None, info_log: Callable = None)[source]¶ To nest errlogs, prefer this function instead of this
ErrLog()
constructor, or else you must keep a reference on the last enclosing errlog and explicitly callErrLog.__call__()
on it.
Module: polyvers.cmdlet.interpctxt
¶
Enable Unicode-trait to pep3101-interpolate {key} patterns from “context” dicts.
-
class
polyvers.cmdlet.interpctxt.
InterpolationContext
[source]¶ A stack of 4 dics to be used as interpolation context.
- The 3 stacked dicts are:
- user-map: writes affect permanently this dict only;
- time: (‘now’, ‘utcnow’), always updated on access;
- env-vars, $-prefixed.
Append more dicts in
self.maps
list if you wish.-
ikeys
(*maps, _stub_keys: Union[str, bool, None] = False, _escaped_for: Union[Callable, str] = None, **kv_pairs) → ContextManager[InterpolationContext][source]¶ Temporarily place maps and kwds immediately after user-map (2nd position).
- For params, see
interp()
.
Attention
Must use
str.format_map()
when _stub_keys is true; otherwise,format()
will clone all existing keys in a static map.- For params, see
-
interp
(text: Optional[str], *maps, _stub_keys=False, _escaped_for: Union[Callable, str] = None, _suppress_errors: bool = None, **kv_pairs) → Optional[str][source]¶ Interpolate text with values from maps and kwds given.
Parameters: - text – the text to interpolate; if null/empty, returned as is
- maps – a list of dictionaries/objects/HasTraits from which to draw items/attributes/trait-values, all in increasing priority. Nulls ignored.
- _stub_keys –
- If false, missing keys raise KeyError.
- If True, any missing key gets replaced by
{key}
(practically remain unchanged). - If callable, the key is passed to it as a the only arg, and the result gets replaced.
- Any other non-false value is returned for every key.
- _suppress_errors – ignore any interpolation errors and return original string
- _escaped_for – a callable or (‘glob’|’regex’) to escape object’s attribute values
Later maps take precedence over earlier ones; kv_pairs have the highest, but _stub_keys the lowest (if true).
-
polyvers.cmdlet.interpctxt.
dictize_object
(obj, _escaped_for: Union[Callable, str] = None)[source]¶ Make an object appear as a dict for
InterpolationContext.ikeys()
.Parameters: _escaped_for – one of ‘glob’, ‘regex’ or a callable to escape object’s attribute values
Utilities¶
Module: polyvers.utils.mainpump
¶
Utils pumping results out of yielding functions for main().
-
class
polyvers.utils.mainpump.
ConsumerBase
[source]¶ Checks if all boolean items (if any) are True, to decide final bool state.
-
class
polyvers.utils.mainpump.
ListConsumer
[source]¶ Collect all items in a list, while checking if all boolean ok.
-
class
polyvers.utils.mainpump.
PrintConsumer
[source]¶ Prints any text-items while checking if all boolean ok.
-
polyvers.utils.mainpump.
collect_cmd
(cmd_res, dont_coalesce=False, assert_ok=False)[source]¶ Pumps cmd-result in a new list.
Parameters: - cmd_res – A list of items returned by a
Cmd.start()
/Cmd.run(). If it is a sole item, it is returned alone without a list. - assert_ok – if true, checks
ListConsumer
’s exit-code is not false.
- cmd_res – A list of items returned by a
-
polyvers.utils.mainpump.
pump_cmd
(cmd_res, consumer=None)[source]¶ Sends (possibly lazy) cmd-results to a consumer (by default to STDOUT).
Parameters: - cmd_res – Whatever is returnened by a
Cmd.start()
/Cmd.run(). - consumer – A callable consuming items and deciding if everything was ok;
defaults to
PrintConsumer
Returns: bool(consumer)
- Remember to have logging setup properly before invoking this.
- This the 2nd half of the replacement for
Application.launch_instance()
.
- cmd_res – Whatever is returnened by a
Module: polyvers.utils.logconfutils
¶
Utils for configuring and using elaborate logs and handling main() failures.
-
polyvers.utils.logconfutils.
exit_with_pride
(reason=None, warn_color='\x1b[31;1m', err_color='\x1b[1m', logger=None)[source]¶ Return an exit-code and logs error/fatal message for
main()
methods.Parameters: - reason –
- If reason is None, exit-code(0) signifying OK;
- if exception, print colorful (if tty) stack-trace, and exit-code(-1);
- otherwise, prints str(reason) colorfully (if tty) and exit-code(1),
- warn_color – ansi color sequence for stack-trace (default: red)
- err_color – ansi color sequence for stack-trace (default: white-on-red)
- logger – Which logger to use to log reason (must support info and fatal). if missing, derived from this module.
Returns: (0, 1 -1), for reason == (None, str, Exception) respectively.
Note that returned string from
main()
are printed to stderr and exit-code set to bool(str) = 1, so print stderr separately and then set the exit-code.For colors use
RainbowLoggingHandler.getColor()
, defaults: - ‘[33;1m’: yellow+bold - ‘[31;1m’: red+boldNote: it’s better to have initialized logging.
- reason –
-
polyvers.utils.logconfutils.
init_logging
(level=20, logconf=None, color=None, logger=None, **kwds)[source]¶ Parameters: - level – Root-logger’s level; Overrides logconf if given, INFO otherwise.
- logconf (None, str, seq[str]) –
File(s) to configure loggers; set [] to prohibit loading any logconf file. Allowed file-extensions:
- ’.conf’ (implied if missing) .
- ’.yml’/’yaml’
The ~ in the path expanded to $HOME. See https://docs.python.org/3/library/logging.config.html
- color – Whether to color log-messages; if undefined, true only in consoles.
- logger – Which logger to use to log logconf source(must support info and debug). if missing, derived from this module.
- kwds – Passed directly to
logging.basicConfig()
(e.g. filename); used only id default HOMElogconf.yaml
file is NOT read.
Module: polyvers.utils.oscmd
¶
Utility to call OS commands through subprocess.run()
with logging.
The polyvers version-configuration tool is generating tags like:
proj-foo-v0.1.0
On purpose python code here kept with as few dependencies as possible.
-
class
polyvers.utils.oscmd.
PopenCmd
(dry_run=False, check_stdout=True, check_stderr=True, check_returncode=True, **popen_kw)[source]¶ A function –> cmd-line builder for executing (mostly) git commands.
To run
git log -n1
:out = cmd.git.log(n=1)
To launch a short python program with
python -c "print('a')"
:out = cmd.python._(c=True)('print('a')')
Raises: sbp.CalledProcessError – if check_returncode=true and exit code is non-zero. Important
Flags written out are mostly for Git, bc flags are produced like that:
-f <value> --flag=<value>
-
polyvers.utils.oscmd.
exec_cmd
(cmd, dry_run=False, check_stdout=True, check_stderr=True, check_returncode=True, encoding='utf-8', encoding_errors='surrogateescape', **popen_kws)[source]¶ Parameters: check_stdout – None: Popen(stdout=None), printed False: Popen(stdout=sbp.DEVNULL), ignored True: Popen(stdout=sbp.PIPE), collected & returned
Module: polyvers.utils.fileutil
¶
Generic utils.
-
polyvers.utils.fileutil.
convpath
(fpath, abs_path=True, exp_user=True, exp_vars=True)[source]¶ Without any flags, just pass through
osp.normpath()
.
-
polyvers.utils.fileutil.
ensure_dir_exists
(path, mode=493)[source]¶ ensure that a directory exists
If it doesn’t exist, try to create it and protect against a race condition if another process is doing the same.
The default permissions are 755, which differ from os.makedirs default of 777.
-
polyvers.utils.fileutil.
ensure_file_ext
(fname, ext, *exts, is_regex=False)[source]¶ Ensure that the filepath ends with the extension(s) specified.
Parameters: - ext (str) – The 1st extension to search & to append if none matches, so must not be a regex.
- exts (str) – Other extensions. These may be regexes, depending on is_regex; a ‘$’ is always added at its end.
- is_regex (bool) – When true, the rest exts are parsed as case-insensitive regexes.
Example:
>>> ensure_file_ext('foo', '.bar') 'foo.bar' >>> ensure_file_ext('foo.', '.bar') 'foo.bar' >>> ensure_file_ext('foo.', 'bar') 'foo.bar' >>> ensure_file_ext('foo.BAR', '.bar') 'foo.BAR' >>> ensure_file_ext('foo.DDD', '.bar') 'foo.DDD.bar'
Note that omitting dot(‘.’) from extension does affect the results:
>>> ensure_file_ext('foo', 'bar') 'foo.bar' >>> ensure_file_ext('foo.BAR', 'bar') 'foo.BAR' >>> ensure_file_ext('fooBAR', 'bar') # File allowed without extension! 'fooBAR'
When more extensions are given, the 1st is appended if none matches:
>>> ensure_file_ext('foo.xlt', '.xlsx', '.XLT') 'foo.xlt' >>> ensure_file_ext('foo.xlt', '.xlsx', '.xltx') 'foo.xlt.xlsx'
And when regexes:
>>> ensure_file_ext('foo.xlt', '.xlsx', r'\.xl\w{1,2}', is_regex=True) 'foo.xlt' >>> ensure_file_ext('foo.xl^', '.xls', r'\.xl\w{1,2}', is_regex=True) 'foo.xl^.xls'