BETSE 1.1.0 (Nice Nestor) released. 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.