Skip to content

Commit 3b68fe6

Browse files
committed
Release 1.1.0
1 parent d993386 commit 3b68fe6

25 files changed

+3300
-1498
lines changed

docs/changelog.txt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,47 @@
22
Changelog
33
#########
44

5+
Version 1.1.0
6+
=============
7+
8+
* New module: `signal` - Callback system based on Signals and Slots, and "Delphi events"
9+
* `~firebird.base.config` module:
10+
11+
- New class `.ApplicationDirectoryScheme`
12+
- `~firebird.base.config.Config.load_config()`: raises error when section is missing,
13+
better error handling when exception is raised while loading options
14+
- `~firebird.base.config.PyCallableOption` `signature` argument could be
15+
`inspect.Signature` or Callable
16+
- Introduced `.PROTO_CONFIG` constant with fully qualified name for `ConfigProto` protobuf
17+
- Optional argument `to_default` in `~firebird.base.config.Option.clear()` is now keyword-only.
18+
* `~firebird.base.logging` module:
19+
20+
- `.get_logging_id()` uses `__qualname__` instead `__name__`
21+
* `~firebird.base.protobuf` module:
22+
23+
- Added direct support for key well-known data types `Empty`, `Any`, `Duration`,
24+
`Timestamp`, `Struct`, `Value`, `ListValue` and `FieldMask`. They are automatically
25+
registered. New constants 'PROTO_<type>' with fully qualified names.
26+
- `~firebird.base.protobuf.create_message()` has new optional `serialized` argument with
27+
`bytes` that should be parsed into newly created message instance.
28+
- New functions `~firebird.base.protobuf.struct2dict()` and `~firebird.base.protobuf.dict2struct()`
29+
* `~firebird.base.trace` module:
30+
31+
- `~firebird.base.trace.TraceFlag` value `DISABLED` was renamed to `NONE`.
32+
- Added support for trace configuration based on `~firebird.base.config`, using new
33+
classes `~firebird.base.trace.BaseTraceConfig`, `~firebird.base.trace.TracedMethodConfig`,
34+
`~firebird.base.trace.TracedClassConfig` and `~firebird.base.trace.TraceConfig`.
35+
- New methods in `~firebird.base.trace.TraceManager`:
36+
37+
- `~firebird.base.trace.TraceManager.load_config()` to update trace from configuration.
38+
- `~firebird.base.trace.TraceManager.set_flag()` and
39+
`~firebird.base.trace.TraceManager.clear_flag()`.
40+
* `~firebird.base.types` module:
41+
42+
- `~firebird.base.types.MIME` now handles access to properties more efficiently and faster.
43+
- New function `~firebird.base.types.load()`.
44+
* Changes in documentation.
45+
546
Version 1.0.0
647
=============
748

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
author = 'Pavel Císař'
2424

2525
# The short X.Y version
26-
version = '1.0.0'
26+
version = '1.1.0'
2727

2828
# The full version, including alpha/beta/rc tags
29-
release = '1.0.0'
29+
release = '1.1.0'
3030

3131

3232
# -- General configuration ---------------------------------------------------

docs/config.txt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ The framework is based around two classes:
3333
`.PyCode` and `.PyCallable`. It also provides special options `ConfigOption` and
3434
`ConfigListOption`.
3535

36+
Additionally, the `.ApplicationDirectoryScheme` abstract base class defines set of mostly
37+
used application directories. The function `.get_directory_scheme()` could be then used
38+
to obtain instance that implements platform-specific standards for file-system location
39+
for these directories. Currently, only "Windows" and "Linux" directory schemes are supported.
40+
41+
.. note::
42+
You may use `platform.system` call to determine the scheme name suitable for platform
43+
where your application is running.
44+
3645
Usage
3746
-----
3847
First, you need to define your own configuration.
@@ -296,8 +305,23 @@ The protobuf message is defined in :file:`/proto/config.proto`.
296305

297306
.. seealso:: `.ConfigOption`, `.ConfigListOption`
298307

308+
.. tip::
309+
310+
To address `ConfigProto`_ in functions like `~firebird.base.protobuf.create_message()`,
311+
use `PROTO_CONFIG` constant.
312+
313+
.. data:: PROTO_CONFIG
314+
:annotation: Fully qualified name for `ConfigProto`_ protobuf.
315+
316+
317+
Application Directory Scheme
318+
============================
319+
320+
.. versionadded:: 1.1.0
321+
322+
.. autoclass:: ApplicationDirectoryScheme
299323

300-
.. autoclass:: ConfigProto
324+
.. autofunction:: get_directory_scheme
301325

302326
Config
303327
======

docs/index.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Topic covered by `firebird-base` package:
1818
- Context-based logging.
1919
- Trace/audit for class instances.
2020
- General "hook" mechanism.
21+
- Callback system based on Signals and Slots, and "Delphi events"
2122

2223

2324
.. note:: Requires Python 3.8+

docs/introduction.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,13 @@ Module `.protobuf` provides central registry for Google Protocol Buffer messages
123123
The generated `*_pb2.py protobuf` files could be registered using `.register_decriptor` or
124124
`.load_registered` function. The registry could be then used to obtain information about
125125
protobuf messages or enum types, or to create message instances or enum values.
126+
127+
Callback systems
128+
================
129+
130+
Module `.signal` provides two callback mechanisms: one based on signals and slots similar
131+
to Qt signal/slot, and second based on optional method delegation similar to events in Delphi.
132+
133+
In both cases, the callback callables could be functions, instance or class methods,
134+
partials and lambda functions. The `inspect` module is used to define the signature for
135+
callbacks, and to validate that only compatible callables are assigned.

docs/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ Package modules
1717
logging
1818
trace
1919
protobuf
20-
20+
signal

docs/protobuf.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ get_enum_field_type
4848
-------------------
4949
.. autofunction:: get_enum_field_type
5050

51+
struct2dict
52+
-----------
53+
.. autofunction:: struct2dict
54+
55+
dict2struct
56+
-----------
57+
.. autofunction:: dict2struct
58+
5159
Data classes
5260
============
5361

docs/signal.txt

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
.. module:: firebird.base.signal
2+
:synopsis: Callback system based on Signals and Slots, and "Delphi events"
3+
4+
########################################################################
5+
signal - Callback system based on Signals and Slots, and "Delphi events"
6+
########################################################################
7+
8+
Overview
9+
========
10+
11+
This module provides two callback mechanisms: one based on signals and slots similar to Qt
12+
signal/slot, and second based on optional method delegation similar to events in Delphi.
13+
14+
In both cases, the callback callables could be functions, instance or class methods,
15+
partials and lambda functions. The `inspect` module is used to define the signature for
16+
callbacks, and to validate that only compatible callables are assigned.
17+
18+
.. important::
19+
20+
All type annotations in signatures are significant, so callbacks must have exactly the
21+
same annotations as signatures used by signals or events. The sole exception are excess
22+
keyword arguments with default values defined on connected callable.
23+
24+
.. tip::
25+
26+
You may use `functools.partial` to adapt callable with different signatures. However,
27+
you can "mask" only keyword arguments (without default) and leading positional arguments
28+
(as any positional argument binded by name will not mask-out parameter from signature
29+
introspection).
30+
31+
Signals and Slots
32+
-----------------
33+
34+
Signals and slots are suitable for 1:N notification schemes. The `Signal` works as a point
35+
to which one or more Slots could be connected. When `Signal` is "emitted", all connected
36+
slots are called (executed). It's possible to pass parameters to slot callables, but any
37+
value returned by slot callable is ignored. The Signal contructor takes `inspect.Signature`
38+
argument that defines the required signature that callables (slots) must have to connect
39+
to this signal.
40+
41+
This mechanism is provided in two forms:
42+
43+
- The `Signal` class to create signal instances for direct use.
44+
- The `signal` decorator to define signals on classes. This decorator works like builtin
45+
`property` (without setter and deleter), where the 'getter' method is used only to define
46+
the signature required for slots.
47+
48+
**Example:**
49+
50+
.. code-block::
51+
52+
class Emitor:
53+
def __init__(self, name: str):
54+
self.name = name
55+
def showtime(self):
56+
self.signal_a(self, 'They Live!', 42)
57+
@signal
58+
def signal_a(self, source: Emitor, msg: str, value: int) -> None:
59+
"Documentation for signal"
60+
61+
class Receptor:
62+
def __init__(self, name: str):
63+
self.name = name
64+
def on_signal_a(self, source: Emitor, msg: str, value: int) -> None:
65+
print(f"{self.name} received signal from {source.name} ({msg=}, {value=})")
66+
@classmethod
67+
def cls_on_signal_a(cls, source: Emitor, msg: str, value: int) -> None:
68+
print(f"{cls.__name__} received signal from {source.name} ({msg=}, {value=})")
69+
70+
def on_signal_a(source: Emitor, msg: str, value: int):
71+
print(f"Function 'on_signal_a' received signal from {source.name} ({msg=}, {value=})")
72+
73+
e1 = Emitor('e1')
74+
e2 = Emitor('e2')
75+
r1 = Receptor('r1')
76+
r2 = Receptor('r2')
77+
#
78+
e1.signal_a.connect(r1.on_signal_a)
79+
e1.signal_a.connect(r2.on_signal_a)
80+
e1.signal_a.connect(r1.cls_on_signal_a)
81+
e2.signal_a.connect(on_signal_a)
82+
e2.signal_a.connect(r2.on_signal_a)
83+
#
84+
e1.showtime()
85+
e2.showtime()
86+
87+
|
88+
89+
**Output from sample code**::
90+
91+
r1 received signal from e1 (msg='They Live!', value=42)
92+
r2 received signal from e1 (msg='They Live!', value=42)
93+
Receptor received signal from e1 (msg='They Live!', value=42)
94+
Function 'on_signal_a' received signal from e2 (msg='They Live!', value=42)
95+
r2 received signal from e2 (msg='They Live!', value=42)
96+
97+
98+
Events
99+
------
100+
101+
Events are suitable for optional callbacks that delegate some functionality to other class
102+
or function.
103+
104+
The 'event' works as a point to which one 'slot' could be connected. The event itself
105+
acts as callable, that executes the connected slot (if assigned). Events may have parameters
106+
and return values. Events could be defined only on classes using `eventsocket` decorator,
107+
that works like builtin `property` (without deleter), where the 'getter' method is used only
108+
to define the signature required for slot, and 'setter' is used to assign the callable.
109+
To disconnect the callable from event, simply assign None to the event.
110+
111+
**Example:**
112+
113+
.. code-block::
114+
115+
class Component:
116+
def __init__(self, name: str):
117+
self.name = name
118+
@eventsocket
119+
def on_init(self, source: Component, arg: str) -> bool:
120+
"Documentation for event"
121+
@eventsocket
122+
def on_exit(self, source: Component) -> None:
123+
"Documentation for event"
124+
def showtime(self) -> None:
125+
print(f"{self.name}.on_init handler is {'SET' if self.on_init.is_set() else 'NOT SET'}")
126+
print(f"{self.name}.on_exit handler is {'SET' if self.on_exit.is_set() else 'NOT SET'}")
127+
print("Event handler returned", self.on_init(self, 'argument'))
128+
print(f"{self.name} does something...")
129+
self.on_exit(self)
130+
131+
class Container:
132+
def __init__(self):
133+
self.c1 = Component('C1')
134+
self.c1.on_init = self.event_init
135+
self.c2 = Component('C2')
136+
self.c2.on_init = self.event_init
137+
self.c2.on_exit = self.event_exit
138+
self.c3 = Component('C3')
139+
self.c3.on_exit = self.event_exit
140+
def event_init(self, source: Component, arg: str) -> bool:
141+
print(f"Handlig {source.name}.on_init({arg=})")
142+
return source is self.c2
143+
def event_exit(self, source: Component) -> None:
144+
print(f"Handlig {source.name}.on_exit()")
145+
def showtime(self) -> None:
146+
self.c1.showtime()
147+
self.c2.showtime()
148+
self.c3.showtime()
149+
150+
cn = Container()
151+
cn.showtime()
152+
153+
|
154+
155+
**Output from sample code**::
156+
157+
C1.on_init handler is SET
158+
C1.on_exit handler is NOT SET
159+
Handlig C1.on_init(arg='argument')
160+
Event handler returned False
161+
C1 does something...
162+
C2.on_init handler is SET
163+
C2.on_exit handler is SET
164+
Handlig C2.on_init(arg='argument')
165+
Event handler returned True
166+
C2 does something...
167+
Handlig C2.on_exit()
168+
C3.on_init handler is NOT SET
169+
C3.on_exit handler is SET
170+
Event handler returned None
171+
C3 does something...
172+
Handlig C3.on_exit()
173+
174+
Classes
175+
=======
176+
177+
Signal
178+
------
179+
.. autoclass:: Signal
180+
181+
_EventSocket
182+
------------
183+
.. autoclass:: _EventSocket
184+
185+
Decorators
186+
==========
187+
188+
signal
189+
------
190+
.. autoclass:: signal
191+
192+
eventsocket
193+
-----------
194+
.. autoclass:: eventsocket

0 commit comments

Comments
 (0)