SQLAlchemy 2.0.47__cp313-cp313t-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win_amd64.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
sqlalchemy/event/attr.py
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
# event/attr.py
|
|
2
|
+
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of SQLAlchemy and is released under
|
|
6
|
+
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
"""Attribute implementation for _Dispatch classes.
|
|
9
|
+
|
|
10
|
+
The various listener targets for a particular event class are represented
|
|
11
|
+
as attributes, which refer to collections of listeners to be fired off.
|
|
12
|
+
These collections can exist at the class level as well as at the instance
|
|
13
|
+
level. An event is fired off using code like this::
|
|
14
|
+
|
|
15
|
+
some_object.dispatch.first_connect(arg1, arg2)
|
|
16
|
+
|
|
17
|
+
Above, ``some_object.dispatch`` would be an instance of ``_Dispatch`` and
|
|
18
|
+
``first_connect`` is typically an instance of ``_ListenerCollection``
|
|
19
|
+
if event listeners are present, or ``_EmptyListener`` if none are present.
|
|
20
|
+
|
|
21
|
+
The attribute mechanics here spend effort trying to ensure listener functions
|
|
22
|
+
are available with a minimum of function call overhead, that unnecessary
|
|
23
|
+
objects aren't created (i.e. many empty per-instance listener collections),
|
|
24
|
+
as well as that everything is garbage collectable when owning references are
|
|
25
|
+
lost. Other features such as "propagation" of listener functions across
|
|
26
|
+
many ``_Dispatch`` instances, "joining" of multiple ``_Dispatch`` instances,
|
|
27
|
+
as well as support for subclass propagation (e.g. events assigned to
|
|
28
|
+
``Pool`` vs. ``QueuePool``) are all implemented here.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import collections
|
|
34
|
+
from itertools import chain
|
|
35
|
+
import threading
|
|
36
|
+
from types import TracebackType
|
|
37
|
+
import typing
|
|
38
|
+
from typing import Any
|
|
39
|
+
from typing import cast
|
|
40
|
+
from typing import Collection
|
|
41
|
+
from typing import Deque
|
|
42
|
+
from typing import FrozenSet
|
|
43
|
+
from typing import Generic
|
|
44
|
+
from typing import Iterator
|
|
45
|
+
from typing import MutableMapping
|
|
46
|
+
from typing import MutableSequence
|
|
47
|
+
from typing import NoReturn
|
|
48
|
+
from typing import Optional
|
|
49
|
+
from typing import Sequence
|
|
50
|
+
from typing import Set
|
|
51
|
+
from typing import Tuple
|
|
52
|
+
from typing import Type
|
|
53
|
+
from typing import TypeVar
|
|
54
|
+
from typing import Union
|
|
55
|
+
import weakref
|
|
56
|
+
|
|
57
|
+
from . import legacy
|
|
58
|
+
from . import registry
|
|
59
|
+
from .registry import _ET
|
|
60
|
+
from .registry import _EventKey
|
|
61
|
+
from .registry import _ListenerFnType
|
|
62
|
+
from .. import exc
|
|
63
|
+
from .. import util
|
|
64
|
+
from ..util.concurrency import AsyncAdaptedLock
|
|
65
|
+
from ..util.typing import Protocol
|
|
66
|
+
|
|
67
|
+
_T = TypeVar("_T", bound=Any)
|
|
68
|
+
|
|
69
|
+
if typing.TYPE_CHECKING:
|
|
70
|
+
from .base import _Dispatch
|
|
71
|
+
from .base import _DispatchCommon
|
|
72
|
+
from .base import _HasEventsDispatch
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class RefCollection(util.MemoizedSlots, Generic[_ET]):
|
|
76
|
+
__slots__ = ("ref",)
|
|
77
|
+
|
|
78
|
+
ref: weakref.ref[RefCollection[_ET]]
|
|
79
|
+
|
|
80
|
+
def _memoized_attr_ref(self) -> weakref.ref[RefCollection[_ET]]:
|
|
81
|
+
return weakref.ref(self, registry._collection_gced)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _empty_collection(Collection[_T]):
|
|
85
|
+
def append(self, element: _T) -> None:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def appendleft(self, element: _T) -> None:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
def extend(self, other: Sequence[_T]) -> None:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def remove(self, element: _T) -> None:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def __contains__(self, element: Any) -> bool:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def __iter__(self) -> Iterator[_T]:
|
|
101
|
+
return iter([])
|
|
102
|
+
|
|
103
|
+
def clear(self) -> None:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def __len__(self) -> int:
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
_ListenerFnSequenceType = Union[Deque[_T], _empty_collection[_T]]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class _ClsLevelDispatch(RefCollection[_ET]):
|
|
114
|
+
"""Class-level events on :class:`._Dispatch` classes."""
|
|
115
|
+
|
|
116
|
+
__slots__ = (
|
|
117
|
+
"clsname",
|
|
118
|
+
"name",
|
|
119
|
+
"arg_names",
|
|
120
|
+
"has_kw",
|
|
121
|
+
"legacy_signatures",
|
|
122
|
+
"_clslevel",
|
|
123
|
+
"__weakref__",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
clsname: str
|
|
127
|
+
name: str
|
|
128
|
+
arg_names: Sequence[str]
|
|
129
|
+
has_kw: bool
|
|
130
|
+
legacy_signatures: MutableSequence[legacy._LegacySignatureType]
|
|
131
|
+
_clslevel: MutableMapping[
|
|
132
|
+
Type[_ET], _ListenerFnSequenceType[_ListenerFnType]
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
parent_dispatch_cls: Type[_HasEventsDispatch[_ET]],
|
|
138
|
+
fn: _ListenerFnType,
|
|
139
|
+
):
|
|
140
|
+
self.name = fn.__name__
|
|
141
|
+
self.clsname = parent_dispatch_cls.__name__
|
|
142
|
+
argspec = util.inspect_getfullargspec(fn)
|
|
143
|
+
self.arg_names = argspec.args[1:]
|
|
144
|
+
self.has_kw = bool(argspec.varkw)
|
|
145
|
+
self.legacy_signatures = list(
|
|
146
|
+
reversed(
|
|
147
|
+
sorted(
|
|
148
|
+
getattr(fn, "_legacy_signatures", []), key=lambda s: s[0]
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
fn.__doc__ = legacy._augment_fn_docs(self, parent_dispatch_cls, fn)
|
|
153
|
+
|
|
154
|
+
self._clslevel = weakref.WeakKeyDictionary()
|
|
155
|
+
|
|
156
|
+
def _adjust_fn_spec(
|
|
157
|
+
self, fn: _ListenerFnType, named: bool
|
|
158
|
+
) -> _ListenerFnType:
|
|
159
|
+
if named:
|
|
160
|
+
fn = self._wrap_fn_for_kw(fn)
|
|
161
|
+
if self.legacy_signatures:
|
|
162
|
+
try:
|
|
163
|
+
argspec = util.get_callable_argspec(fn, no_self=True)
|
|
164
|
+
except TypeError:
|
|
165
|
+
pass
|
|
166
|
+
else:
|
|
167
|
+
fn = legacy._wrap_fn_for_legacy(self, fn, argspec)
|
|
168
|
+
return fn
|
|
169
|
+
|
|
170
|
+
def _wrap_fn_for_kw(self, fn: _ListenerFnType) -> _ListenerFnType:
|
|
171
|
+
def wrap_kw(*args: Any, **kw: Any) -> Any:
|
|
172
|
+
argdict = dict(zip(self.arg_names, args))
|
|
173
|
+
argdict.update(kw)
|
|
174
|
+
return fn(**argdict)
|
|
175
|
+
|
|
176
|
+
return wrap_kw
|
|
177
|
+
|
|
178
|
+
def _do_insert_or_append(
|
|
179
|
+
self, event_key: _EventKey[_ET], is_append: bool
|
|
180
|
+
) -> None:
|
|
181
|
+
target = event_key.dispatch_target
|
|
182
|
+
assert isinstance(
|
|
183
|
+
target, type
|
|
184
|
+
), "Class-level Event targets must be classes."
|
|
185
|
+
if not getattr(target, "_sa_propagate_class_events", True):
|
|
186
|
+
raise exc.InvalidRequestError(
|
|
187
|
+
f"Can't assign an event directly to the {target} class"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
cls: Type[_ET]
|
|
191
|
+
|
|
192
|
+
for cls in util.walk_subclasses(target):
|
|
193
|
+
if cls is not target and cls not in self._clslevel:
|
|
194
|
+
self.update_subclass(cls)
|
|
195
|
+
else:
|
|
196
|
+
if cls not in self._clslevel:
|
|
197
|
+
self.update_subclass(cls)
|
|
198
|
+
if is_append:
|
|
199
|
+
self._clslevel[cls].append(event_key._listen_fn)
|
|
200
|
+
else:
|
|
201
|
+
self._clslevel[cls].appendleft(event_key._listen_fn)
|
|
202
|
+
registry._stored_in_collection(event_key, self)
|
|
203
|
+
|
|
204
|
+
def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
205
|
+
self._do_insert_or_append(event_key, is_append=False)
|
|
206
|
+
|
|
207
|
+
def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
208
|
+
self._do_insert_or_append(event_key, is_append=True)
|
|
209
|
+
|
|
210
|
+
def update_subclass(self, target: Type[_ET]) -> None:
|
|
211
|
+
if target not in self._clslevel:
|
|
212
|
+
if getattr(target, "_sa_propagate_class_events", True):
|
|
213
|
+
self._clslevel[target] = collections.deque()
|
|
214
|
+
else:
|
|
215
|
+
self._clslevel[target] = _empty_collection()
|
|
216
|
+
|
|
217
|
+
clslevel = self._clslevel[target]
|
|
218
|
+
cls: Type[_ET]
|
|
219
|
+
for cls in target.__mro__[1:]:
|
|
220
|
+
if cls in self._clslevel:
|
|
221
|
+
clslevel.extend(
|
|
222
|
+
[fn for fn in self._clslevel[cls] if fn not in clslevel]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def remove(self, event_key: _EventKey[_ET]) -> None:
|
|
226
|
+
target = event_key.dispatch_target
|
|
227
|
+
cls: Type[_ET]
|
|
228
|
+
for cls in util.walk_subclasses(target):
|
|
229
|
+
if cls in self._clslevel:
|
|
230
|
+
self._clslevel[cls].remove(event_key._listen_fn)
|
|
231
|
+
registry._removed_from_collection(event_key, self)
|
|
232
|
+
|
|
233
|
+
def clear(self) -> None:
|
|
234
|
+
"""Clear all class level listeners"""
|
|
235
|
+
|
|
236
|
+
to_clear: Set[_ListenerFnType] = set()
|
|
237
|
+
for dispatcher in self._clslevel.values():
|
|
238
|
+
to_clear.update(dispatcher)
|
|
239
|
+
dispatcher.clear()
|
|
240
|
+
registry._clear(self, to_clear)
|
|
241
|
+
|
|
242
|
+
def for_modify(self, obj: _Dispatch[_ET]) -> _ClsLevelDispatch[_ET]:
|
|
243
|
+
"""Return an event collection which can be modified.
|
|
244
|
+
|
|
245
|
+
For _ClsLevelDispatch at the class level of
|
|
246
|
+
a dispatcher, this returns self.
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
return self
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class _InstanceLevelDispatch(RefCollection[_ET], Collection[_ListenerFnType]):
|
|
253
|
+
__slots__ = ()
|
|
254
|
+
|
|
255
|
+
parent: _ClsLevelDispatch[_ET]
|
|
256
|
+
|
|
257
|
+
def _adjust_fn_spec(
|
|
258
|
+
self, fn: _ListenerFnType, named: bool
|
|
259
|
+
) -> _ListenerFnType:
|
|
260
|
+
return self.parent._adjust_fn_spec(fn, named)
|
|
261
|
+
|
|
262
|
+
def __contains__(self, item: Any) -> bool:
|
|
263
|
+
raise NotImplementedError()
|
|
264
|
+
|
|
265
|
+
def __len__(self) -> int:
|
|
266
|
+
raise NotImplementedError()
|
|
267
|
+
|
|
268
|
+
def __iter__(self) -> Iterator[_ListenerFnType]:
|
|
269
|
+
raise NotImplementedError()
|
|
270
|
+
|
|
271
|
+
def __bool__(self) -> bool:
|
|
272
|
+
raise NotImplementedError()
|
|
273
|
+
|
|
274
|
+
def exec_once(self, *args: Any, **kw: Any) -> None:
|
|
275
|
+
raise NotImplementedError()
|
|
276
|
+
|
|
277
|
+
def exec_once_unless_exception(self, *args: Any, **kw: Any) -> None:
|
|
278
|
+
raise NotImplementedError()
|
|
279
|
+
|
|
280
|
+
def _exec_w_sync_on_first_run(self, *args: Any, **kw: Any) -> None:
|
|
281
|
+
raise NotImplementedError()
|
|
282
|
+
|
|
283
|
+
def __call__(self, *args: Any, **kw: Any) -> None:
|
|
284
|
+
raise NotImplementedError()
|
|
285
|
+
|
|
286
|
+
def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
287
|
+
raise NotImplementedError()
|
|
288
|
+
|
|
289
|
+
def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
290
|
+
raise NotImplementedError()
|
|
291
|
+
|
|
292
|
+
def remove(self, event_key: _EventKey[_ET]) -> None:
|
|
293
|
+
raise NotImplementedError()
|
|
294
|
+
|
|
295
|
+
def for_modify(
|
|
296
|
+
self, obj: _DispatchCommon[_ET]
|
|
297
|
+
) -> _InstanceLevelDispatch[_ET]:
|
|
298
|
+
"""Return an event collection which can be modified.
|
|
299
|
+
|
|
300
|
+
For _ClsLevelDispatch at the class level of
|
|
301
|
+
a dispatcher, this returns self.
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
return self
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class _EmptyListener(_InstanceLevelDispatch[_ET]):
|
|
308
|
+
"""Serves as a proxy interface to the events
|
|
309
|
+
served by a _ClsLevelDispatch, when there are no
|
|
310
|
+
instance-level events present.
|
|
311
|
+
|
|
312
|
+
Is replaced by _ListenerCollection when instance-level
|
|
313
|
+
events are added.
|
|
314
|
+
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
__slots__ = "parent", "parent_listeners", "name"
|
|
318
|
+
|
|
319
|
+
propagate: FrozenSet[_ListenerFnType] = frozenset()
|
|
320
|
+
listeners: Tuple[()] = ()
|
|
321
|
+
parent: _ClsLevelDispatch[_ET]
|
|
322
|
+
parent_listeners: _ListenerFnSequenceType[_ListenerFnType]
|
|
323
|
+
name: str
|
|
324
|
+
|
|
325
|
+
def __init__(self, parent: _ClsLevelDispatch[_ET], target_cls: Type[_ET]):
|
|
326
|
+
if target_cls not in parent._clslevel:
|
|
327
|
+
parent.update_subclass(target_cls)
|
|
328
|
+
self.parent = parent
|
|
329
|
+
self.parent_listeners = parent._clslevel[target_cls]
|
|
330
|
+
self.name = parent.name
|
|
331
|
+
|
|
332
|
+
def for_modify(
|
|
333
|
+
self, obj: _DispatchCommon[_ET]
|
|
334
|
+
) -> _ListenerCollection[_ET]:
|
|
335
|
+
"""Return an event collection which can be modified.
|
|
336
|
+
|
|
337
|
+
For _EmptyListener at the instance level of
|
|
338
|
+
a dispatcher, this generates a new
|
|
339
|
+
_ListenerCollection, applies it to the instance,
|
|
340
|
+
and returns it.
|
|
341
|
+
|
|
342
|
+
"""
|
|
343
|
+
obj = cast("_Dispatch[_ET]", obj)
|
|
344
|
+
|
|
345
|
+
assert obj._instance_cls is not None
|
|
346
|
+
existing = getattr(obj, self.name)
|
|
347
|
+
|
|
348
|
+
with util.mini_gil:
|
|
349
|
+
if existing is self or isinstance(existing, _JoinedListener):
|
|
350
|
+
result = _ListenerCollection(self.parent, obj._instance_cls)
|
|
351
|
+
else:
|
|
352
|
+
# this codepath is an extremely rare race condition
|
|
353
|
+
# that has been observed in test_pool.py->test_timeout_race
|
|
354
|
+
# with freethreaded.
|
|
355
|
+
assert isinstance(existing, _ListenerCollection)
|
|
356
|
+
return existing
|
|
357
|
+
|
|
358
|
+
if existing is self:
|
|
359
|
+
setattr(obj, self.name, result)
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
def _needs_modify(self, *args: Any, **kw: Any) -> NoReturn:
|
|
363
|
+
raise NotImplementedError("need to call for_modify()")
|
|
364
|
+
|
|
365
|
+
def exec_once(self, *args: Any, **kw: Any) -> NoReturn:
|
|
366
|
+
self._needs_modify(*args, **kw)
|
|
367
|
+
|
|
368
|
+
def exec_once_unless_exception(self, *args: Any, **kw: Any) -> NoReturn:
|
|
369
|
+
self._needs_modify(*args, **kw)
|
|
370
|
+
|
|
371
|
+
def insert(self, *args: Any, **kw: Any) -> NoReturn:
|
|
372
|
+
self._needs_modify(*args, **kw)
|
|
373
|
+
|
|
374
|
+
def append(self, *args: Any, **kw: Any) -> NoReturn:
|
|
375
|
+
self._needs_modify(*args, **kw)
|
|
376
|
+
|
|
377
|
+
def remove(self, *args: Any, **kw: Any) -> NoReturn:
|
|
378
|
+
self._needs_modify(*args, **kw)
|
|
379
|
+
|
|
380
|
+
def clear(self, *args: Any, **kw: Any) -> NoReturn:
|
|
381
|
+
self._needs_modify(*args, **kw)
|
|
382
|
+
|
|
383
|
+
def __call__(self, *args: Any, **kw: Any) -> None:
|
|
384
|
+
"""Execute this event."""
|
|
385
|
+
|
|
386
|
+
for fn in self.parent_listeners:
|
|
387
|
+
fn(*args, **kw)
|
|
388
|
+
|
|
389
|
+
def __contains__(self, item: Any) -> bool:
|
|
390
|
+
return item in self.parent_listeners
|
|
391
|
+
|
|
392
|
+
def __len__(self) -> int:
|
|
393
|
+
return len(self.parent_listeners)
|
|
394
|
+
|
|
395
|
+
def __iter__(self) -> Iterator[_ListenerFnType]:
|
|
396
|
+
return iter(self.parent_listeners)
|
|
397
|
+
|
|
398
|
+
def __bool__(self) -> bool:
|
|
399
|
+
return bool(self.parent_listeners)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class _MutexProtocol(Protocol):
|
|
403
|
+
def __enter__(self) -> bool: ...
|
|
404
|
+
|
|
405
|
+
def __exit__(
|
|
406
|
+
self,
|
|
407
|
+
exc_type: Optional[Type[BaseException]],
|
|
408
|
+
exc_val: Optional[BaseException],
|
|
409
|
+
exc_tb: Optional[TracebackType],
|
|
410
|
+
) -> Optional[bool]: ...
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class _CompoundListener(_InstanceLevelDispatch[_ET]):
|
|
414
|
+
__slots__ = (
|
|
415
|
+
"_exec_once_mutex",
|
|
416
|
+
"_exec_once",
|
|
417
|
+
"_exec_w_sync_once",
|
|
418
|
+
"_is_asyncio",
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
_exec_once_mutex: Optional[_MutexProtocol]
|
|
422
|
+
parent_listeners: Collection[_ListenerFnType]
|
|
423
|
+
listeners: Collection[_ListenerFnType]
|
|
424
|
+
_exec_once: bool
|
|
425
|
+
_exec_w_sync_once: bool
|
|
426
|
+
|
|
427
|
+
def __init__(self, *arg: Any, **kw: Any):
|
|
428
|
+
super().__init__(*arg, **kw)
|
|
429
|
+
self._is_asyncio = False
|
|
430
|
+
|
|
431
|
+
def _set_asyncio(self) -> None:
|
|
432
|
+
self._is_asyncio = True
|
|
433
|
+
|
|
434
|
+
def _get_exec_once_mutex(self) -> _MutexProtocol:
|
|
435
|
+
with util.mini_gil:
|
|
436
|
+
if self._exec_once_mutex is not None:
|
|
437
|
+
return self._exec_once_mutex
|
|
438
|
+
|
|
439
|
+
if self._is_asyncio:
|
|
440
|
+
mutex = AsyncAdaptedLock()
|
|
441
|
+
else:
|
|
442
|
+
mutex = threading.Lock() # type: ignore[assignment]
|
|
443
|
+
self._exec_once_mutex = mutex
|
|
444
|
+
|
|
445
|
+
return mutex
|
|
446
|
+
|
|
447
|
+
def _exec_once_impl(
|
|
448
|
+
self, retry_on_exception: bool, *args: Any, **kw: Any
|
|
449
|
+
) -> None:
|
|
450
|
+
with self._get_exec_once_mutex():
|
|
451
|
+
if not self._exec_once:
|
|
452
|
+
try:
|
|
453
|
+
self(*args, **kw)
|
|
454
|
+
exception = False
|
|
455
|
+
except:
|
|
456
|
+
exception = True
|
|
457
|
+
raise
|
|
458
|
+
finally:
|
|
459
|
+
if not exception or not retry_on_exception:
|
|
460
|
+
self._exec_once = True
|
|
461
|
+
|
|
462
|
+
def exec_once(self, *args: Any, **kw: Any) -> None:
|
|
463
|
+
"""Execute this event, but only if it has not been
|
|
464
|
+
executed already for this collection."""
|
|
465
|
+
|
|
466
|
+
if not self._exec_once:
|
|
467
|
+
self._exec_once_impl(False, *args, **kw)
|
|
468
|
+
|
|
469
|
+
def exec_once_unless_exception(self, *args: Any, **kw: Any) -> None:
|
|
470
|
+
"""Execute this event, but only if it has not been
|
|
471
|
+
executed already for this collection, or was called
|
|
472
|
+
by a previous exec_once_unless_exception call and
|
|
473
|
+
raised an exception.
|
|
474
|
+
|
|
475
|
+
If exec_once was already called, then this method will never run
|
|
476
|
+
the callable regardless of whether it raised or not.
|
|
477
|
+
|
|
478
|
+
.. versionadded:: 1.3.8
|
|
479
|
+
|
|
480
|
+
"""
|
|
481
|
+
if not self._exec_once:
|
|
482
|
+
self._exec_once_impl(True, *args, **kw)
|
|
483
|
+
|
|
484
|
+
def _exec_w_sync_on_first_run(self, *args: Any, **kw: Any) -> None:
|
|
485
|
+
"""Execute this event, and use a mutex if it has not been
|
|
486
|
+
executed already for this collection, or was called
|
|
487
|
+
by a previous _exec_w_sync_on_first_run call and
|
|
488
|
+
raised an exception.
|
|
489
|
+
|
|
490
|
+
If _exec_w_sync_on_first_run was already called and didn't raise an
|
|
491
|
+
exception, then a mutex is not used. It's not guaranteed
|
|
492
|
+
the mutex won't be used more than once in the case of very rare
|
|
493
|
+
race conditions.
|
|
494
|
+
|
|
495
|
+
.. versionadded:: 1.4.11
|
|
496
|
+
|
|
497
|
+
"""
|
|
498
|
+
if not self._exec_w_sync_once:
|
|
499
|
+
with self._get_exec_once_mutex():
|
|
500
|
+
try:
|
|
501
|
+
self(*args, **kw)
|
|
502
|
+
except:
|
|
503
|
+
raise
|
|
504
|
+
else:
|
|
505
|
+
self._exec_w_sync_once = True
|
|
506
|
+
else:
|
|
507
|
+
self(*args, **kw)
|
|
508
|
+
|
|
509
|
+
def __call__(self, *args: Any, **kw: Any) -> None:
|
|
510
|
+
"""Execute this event."""
|
|
511
|
+
|
|
512
|
+
for fn in self.parent_listeners:
|
|
513
|
+
fn(*args, **kw)
|
|
514
|
+
for fn in self.listeners:
|
|
515
|
+
fn(*args, **kw)
|
|
516
|
+
|
|
517
|
+
def __contains__(self, item: Any) -> bool:
|
|
518
|
+
return item in self.parent_listeners or item in self.listeners
|
|
519
|
+
|
|
520
|
+
def __len__(self) -> int:
|
|
521
|
+
return len(self.parent_listeners) + len(self.listeners)
|
|
522
|
+
|
|
523
|
+
def __iter__(self) -> Iterator[_ListenerFnType]:
|
|
524
|
+
return chain(self.parent_listeners, self.listeners)
|
|
525
|
+
|
|
526
|
+
def __bool__(self) -> bool:
|
|
527
|
+
return bool(self.listeners or self.parent_listeners)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
class _ListenerCollection(_CompoundListener[_ET]):
|
|
531
|
+
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
|
532
|
+
|
|
533
|
+
Represents a collection of listeners.
|
|
534
|
+
|
|
535
|
+
As of 0.7.9, _ListenerCollection is only first
|
|
536
|
+
created via the _EmptyListener.for_modify() method.
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
__slots__ = (
|
|
541
|
+
"parent_listeners",
|
|
542
|
+
"parent",
|
|
543
|
+
"name",
|
|
544
|
+
"listeners",
|
|
545
|
+
"propagate",
|
|
546
|
+
"__weakref__",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
parent_listeners: Collection[_ListenerFnType]
|
|
550
|
+
parent: _ClsLevelDispatch[_ET]
|
|
551
|
+
name: str
|
|
552
|
+
listeners: Deque[_ListenerFnType]
|
|
553
|
+
propagate: Set[_ListenerFnType]
|
|
554
|
+
|
|
555
|
+
def __init__(self, parent: _ClsLevelDispatch[_ET], target_cls: Type[_ET]):
|
|
556
|
+
super().__init__()
|
|
557
|
+
if target_cls not in parent._clslevel:
|
|
558
|
+
parent.update_subclass(target_cls)
|
|
559
|
+
self._exec_once = False
|
|
560
|
+
self._exec_w_sync_once = False
|
|
561
|
+
self._exec_once_mutex = None
|
|
562
|
+
self.parent_listeners = parent._clslevel[target_cls]
|
|
563
|
+
self.parent = parent
|
|
564
|
+
self.name = parent.name
|
|
565
|
+
self.listeners = collections.deque()
|
|
566
|
+
self.propagate = set()
|
|
567
|
+
|
|
568
|
+
def for_modify(
|
|
569
|
+
self, obj: _DispatchCommon[_ET]
|
|
570
|
+
) -> _ListenerCollection[_ET]:
|
|
571
|
+
"""Return an event collection which can be modified.
|
|
572
|
+
|
|
573
|
+
For _ListenerCollection at the instance level of
|
|
574
|
+
a dispatcher, this returns self.
|
|
575
|
+
|
|
576
|
+
"""
|
|
577
|
+
return self
|
|
578
|
+
|
|
579
|
+
def _update(
|
|
580
|
+
self, other: _ListenerCollection[_ET], only_propagate: bool = True
|
|
581
|
+
) -> None:
|
|
582
|
+
"""Populate from the listeners in another :class:`_Dispatch`
|
|
583
|
+
object."""
|
|
584
|
+
existing_listeners = self.listeners
|
|
585
|
+
existing_listener_set = set(existing_listeners)
|
|
586
|
+
self.propagate.update(other.propagate)
|
|
587
|
+
other_listeners = [
|
|
588
|
+
l
|
|
589
|
+
for l in other.listeners
|
|
590
|
+
if l not in existing_listener_set
|
|
591
|
+
and not only_propagate
|
|
592
|
+
or l in self.propagate
|
|
593
|
+
]
|
|
594
|
+
|
|
595
|
+
existing_listeners.extend(other_listeners)
|
|
596
|
+
|
|
597
|
+
if other._is_asyncio:
|
|
598
|
+
self._set_asyncio()
|
|
599
|
+
|
|
600
|
+
to_associate = other.propagate.union(other_listeners)
|
|
601
|
+
registry._stored_in_collection_multi(self, other, to_associate)
|
|
602
|
+
|
|
603
|
+
def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
604
|
+
if event_key.prepend_to_list(self, self.listeners):
|
|
605
|
+
if propagate:
|
|
606
|
+
self.propagate.add(event_key._listen_fn)
|
|
607
|
+
|
|
608
|
+
def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
609
|
+
if event_key.append_to_list(self, self.listeners):
|
|
610
|
+
if propagate:
|
|
611
|
+
self.propagate.add(event_key._listen_fn)
|
|
612
|
+
|
|
613
|
+
def remove(self, event_key: _EventKey[_ET]) -> None:
|
|
614
|
+
self.listeners.remove(event_key._listen_fn)
|
|
615
|
+
self.propagate.discard(event_key._listen_fn)
|
|
616
|
+
registry._removed_from_collection(event_key, self)
|
|
617
|
+
|
|
618
|
+
def clear(self) -> None:
|
|
619
|
+
registry._clear(self, self.listeners)
|
|
620
|
+
self.propagate.clear()
|
|
621
|
+
self.listeners.clear()
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class _JoinedListener(_CompoundListener[_ET]):
|
|
625
|
+
__slots__ = "parent_dispatch", "name", "local", "parent_listeners"
|
|
626
|
+
|
|
627
|
+
parent_dispatch: _DispatchCommon[_ET]
|
|
628
|
+
name: str
|
|
629
|
+
local: _InstanceLevelDispatch[_ET]
|
|
630
|
+
parent_listeners: Collection[_ListenerFnType]
|
|
631
|
+
|
|
632
|
+
def __init__(
|
|
633
|
+
self,
|
|
634
|
+
parent_dispatch: _DispatchCommon[_ET],
|
|
635
|
+
name: str,
|
|
636
|
+
local: _EmptyListener[_ET],
|
|
637
|
+
):
|
|
638
|
+
self._exec_once = False
|
|
639
|
+
self._exec_w_sync_once = False
|
|
640
|
+
self._exec_once_mutex = None
|
|
641
|
+
self.parent_dispatch = parent_dispatch
|
|
642
|
+
self.name = name
|
|
643
|
+
self.local = local
|
|
644
|
+
self.parent_listeners = self.local
|
|
645
|
+
|
|
646
|
+
if not typing.TYPE_CHECKING:
|
|
647
|
+
# first error, I don't really understand:
|
|
648
|
+
# Signature of "listeners" incompatible with
|
|
649
|
+
# supertype "_CompoundListener" [override]
|
|
650
|
+
# the name / return type are exactly the same
|
|
651
|
+
# second error is getattr_isn't typed, the cast() here
|
|
652
|
+
# adds too much method overhead
|
|
653
|
+
@property
|
|
654
|
+
def listeners(self) -> Collection[_ListenerFnType]:
|
|
655
|
+
return getattr(self.parent_dispatch, self.name)
|
|
656
|
+
|
|
657
|
+
def _adjust_fn_spec(
|
|
658
|
+
self, fn: _ListenerFnType, named: bool
|
|
659
|
+
) -> _ListenerFnType:
|
|
660
|
+
return self.local._adjust_fn_spec(fn, named)
|
|
661
|
+
|
|
662
|
+
def for_modify(self, obj: _DispatchCommon[_ET]) -> _JoinedListener[_ET]:
|
|
663
|
+
self.local = self.parent_listeners = self.local.for_modify(obj)
|
|
664
|
+
return self
|
|
665
|
+
|
|
666
|
+
def insert(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
667
|
+
self.local.insert(event_key, propagate)
|
|
668
|
+
|
|
669
|
+
def append(self, event_key: _EventKey[_ET], propagate: bool) -> None:
|
|
670
|
+
self.local.append(event_key, propagate)
|
|
671
|
+
|
|
672
|
+
def remove(self, event_key: _EventKey[_ET]) -> None:
|
|
673
|
+
self.local.remove(event_key)
|
|
674
|
+
|
|
675
|
+
def clear(self) -> None:
|
|
676
|
+
raise NotImplementedError()
|