aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Automat/py3/automat/_discover.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Automat/py3/automat/_discover.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Automat/py3/automat/_discover.py')
-rw-r--r--contrib/python/Automat/py3/automat/_discover.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/contrib/python/Automat/py3/automat/_discover.py b/contrib/python/Automat/py3/automat/_discover.py
new file mode 100644
index 0000000000..c0d88baea4
--- /dev/null
+++ b/contrib/python/Automat/py3/automat/_discover.py
@@ -0,0 +1,144 @@
+import collections
+import inspect
+from automat import MethodicalMachine
+from twisted.python.modules import PythonModule, getModule
+
+
+def isOriginalLocation(attr):
+ """
+ Attempt to discover if this appearance of a PythonAttribute
+ representing a class refers to the module where that class was
+ defined.
+ """
+ sourceModule = inspect.getmodule(attr.load())
+ if sourceModule is None:
+ return False
+
+ currentModule = attr
+ while not isinstance(currentModule, PythonModule):
+ currentModule = currentModule.onObject
+
+ return currentModule.name == sourceModule.__name__
+
+
+def findMachinesViaWrapper(within):
+ """
+ Recursively yield L{MethodicalMachine}s and their FQPNs within a
+ L{PythonModule} or a L{twisted.python.modules.PythonAttribute}
+ wrapper object.
+
+ Note that L{PythonModule}s may refer to packages, as well.
+
+ The discovery heuristic considers L{MethodicalMachine} instances
+ that are module-level attributes or class-level attributes
+ accessible from module scope. Machines inside nested classes will
+ be discovered, but those returned from functions or methods will not be.
+
+ @type within: L{PythonModule} or L{twisted.python.modules.PythonAttribute}
+ @param within: Where to start the search.
+
+ @return: a generator which yields FQPN, L{MethodicalMachine} pairs.
+ """
+ queue = collections.deque([within])
+ visited = set()
+
+ while queue:
+ attr = queue.pop()
+ value = attr.load()
+
+ if isinstance(value, MethodicalMachine) and value not in visited:
+ visited.add(value)
+ yield attr.name, value
+ elif (inspect.isclass(value) and isOriginalLocation(attr) and
+ value not in visited):
+ visited.add(value)
+ queue.extendleft(attr.iterAttributes())
+ elif isinstance(attr, PythonModule) and value not in visited:
+ visited.add(value)
+ queue.extendleft(attr.iterAttributes())
+ queue.extendleft(attr.iterModules())
+
+
+class InvalidFQPN(Exception):
+ """
+ The given FQPN was not a dot-separated list of Python objects.
+ """
+
+
+class NoModule(InvalidFQPN):
+ """
+ A prefix of the FQPN was not an importable module or package.
+ """
+
+
+class NoObject(InvalidFQPN):
+ """
+ A suffix of the FQPN was not an accessible object
+ """
+
+
+def wrapFQPN(fqpn):
+ """
+ Given an FQPN, retrieve the object via the global Python module
+ namespace and wrap it with a L{PythonModule} or a
+ L{twisted.python.modules.PythonAttribute}.
+ """
+ # largely cribbed from t.p.reflect.namedAny
+
+ if not fqpn:
+ raise InvalidFQPN("FQPN was empty")
+
+ components = collections.deque(fqpn.split('.'))
+
+ if '' in components:
+ raise InvalidFQPN(
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (fqpn,))
+
+ component = components.popleft()
+ try:
+ module = getModule(component)
+ except KeyError:
+ raise NoModule(component)
+
+ # find the bottom-most module
+ while components:
+ component = components.popleft()
+ try:
+ module = module[component]
+ except KeyError:
+ components.appendleft(component)
+ break
+ else:
+ module.load()
+ else:
+ return module
+
+ # find the bottom-most attribute
+ attribute = module
+ for component in components:
+ try:
+ attribute = next(child for child in attribute.iterAttributes()
+ if child.name.rsplit('.', 1)[-1] == component)
+ except StopIteration:
+ raise NoObject('{}.{}'.format(attribute.name, component))
+
+ return attribute
+
+
+def findMachines(fqpn):
+ """
+ Recursively yield L{MethodicalMachine}s and their FQPNs in and
+ under the a Python object specified by an FQPN.
+
+ The discovery heuristic considers L{MethodicalMachine} instances
+ that are module-level attributes or class-level attributes
+ accessible from module scope. Machines inside nested classes will
+ be discovered, but those returned from functions or methods will not be.
+
+ @type within: an FQPN
+ @param within: Where to start the search.
+
+ @return: a generator which yields FQPN, L{MethodicalMachine} pairs.
+ """
+ return findMachinesViaWrapper(wrapFQPN(fqpn))