Significant changes include:
* "--headless" option. The "betse" command now accepts a "--headless"
option, permitting both end users and our test suite alike to
explicitly enable headless operation. While the codebase did already
detect most headless environments and thus automatically enable
headless operation as needed, permitting interested parties to do so
sanitizes numerous use cases.
* Backward compatibility guarantee extended to BETSE >= 0.5.0. BETSE now
guarantees backward compatibility with simulation configurations
supported by relatively recent versions of BETSE (i.e., BETSE 0.5.0
and newer).
* PyYAML -> ruamel.yaml >= 0.15.24 required. BETSE now requires the
comment- and whitespace-preserving "ruamel.yaml" YAML roundtripper
rather than the antiquated PyYAML parser. Inadequacies in the Python
installation ecosystem (e.g., setuptools, wheels) prevent BETSE from
permissively accepting one of several different YAML implementations
at installation time. Since "ruamel.yaml" is the superset of all YAML
implementations, BETSE 1.1.0 officially drops PyYAML in favour of
"ruamel.yaml".
* YAML 1.1 -> 1.2. All bundled YAML files have been trivially bumped
from compliance with YAML 1.1 to 1.2 (i.e., the most recent YAML
standard), resolving numerous valid complaints from "ruamel.yaml" at
load time. To preserve backward compatibility with prior
YAML-formatted simulation configuration files, files compliant with
YAML 1.1 are implicitly interpreted as compliant with YAML 1.2
instead.
* Mandatory dependencies bumped:
* numpy >= 1.13.0. A recent version of numpy is now required, due to
our widespread usage of the optional "axis" keyword argument passed
to the numpy.unique() function first introduced by numpy 1.13.0.
Doing so resolves a recent spate of CI failures in Windows-ish
AppVeyor pipelines.
* py.test >= 3.7.0. A recent version of py.test is now required, which
introduced a new "package" scope for fixtures. This scope is
required to efficiently initialize and deinitialize application
metadata singletons for unit tests.
* Deprecation warnings resolved:
* A deprecation from the "betse" script wrapper.
* A deprecation from the numpy.unique() function on failing to
explicitly pass the "axis" keyword argument.
* Numpy optimization detection. To improve detection of optimized Numpy
installations (i.e., of a "numpy" package linked against a
multithreaded BLAS implementation), application startup preferentially
detects whether the active Python interpreter is managed by any of the
following, in which case Numpy is guaranteed to be optimized:
* The "conda" package manager.
* The modern Gentoo Linux scientific stack (i.e., "eselect-ldso").
* Problematic matplotlib backends blacklisted (i.e., prohibited):
* Unconditionally blacklisted all Qt 4-specific matplotlib backends
(e.g., "Qt4Agg", "Qt4cairo"). The Qt Company ceased maintenance of
Qt 4 in 2015, implying Qt 4 to be insecure, fragile, and largely
unavailable.
* Conditionally blacklisted all Qt 5-specific matplotlib backends
(e.g., "Qt5Agg", "Qt5cairo") under headless environments. When
headless, these backends reliably cause Qt 5 to silently fail with a
segmentation fault.
* Logging improvements:
* Logfile rotation race conditions resolved. A long-standing issue
inviting race conditions between multiple processes contending for
write access to rotational logfiles has now been resolved, thanks to
intrepid end user Edward Ned Harvey at the Levin Lab. Thanks a
metric ton, Ed!
* Logfile closure. Open logfile handles are now guaranteeably closed
in a platform-portable manner at application shutdown, resolving
Windows-specific "ResourceWarning: unclosed file" warnings. Namely:
* The application metadata singleton is now unconditionally
deinitialized at application shutdown (even in the event of fatal
exceptions).
* Our default logging configuration now removes all existing
handlers from the root logger and close all open file handles
maintained by those handlers at both initialization and
deinitialization time.
* Testing improvements:
* "${DISPLAY}" squelched. The X.org-specific "${DISPLAY}" environment
variable is now squelched during testing, resolving functional test
failures.
* Windows-specific warnings logged as informational non-warnings, a
crude workaround to prevent PowerShell from erroneously raising
non-human-readable exceptions on the first write to standard error.
(Why!?)
* "--export-sim-conf-dir" removed. The awkward "--export-sim-conf-dir"
option previously accepted by our test suite has been obsoleted.
* Our AppVeyor configuration now:
* Locally resolves conda/conda#8836, a recent Windows-specific
Anaconda change significantly breaking backward compatibility with
most existing AppVeyor configurations -- including ours. To do so,
the base Anaconda environment is now explicitly activated in an
early-time manner.
* Activates its "conda" environment via the "activate" command
rather than the "conda activate" subcommand, which appears to be
non-functional.
* Coerces high-level PowerShell stream objects produced by running
the "py.test" command into low-level strings. If omitted,
PowerShell insanely interprets the first write to stderr as a
non-human-readable exception.
* Our GitLab-CI Runner configuration now:
* Caches both "conda" environments and packages to project-local
directories, which should reduce redundant pipeline overhead.
* Imports environment variables required for sane "conda" usage and,
in particular, the recently introduced "conda activate"
subcommand.
* Has been streamlined for porting to downstream consumers (e.g.,
BETSEE):
* Removed all disabled configuration pertaining to "pip".
Leveraging "pip" from a Miniconda-based configuration is
fundamentally wrong.
* Printed application metadata before running the test suite via
the new "--headless" option.
* Documentation improvements:
* Docstring revisions for numerous subclasses in the
"betse.science.config.export.visual" submodule for accuracy.
* API generalizations:
* Renamed the prior:
* "betse.metaapp" submodule to "betse.appmeta".
* "betse.util.app.meta.metaappabc" submodule to
"betse.util.app.meta.appmetaabc".
* "betse.util.app.meta.appmetadep" submodule to
"betse.util.app.meta.appmetamod".
* "betse.util.app.meta.metaappton" submodule to
"betse.util.app.meta.appmetaone".
* "betse.util.io.log.logconfig" submodule to
"betse.util.io.log.conf.logconf".
* "betse.util.io.log.logformat" submodule to
"betse.util.io.log.conf.logconfformat".
* "betse.util.io.log.loghandle" submodule to
"betse.util.io.log.conf.logconfhandle".
* "betse_test.fixture.metaapper" submodule to
"betse_test.fixture.initter".
* Generalized the existing "betse.exceptions" submodule:
* Defines new exception classes, including:
* "BetseMappingKeyException".
* "BetseLogRaceException", unique to the aforementioned log
rotation race condition.
* Generalized the existing "betse.metadata" submodule:
* Replaced all references to the obsolete static "SCRIPT_BASENAME"
global with the dynamic
betse.util.app.meta.appmetaone.get_app_meta().package_name()
property, which properly applies to downstream consumers (e.g.,
BETSEE).
* Renamed the "GIT_TAG_OLDEST_BACKWARD_COMPATIBILITY" global to
"GIT_TAG_COMPAT_OLDEST" for brevity.
* Reverted this global to "v0.5.0", restoring guaranteeable backward
compatibility with all simulation configurations from BETSE >=
0.5.0.
* Generalized the existing "betse.lib.matplotlib.matplotlibs"
submodule:
* Defined a new MplConfig.logger() property, yielding Matplotlib's
root logger object (i.e., "matplotlib").
* Optimized the existing "betse.lib.numpy.numpys" submodule:
* Defined a new _is_blas_optimized_conda() tester, deferring to the
newly defined betse.util.py.pys.is_conda() tester.
* Optimized the existing is_blas_optimized() tester to
preferentially call the _is_blas_optimized_conda() tester first,
which is guaranteed to be both the most optimal and portable
solution.
* Streamlined the existing "betse.lib.setuptools.command.supcmdtest"
submodule:
* Removed the obsolete "test.export_sim_conf_dir" instance variable.
* Generalized the existing "betse.lib.yaml.yamls" submodule:
* Refactored the load() function to accept a new optional
"yaml_version" parameter, overriding any version directive
prefacing the passed YAML-formatted file. When passed such a
parameter with a value other than "1.1" (e.g., "1.2"), this
function temporarily ignores all non-fatal warnings emitted by
"ruamel.yaml" specific to the YAML 1.1 specification.
* Defined a new "betse.lib.yaml.abc.yamlfileabc" submodule:
* Shifted the prior "betse.lib.yaml.abc.yamlabc.YamlFileABC"
superclass into this submodule:
* Defined a new copy() method, encapsulating the lower-level
load() and save() methods for safe copying YAML-formatted files.
* Generalized the load() method to accept variadic keyword
arguments, passed as is to the betse.lib.yaml.yamls.load()
function.
* Generalized the save() method to accept two new optional
parameters:
* "is_conf_file_overwritable", the overwrite policy for the
passed target file.
* "conf_subdir_overwrite_policy", the overwrite policy for
subdirectories of this file.
* Defined a new "YamlFileDefaultABC" superclass defining:
* An abstract conf_default_filename() class property, returning
the absolute filename of the subclass-specific default YAML file
(e.g., default simulation configuration).
* A concrete copy_default() method, copying this file to the
passed target filename.
* Removed the "betse.science.config.confio" submodule.
* Generalized the existing "betse.science.parameters" submodule:
* Refactored the "Parameters" class:
* To subclass the newly defined "YamlFileDefaultABC" subclass.
* Refactored the load() method to override any version directive
prefacing all YAML-formatted files loaded by this method with
"1.2", preserving backward compatibility with prior simulation
configuration files erroneously prefaced by the "%YAML 1.1"
directive.
* Replaced all prior calls to the
betse.science.config.confio.write_default() function with calls to
the save_default() method.
* Generalized the existing "betse.science.math.mesh" submodule:
* Improved the DECMesh.refine_mesh() method to log rather than
merely print informational messages.
* Generalized the existing "betse.util.app.meta.metaappabc" submodule:
* Refactored the "AppMetaABC" superclass:
* Defined a new deinit() method, deinitializing both this
application metadata singleton and the current application.
* Defined the following new properties:
* module_metadata(), the subclass-specific application metadata
submodule.
* module_metadeps(), the subclass-specific application
dependency metadata submodule.
* test_package(), the root test package for the current
application if found or raises an exception otherwise.
* test_dirname(), the absolute dirname of that package's
directory if found or raises an exception otherwise.
* test_data_dirname(), the absolute dirname of that package's
data subdirectory if found or raises an exception otherwise.
* Replaced all prior references to the prior abstract
betse.util.cli.cliabc._module_metadata() property to the new
module_metadata() property and removed the former.
* Generalized the existing "betse.util.app.meta.appmetamod" submodule:
* Defined a new merge_module_metadeps() function:
* Dynamically creating and returning a new application dependency
metadata module with arbitrary name iteratively merged from the
contents of all passed application dependency metadata modules.
* Copying the "RequirementCommand" class globally defined by the
first such module into the output module.
* Generalized the existing "betse.util.app.meta.appmetaone" submodule:
* Defined a new deinit() function, conditionally encapsulating the
lower-level betse.util.app.meta.appmetaabc.AppMetaABC.deinit()
method.
* Generalized the "betse.util.cli.cliabc" submodule:
* Refactored the _make_options_top() method into an "_options_top"
property, preserving orthogonality with the existing
"betse.util.cli.clicmdabc._subcommander_top" property.
* Refactored the "_is_option_matplotlib_backend" boolean property
into an "_matplotlib_backend_name_forced" string property,
enabling subclasses to force initialization of a desired
subclass-specific matplotlib backend.
* Defined a new "betse.util.io.log.conf" subpackage.
* Defined a new "betse.util.io.log.conf.logconfcls" submodule:
* Shifted the prior "betse.util.io.log.logconfig.LogConfig" class to
"LogConf" in this submodule.
* Generalized the existing "betse.util.io.log.conf.logconf" submodule:
* Defined a new deinit() function calling a new
betse.util.io.log.conf.logconfcls.LogConf.deinit() method,
deinitializing the initialized logging configuration and hence
closing all open file handles associated with this configuration.
* Improved the existing init() function to reduce to a noop rather
than reinitialize a previously initialized logging configuration.
* Generalized the existing "betse.util.io.log.conf.logconfhandle"
submodule:
* Refactored the .LogHandlerFileRotateSafe" subclass:
* Revised class and method docstrings for clarity, including a
recommendation that external callers manually pass the
"--log-file" CLI option when running concurrent BETSE processes.
* Refactored the _emit_safely() method to:
* Accept a "self" parameter, a critical bug fix.
* Sleep for 100ms rather than 50ms.
* Print additional debug output to standard error documenting
this method's automatic resolution process.
* Raise a human-readable exception on exhausting all other
options, including the same recommendation as documented in
docstrings.
* Generalized the existing "betse.util.io.log.logs" submodule:
* Renamed the get() function to get_logger() for disambiguity.
* Generalized the existing "betse.util.io.stdouts" submodule:
* Repaired the output_lines() function, which suffered a critical
(albeit trivial) defect.
* Defined a new "betse.util.os.brand.linux" submodule:
* Defined a new is_mir() tester, returning true only if the active
Python interpreter is running under the Mir compositor.
* Defined a new is_wayland() tester, returning true only if the
active Python interpreter is running under the Wayland compositor.
* Shifted the existing betse.util.os.is_linux() tester into this
submodule.
* Defined a new "betse.util.os.brand.macos" submodule:
* Shifted the existing betse.util.os.is_macos() tester into this
submodule.
* Defined a new "betse.util.os.brand.posix" submodule:
* Defined a new is_x11() tester, returning true only if the active
Python interpreter is running under an X11 display server.
* Shifted the existing betse.util.os.is_posix() tester into this
submodule.
* Generalized the existing "betse.util.os.brand.windows" submodule:
* Defined a new is_version_10_or_newer() tester, returning true if
the current platform is Windows >= 10.
* Defined a new get_api_version() getter, returning the current
low-level Windows API (WinAPI, Win32) version.
* Shifted the is_windows(), is_windows_cygwin(),
is_windows_vanilla(), and is_windows_wsl() testers from the
existing "betse.util.os.brand.os" submodule into this submodule.
* Generalized the existing "betse.util.os.displays" submodule:
* Defined a new is_dpi_scaling() tester, returning true if the
current platform natively supports high-DPI scaling.
* Refactored the is_headless() tester to defer to the most recent
call to the newly defined set_headless() function (if any).
* Defined a new set_headless() setter, enabling callers to
explicitly force headless operation and hence override the
implicit detection of headless environments performed by the
is_headless() tester.
* Generalized the existing "betse.util.os.kernels" submodule:
* Defined a new is_version_greater_than_or_equal_to() tester,
enabling callers to trivially detect minimum kernel versions and
hence the existence of platform features introduced by those
versions.
* Revises the get_version() getter to return the high-level Windows
kernel version (e.g., "10.0.10240") rather than the low-level
Windows API version (e.g., "6.2.9200").
* Generalized the existing "betse.util.os.shell.shellenv" submodule:
* Defined a new to_str() function, returning a human-readable string
of all environment variables defined by the host shell
environment.
* Generalized the existing "betse.util.py.module.pymodname" submodule:
* Defined a new make_module() function, enabling callers to
dynamically generate new in-memory modules containing arbitrary
module attributes.
* Generalized the existing "betse.util.py.module.pymodule" submodule:
* Defining a new is_module() tester, returning true only if the
passed object is a module.
* Generalized the existing "betse.util.type.obj.objects" submodule:
* Refactoring the get_attr() getter to embed the fully-qualified
name of the passed module when passed a module in raised
exceptions.
* Generalized the existing "betse.util.py.pys" submodule:
* Defined a new is_conda() tester, returning true if the active
Python interpreter is managed by conda. For debuggability, this
metadata is now logged when the "info" subcommand is run.
* Defined a new "betse.util.test.pytest.fixture" subpackage:
* Defined a new "betse.util.test.pytest.fixture.pytfixture"
submodule:
* Defined a new monkeypatch_session() fixture, generalizing the
standard function-scope "monkeypatch" fixture to session scope.
* Generalized the existing "betse.util.test.pytest.pytests" submodule:
* Defined a new output() function, printing the passed objects to
standard output in a format consistent with that of standard
"py.test" messages.
* Generalized the existing "betse.util.type.iterable.iterables"
submodule:
* Defined a new to_str() function, synthesizing a human-readable
prettified string of the passed iterable.
* Improves the existing to_iterable() converter to behave as
expected when passed an input generator.
* Defined a new "betse.util.type.iterable.iteriter" submodule:
* Defined a new iter_pairs() iterator, creating and returning a new
iterable of all possible pairs of items in the passed iterable.
* Defined a new iter_combinations() iterator, creating and returning
a new iterable of all possible k-combinations of items in the
passed iterable.
* Generalized the existing "betse.util.type.iterable.mapping.mappings"
submodule:
* Defined a new die_unless_keys_unique() function, raising an
exception unless no passed dictionaries collide (i.e., contain the
same key).
* Defined a new is_keys_unique() tester, returning true only if no
passed dictionaries collide.
* Defined a new iter_keys_ascending() iterator, returning an
iterable of all keys of the passed dictionary in ascending order
(typically, lexicographic).
* Removed the unused (and largely useless) format_map() function,
now superceded by the newly defined
betse.util.type.iterable.iterables.to_str() function.
* Defined a new "betse.util.type.iterable.mapping.mapmerge" submodule:
* Shifted the betse.util.type.iterable.mapping.mappings.merge_maps()
function into this submodule.
* Defined a new "MergeCollisionPolicy" enumeration type whose
members each describe a different type of key collision merger
policy (i.e., strategy for merging keys shared by one or more
mappings).
* Refactored the merge_maps() function to:
* Merge arbitrarily many dictionaries under any key collision
policy.
* Behave as expected when passed a non-sequence of input mappings.
* Defer to the newly defined
betse.util.type.iterable.mapping.maptest.die_if_maps_collide()
validator.
* Defined a new "betse.util.type.iterable.mapping.maptest" submodule:
* Shifted all testing and exception handling logic from the
"betse.util.type.iterable.mapping.mappings" submodule into this
submodule.
* Renamed the following functions for orthogonality:
* die_unless_keys_equal() to die_unless_maps_keys_equal().
* die_unless_keys_unique() to die_unless_maps_keys_unique().
* is_key() to has_keys().
* is_keys_equal() to is_maps_keys_equal().
* is_keys_unique() to is_maps_keys_unique().
* Defined a new die_unless_has_keys() validator, raising an
exception unless the passed mappings contains all passed keys.
* Replaced the largely useless die_unless_maps_keys_unique()
validator with a new die_if_maps_collide() validator, raising an
exception only if no passed mappings collide.
* Replaced the largely useless is_maps_keys_unique() tester with a
new is_maps_collide() tester, returning true only if one or more
passed mappings collide (i.e., contain key-value pairs containing
the same keys but differing values).
* Refactored the die_unless_maps_keys_unique() validator to improve
the granularity of the exception message raised by this function,
which now iteratively finds the exact set of key collisions in the
passed dictionaries with the newly defined
betse.util.type.iterable.iteriter.iter_pairs() iterator.
* Refactored the has_keys() tester to accept a simple iterable of
all keys to be tested rather than a variadic positional tuple of
such keys.
* Generalized the "betse.util.type.iterable.sequences" submodule:
* Defined a new die_if_length_less_than() validator, raising an
exception if the passed sequence contains less than the passed
number of items.
* Defined a new to_sequence() converter, converting the passed
iterable into a sequence as needed.
* Refactored the die_if_empty() validator to operate similarly to
all other validators defined by this submodule.
* Generalizes the "betse.util.type.iterable.mapping.maptest"
submodule:
* Refactored the is_maps_collide() tester to call the newly defined
betse.util.type.iterable.sequences.die_if_length_less_than() and
betse.util.type.iterable.set.sets.make_union() functions, thereby
both simplifying and correcting the existing implementation.
* Defined a new "betse.util.type.iterable.set" subpackage:
* Shifted the existing "betse.util.type.iterable.sets" submodule to
"betse.util.type.iterable.set.setcls" for disambiguity.
* Defined a new "betse.util.type.iterable.set.sets" submodule:
* Defined a new make_union() function, calculating the union of
all items in arbitrarily many possibly non-set iterables.
* Generalized the existing "betse.util.type.text.string.strs"
submodule:
* Refactored the get_prefix_preceding_char_or_none() getter:
* Renamed this getter to get_prefix_or_none().
* Renamed the "char" parameter accepted by this getter to
"anchor".
* Added an optional "is_first" parameter to this getter, enabling
callers to retrieve both greedy and non-greedy prefixes.
* Streamlined the existing "betse_setup.buputil" submodule by removing
extraneous functionality (e.g., die_unless_module(), is_module()).
* Streamlined the existing "betse_test.conftest" submodule:
* Removed all functionality pertaining to the ${DISPLAY} environment
variable from the less robust and reusable pytest_configure()
function in favour of the
betse_test.fixture.initter.betse_autouse() fixture.
* Removed the obsolete "EXPORT_SIM_CONF_DIRNAME" global.
* Defined a new "betse_test.data" subdirectory embedding the default
simulation configuration supplied by BETSE 0.5.0, enabling the
betse_test.func.sim.test_sim.test_cli_sim_compat() functional test
to fully exercise backward compatibility with this configuration.
* Rewrote the "betse_test.fixture.initter" submodule:
* Defined a new autouse package-scoped betse_init_package() fixture,
intended to be imported by the "betse_test.unit.conftest" plugin
to guarantee application initialization before unit testing.
* Renamed the betse_meta_app() fixture to betse_autouse(), which now
additionally unsets the ${DISPLAY} environment variable if set.
* Moved the existing betse_test.conftest._init_app() and
_deinit_app() functions into this submodule as init_app() and
deinit_app() for importability elsewhere.
* Generalized the existing "betse_test.fixture.simconf.simconfer"
submodule:
* Redefined the betse_sim_conf() fixture in terms of the lower-level
betse_sim_conf_default() fixture.
* Refactored the betse_sim_conf_compat() fixture to leverage the
Git-hosted "betse_test/data/v0.5.0/yaml/sim_config.yaml"
simulation configuration corresponding to the default
configuration circa BETSE 0.5.0.
* Generalized the existing "betse_test.fixture.simconf.simconfclser"
submodule:
* Refactored the "SimConfTestABC" superclass to initialize the
application metadata singleton before saving a simulation
configuration for the current functional test.
* Refactored the "SimConfTestInternal" subclass to require
general-purpose "src_conf_filename" and "trg_conf_filepath"
filenames be passed on initialization. Previously, this subclass
only accepted the latter and defaulted the former to the default
simulation configuration file.
* Generalized the existing "betse_test.fixture.simconf.simconfwrapper"
submodule:
* Refactored the "SimConfigTestWrapper" class:
* Refactored the __init__() method to accept a high-level
"Parameters" object rather than a low-level simulation
configuration filename.
* Removed a number of extraneous methods and properties (notably,
the make_default() class method).
* Generalized the existing "betse_test.func.fixture.clier" submodule:
* Called the betse.util.app.meta.metaappabc.AppMetaABC.deinit()
method before running each functional test.
* Removed the obsolete "betse_test.func.sim.test_sim_export"
submodule.
* Generalized the existing "betse_test.func.sim.test_sim" submodule:
* Removed all @skip decoration from the test_cli_sim_compat() test.
* Defined a new "betse_test.unit.type.iterable.mapping.test_mapmerge"
submodule, fully exercising the newly generalized
betse.util.type.iterable.mapping.mapmerge.merge_maps() function (now
featuring 667% more obscure Cthulhu mythos references).
* Defined a new "betse_test.unit.app.meta.test_appmetamod" submodule,
fully exercising the newly defined
betse.util.app.meta.appmetamod.merge_module_metadeps() function.
* Generalized the betse_test.unit.path.test_dirs.test_packages_init()
unit test to exclude all data-specific directories from
consideration, including both the "AppMetaABC.data_dirname" and
"AppMetaABC.test_data_dirname" directories if found.
* Generalized the existing
betse_test.unit.type.iterable.test_iterables.test_to_iterable() unit
test to exercise additional edge cases of that underlying function.