From fef6e9be0d0847ed473a1b6e0576c9399e30cb7b Mon Sep 17 00:00:00 2001 From: Pavel Cisar Date: Fri, 3 Mar 2023 16:31:06 +0100 Subject: [PATCH 01/38] Release v1.6.1 --- docs/changelog.txt | 11 +++++++++++ docs/conf.py | 4 ++-- docs/config.txt | 2 ++ pyproject.toml | 4 ++-- src/firebird/base/config.py | 39 +++++++++++++++++++++++++++++-------- src/firebird/base/types.py | 7 ++++--- test/test_config.py | 20 +++++++++++++++++++ 7 files changed, 72 insertions(+), 15 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 08221a1..a4f096f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -2,6 +2,16 @@ Changelog ######### +Version 1.6.1 +============= + +* `~firebird.base.config` module: + + - Fixed bug with `.Config.get_config()` and `plain` bool argument. + - `.StrOption` now supports preservation of significant leading whitespace for multiline + values (like `.PyCodeOption`). + + Version 1.6.0 ============= @@ -14,6 +24,7 @@ Version 1.6.0 - `.Config.get_config()` and `.Option.get_config()` now provides `plain` bool argument to return configuration text without comments. Deafult is False. + - `.create_config` is now deprecated, will be removed in version 2.0. * `~firebird.base.trace` module: diff --git a/docs/conf.py b/docs/conf.py index 7e80248..e5a34a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,10 +23,10 @@ author = 'Pavel Císař' # The short X.Y version -version = '1.6.0' +version = '1.6.1' # The full version, including alpha/beta/rc tags -release = '1.6.0' +release = '1.6.1' # -- General configuration --------------------------------------------------- diff --git a/docs/config.txt b/docs/config.txt index dc79f62..daf1d0f 100644 --- a/docs/config.txt +++ b/docs/config.txt @@ -359,5 +359,7 @@ Options Functions ========= +.. autofunction:: has_verticals +.. autofunction:: has_leading_spaces .. autofunction:: create_config diff --git a/pyproject.toml b/pyproject.toml index 8ef08ad..f0824a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "firebird-base" -version = "1.6.0" +version = "1.6.1" description = "Firebird base modules for Python" readme = "README.rst" requires-python = ">=3.8" @@ -26,7 +26,7 @@ classifiers = [ "Topic :: Software Development", ] dependencies = [ - "protobuf>=4.21.0, <5", + "protobuf>=4.21.0, <4.22", ] [project.urls] diff --git a/src/firebird/base/config.py b/src/firebird/base/config.py index a7258f7..a150f2d 100644 --- a/src/firebird/base/config.py +++ b/src/firebird/base/config.py @@ -63,8 +63,16 @@ PROTO_CONFIG = 'firebird.base.ConfigProto' +def has_verticals(value: str) -> bool: + "Returns True if lines in multiline string contains leading '|' character." + return any(1 for line in value.split('\n') if line.startswith('|')) + +def has_leading_spaces(value: str) -> bool: + "Returns True if any line in multiline string starts with space(s)." + return any(1 for line in value.split('\n') if line.startswith(' ')) + def unindent_verticals(value: str) -> str: - """Removes trailing '|' character from each line in multiline string.""" + """Removes leading '|' character from each line in multiline string.""" lines = [] indent = None for line in value.split('\n'): @@ -81,11 +89,13 @@ def _eq(a: Any, b: Any) -> bool: def create_config(_cls: Type[Config], name: str) -> Config: # pragma: no cover """Return newly created `Config` instance. Intended to be used with `functools.partial`. + + .. deprecated:: 1.6 + Will be removed in version 2.0 """ return _cls(name) # Next two functions are copied from stdlib enum module, as they were removed in Python 3.11 - def _decompose(flag, value): """ Extract all members from the value. @@ -409,8 +419,8 @@ def get_directory_scheme(app_name: str, version: str=None, *, force_home: bool=F app_name: Application name version: Application version string force_home: When True, the general directies are always set to subdirectories of - `DirectoryScheme.home` directory. When False, these the home is used - ONLY when it's set by "_HOME" environment variable. + `DirectoryScheme.home` directory. When False, then home is used ONLY + when it's set by "_HOME" environment variable. """ return {'Windows': WindowsDirectoryScheme, 'Linux':LinuxDirectoryScheme, @@ -461,7 +471,7 @@ def _get_config_lines(self, plain: bool=False) -> List[str]: file processed with `~configparser.ConfigParser`. Text lines with configuration start with comment marker `;` and end with newline. - + Arguments: plain: When True, it outputs only the option value. When False, it includes also option description and other helpful information. @@ -517,7 +527,7 @@ def validate(self) -> None: def get_config(self, *, plain: bool=False) -> str: """Returns string containing text lines suitable for use in configuration file processed with `~configparser.ConfigParser`. - + Arguments: plain: When True, it outputs only the option value. When False, it includes also option description and other helpful information. @@ -655,7 +665,7 @@ def get_config(self, *, plain: bool=False) -> str: for config in self.configs: if not plain: lines.append('\n') - lines.append(config.get_config()) + lines.append(config.get_config(plain=plain)) return ''.join(lines) def load_config(self, config: ConfigParser, section: str=None) -> None: """Update configuration. @@ -738,6 +748,17 @@ def configs(self) -> List[Config]: # Options class StrOption(Option[str]): """Configuration option with string value. + + .. versionadded:: 1.6.1 + Support for verticals to preserve leading whitespace. + + Important: + Multiline string values could contain significant leading whitespace, but + ConfigParser multiline string values have leading whitespace removed. To circumvent + this, the `StrOption` supports assignment of text values where lines start with `|` + character. This character is removed, along with any number of subsequent whitespace + characters that are between `|` and first non-whitespace character on first line + starting with `|`. """ def __init__(self, name: str, description: str, *, required: bool=False, default: str=None): """ @@ -764,9 +785,10 @@ def get_formatted(self) -> str: result = self._value if '\n' in result: lines = [] + indent = ' | ' if has_leading_spaces(result) else ' ' for line in result.splitlines(True): if lines: - lines.append(' ' + line) + lines.append(indent + line) else: lines.append(line) result = ''.join(lines) @@ -780,6 +802,7 @@ def set_as_str(self, value: str) -> None: Raises: ValueError: When the argument is not a valid option value. """ + value = unindent_verticals(value) self._value = value def get_as_str(self) -> str: """Returns value as string. diff --git a/src/firebird/base/types.py b/src/firebird/base/types.py index 0837326..5d0db9d 100644 --- a/src/firebird/base/types.py +++ b/src/firebird/base/types.py @@ -374,7 +374,7 @@ def get_callable(self, arguments: str='', namespace: Dict[str, Any]=None) -> Cal return ns['expr'] @property def expr(self): - "Expression code ready to be appased to `eval`." + "Expression code ready to be pased to `eval`." return self._expr_ class PyCode(str): @@ -394,7 +394,7 @@ def __new__(cls, value: str): return new @property def code(self): - """Python code ready to be appased to `exec`. + """Python code ready to be pased to `exec`. """ return self._code_ @@ -436,7 +436,8 @@ def __call__(self, *args, **kwargs): # Metaclasses def Conjunctive(name, bases, attrs): """Returns a metaclass that is conjunctive descendant of all metaclasses used by parent - classes. + classes. It's necessary to create a class with multiple inheritance, where multiple + parent classes use different metaclasses. Example: diff --git a/test/test_config.py b/test/test_config.py index 62ecb32..d6fedf8 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -129,6 +129,13 @@ def setUp(self): [%(ABSENT)s] [%(BAD)s] option_name = +[VERTICALS] +option_name = + | def pp(value): + | print("Value:",value,file=output) + | + | for i in [1,2,3]: + | pp(i) """) def test_simple(self): opt = config.StrOption('option_name', 'description') @@ -156,6 +163,9 @@ def test_simple(self): opt.set_value(self.NEW_VAL) self.assertEqual(opt.value, self.NEW_VAL) self.assertIsInstance(opt.value, opt.datatype) + # Verticals + opt.load_config(self.conf, 'VERTICALS') + self.assertEqual(opt.get_as_str(), '\ndef pp(value):\n print("Value:",value,file=output)\n\nfor i in [1,2,3]:\n pp(i)') def test_required(self): opt = config.StrOption('option_name', 'description', required=True) self.assertEqual(opt.name, 'option_name') @@ -259,6 +269,16 @@ def test_get_config(self): """ opt.set_value(None) self.assertEqual(opt.get_config(), lines) + lines = """; description +; Type: str +option_name = + | def pp(value): + | print("Value:",value,file=output) + | + | for i in [1,2,3]: + | pp(i)""" + opt.set_value('\ndef pp(value):\n print("Value:",value,file=output)\n\nfor i in [1,2,3]:\n pp(i)') + self.assertEqual('\n'.join(x.rstrip() for x in opt.get_config().splitlines()), lines) class TestIntOption(BaseConfigTest): "Unit tests for firebird.base.config.IntOption" From fd3f0025f5026c2d5520f96e6813c068be269faa Mon Sep 17 00:00:00 2001 From: Pavel Cisar Date: Fri, 3 Mar 2023 17:03:23 +0100 Subject: [PATCH 02/38] Updated documentation --- README.rst | 155 +- .../Contents/Resources/Documents/.buildinfo | 2 +- .../_modules/firebird/base/buffer.html | 87 +- .../_modules/firebird/base/collections.html | 89 +- .../_modules/firebird/base/config.html | 526 +- .../_modules/firebird/base/hooks.html | 35 +- .../_modules/firebird/base/logging.html | 49 +- .../_modules/firebird/base/protobuf.html | 53 +- .../_modules/firebird/base/signal.html | 33 +- .../_modules/firebird/base/strconv.html | 45 +- .../_modules/firebird/base/trace.html | 81 +- .../_modules/firebird/base/types.html | 80 +- .../Resources/Documents/_modules/index.html | 11 +- .../Documents/_sources/changelog.txt | 11 + .../Resources/Documents/_sources/config.txt | 2 + .../Documents/_sources/introduction.txt | 31 +- .../Resources/Documents/_sources/trace.txt | 4 +- .../_sphinx_javascript_frameworks_compat.js | 134 - .../Resources/Documents/_static/doctools.js | 2 +- .../_static/documentation_options.js | 2 +- .../Documents/_static/jquery-3.6.0.js | 10881 ---------------- .../Resources/Documents/_static/jquery.js | 2 - .../Documents/_static/language_data.js | 2 +- .../Documents/_static/searchtools.js | 2 +- .../Documents/_static/underscore-1.13.1.js | 2042 --- .../Resources/Documents/_static/underscore.js | 6 - .../Contents/Resources/Documents/buffer.html | 165 +- .../Resources/Documents/changelog.html | 27 +- .../Resources/Documents/collections.html | 189 +- .../Contents/Resources/Documents/config.html | 938 +- .../Resources/Documents/genindex.html | 15 +- .../Contents/Resources/Documents/hooks.html | 67 +- .../Contents/Resources/Documents/index.html | 13 +- .../Resources/Documents/introduction.html | 42 +- .../Contents/Resources/Documents/license.html | 13 +- .../Contents/Resources/Documents/logging.html | 61 +- .../Contents/Resources/Documents/modules.html | 13 +- .../Contents/Resources/Documents/objects.inv | Bin 5782 -> 5816 bytes .../Resources/Documents/protobuf.html | 75 +- .../Resources/Documents/py-modindex.html | 11 +- .../Contents/Resources/Documents/search.html | 11 +- .../Resources/Documents/searchindex.js | 2 +- .../Contents/Resources/Documents/signal.html | 39 +- .../Contents/Resources/Documents/strconv.html | 95 +- .../Contents/Resources/Documents/trace.html | 123 +- .../Contents/Resources/Documents/types.html | 58 +- .../Contents/Resources/docSet.dsidx | Bin 86016 -> 86016 bytes docs/introduction.txt | 31 +- docs/trace.txt | 4 +- 49 files changed, 1731 insertions(+), 14628 deletions(-) delete mode 100644 docs/firebird-base.docset/Contents/Resources/Documents/_static/_sphinx_javascript_frameworks_compat.js delete mode 100644 docs/firebird-base.docset/Contents/Resources/Documents/_static/jquery-3.6.0.js delete mode 100644 docs/firebird-base.docset/Contents/Resources/Documents/_static/jquery.js delete mode 100644 docs/firebird-base.docset/Contents/Resources/Documents/_static/underscore-1.13.1.js delete mode 100644 docs/firebird-base.docset/Contents/Resources/Documents/_static/underscore.js diff --git a/README.rst b/README.rst index 2a6f713..7107739 100644 --- a/README.rst +++ b/README.rst @@ -2,22 +2,147 @@ Firebird base modules for Python ================================ -The `firebird-base` package provides common Python 3 modules used by `Firebird Project`_ -in various development projects. However, these modules have general applicability outside -the scope of development for `Firebird`_ ® RDBMS. - -Topic covered by `firebird-base` package: - -* General data types like `singletons`, `sentinels` and objects with identity. -* Unified system for data conversion from/to string. -* `DataList` and `Registry` collection types with advanced data-processing cappabilities. -* Work with structured binary buffers. -* Global registry of Google `protobuf` messages and enumerations. -* Extended configuration system based on `ConfigParser`. -* Context-based logging. -* Trace/audit for class instances. -* General "hook" mechanism. +The firebird-base package is a set of Python 3 modules commonly used by Firebird Project_ in +various development projects (for example the firebird-driver or Saturnin). However, these +modules have general applicability outside the scope of development for Firebird_. +Common data types +================= + +The `types` module provides collection of classes and other types that are often used by +other library modules or applications. + +* Exception `Error` that is intended to be used as a base class of all application-related + errors. The important difference from `Exception` class is that `Error` accepts keyword + arguments, that are stored into instance attributes with the same name. +* `Singleton` base class for singletons. +* `Sentinel` base class for named sentinel objects that provide meaningful `str` and `repr`, + along with collection of predefined sentinels. +* `Distinct` abstract base class for classes (incl. dataclasses) with distinct instances. +* Collection of `Enums` and `custom string types`. + +Various collection types +======================== + +The `collections` module provides data structures that behave much like builtin `list` and +`dict` types, but with direct support of operations that can use structured data stored in +container, and which would normally require utilization of `operator`, `functools` or other +means. + +All containers provide next operations: + +* `filter` and `filterfalse` that return generator that yields items for which expr is + evaluated as True (or False). +* `find` that returns first item for which expr is evaluated as True, or default. +* `contains` that returns True if there is any item for which expr is evaluated as True. +* `occurrence` that returns number of items for which expr is evaluated as True. +* `all` and `any` that return True if expr is evaluated as True for all or any collection element(s). +* `report` that returns generator that yields data produced by expression(s) evaluated on collection items. + +Individual collection types provide additional operations like splitting and extracting +based on expression etc. + +Expressions used by these methods could be strings that contain Python expression referencing +the collection item(s), or lambda functions. + +Data conversion from/to string +============================== + +While Python types typically support conversion to string via builtin `str()` function (and +custom `__str__` methods), there is no symetric operation that converts string created by +`str()` back to typed value. Module `strconv` provides support for such symetric conversion +from/to string for any data type. + +Symetric string conversion is used by `~firebird.base.config` module, notably by +`~firebird.base.config.ListOption` and `~firebird.base.config.DataclassOption`. You can +extend the range of data types supported by these options by registering convertors for +required data types. + +Configuration definitions +========================= + +Complex applications (and some library modules like `logging`) could be often parametrized +via configuration. Module `~firebird.base.config` provides a framework for unified structured +configuration that supports: + +* configuration options of various data type, including lists and other complex types +* validation +* direct manipulation of configuration values +* reading from (and writing into) configuration in `configparser` format +* exchanging configuration (for example between processes) using Google protobuf messages + +Additionally, the `ApplicationDirectoryScheme` abstract base class defines set of mostly +used application directories. The function `get_directory_scheme()` could be then used +to obtain instance that implements platform-specific standards for file-system location +for these directories. Currently, only "Windows", "Linux" and "MacOS" directory schemes +are supported. + +Memory buffer manager +===================== + +Module `buffer` provides a raw memory buffer manager with convenient methods to read/write +data of various data types. + +Hook manager +============ + +Module `hooks` provides a general framework for callbacks and “hookable” events, that +supports multiple usage strategies. + +Context-based logging +===================== + +Module `logging` provides context-based logging system built on top of standard `logging` +module. + +The context-based logging: + +* Adds context information (defined as combination of topic, agent and context string values) + into `~logging.LogRecord`, that could be used in logging message. +* Adds support for f-string message format. +* Allows assignment of loggers to specific contexts. The `LoggingManager` class maintains + a set of bindings between `Logger` objects and combination of `agent`, `context` and `topic` + specifications. It’s possible to bind loggers to exact combination of values, or whole + sets of values using `ANY` sentinel. It means that is possible to assign specific Logger + to log messages for particular agent in any context, or any agent operating in specific + context etc. + +Trace/audit for class instances +=============================== + +Module `trace` provides trace/audit logging for functions or object methods through +context-based logging provided by `logging` module. + +The trace logging is performed by `traced` decorator. You can use this decorator directly, +or use `TracedMixin` class to automatically decorate methods of class instances on creation. +Each decorated callable could log messages before execution, after successful execution or +on failed execution (when unhandled exception is raised by callable). The trace decorator +can automatically add `agent` and `context` information, and include parameters passed to +callable, execution time, return value, information about raised exception etc. to log messages. + +The trace logging is managed by `TraceManager`, that allows dynamic configuration of traced +callables at runtime. + +Trace supports configuration based on `~firebird.base.config`. + +Registry for Google Protocol Buffer messages and enums +====================================================== + +Module `protobuf` provides central registry for Google Protocol Buffer messages and enums. +The generated `*_pb2.py protobuf` files could be registered using `register_decriptor` or +`load_registered` function. The registry could be then used to obtain information about +protobuf messages or enum types, or to create message instances or enum values. + +Callback systems +================ + +Module `~firebird.base.signal` provides two callback mechanisms: one based on signals and +slots similar to Qt signal/slot, and second based on optional method delegation similar to +events in Delphi. + +In both cases, the callback callables could be functions, instance or class methods, +partials and lambda functions. The `inspect` module is used to define the signature for +callbacks, and to validate that only compatible callables are assigned. |donate| diff --git a/docs/firebird-base.docset/Contents/Resources/Documents/.buildinfo b/docs/firebird-base.docset/Contents/Resources/Documents/.buildinfo index 5d0f8b8..637384c 100644 --- a/docs/firebird-base.docset/Contents/Resources/Documents/.buildinfo +++ b/docs/firebird-base.docset/Contents/Resources/Documents/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: c6141e70e401315cd341f57590fb931d +config: 6c1c3e11e055f670ac1c61f7bae89497 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/firebird-base.docset/Contents/Resources/Documents/_modules/firebird/base/buffer.html b/docs/firebird-base.docset/Contents/Resources/Documents/_modules/firebird/base/buffer.html index 131ccec..c9d13d3 100644 --- a/docs/firebird-base.docset/Contents/Resources/Documents/_modules/firebird/base/buffer.html +++ b/docs/firebird-base.docset/Contents/Resources/Documents/_modules/firebird/base/buffer.html @@ -4,16 +4,13 @@ - firebird.base.buffer — Firebird-base 1.4.2 documentation + firebird.base.buffer — Firebird-base 1.6.1 documentation - - - - + @@ -39,7 +36,7 @@ Firebird-base - 1.4.2 + 1.6.1