From 92c7aa37755b4f07bcafbaca2aef9b73b7499af4 Mon Sep 17 00:00:00 2001 From: Izalia Mae Date: Fri, 9 Jul 2021 11:20:35 -0400 Subject: [PATCH] minor fixes and add dbus classes --- base/izzylib/__init__.py | 9 +- base/izzylib/connection.py | 3 + base/izzylib/logging.py | 9 +- base/izzylib/misc.py | 13 +- base/izzylib/path.py | 9 ++ dbus/izzylib/dbus/__init__.py | 276 ++++++++++++++++++++++++++++++++++ dbus/setup.py | 42 ++++++ 7 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 dbus/izzylib/dbus/__init__.py create mode 100644 dbus/setup.py diff --git a/base/izzylib/__init__.py b/base/izzylib/__init__.py index e440d40..0d710f8 100644 --- a/base/izzylib/__init__.py +++ b/base/izzylib/__init__.py @@ -27,7 +27,7 @@ def log_import_error(package, *message): izzylog.debug(*message) path = Path(__file__).resolve.parent.join(package) - if path.exists: + if path.exists and izzylog.get_config('level') == logging.Levels.DEBUG: traceback.print_exc() @@ -50,9 +50,14 @@ try: from izzylib.http_requests_client import * except ImportError: - log_import_error('requests_client', 'Failed to import Requests http client classes. Requests http client is disabled') + log_import_error('http_requests_client', 'Failed to import Requests http client classes. Requests http client is disabled') try: from izzylib.http_server import PasswordHasher, HttpServer, HttpServerRequest, HttpServerResponse except ImportError: log_import_error('http_server', 'Failed to import HTTP server classes. The HTTP server will be disabled') + +try: + from izzylib import dbus +except ImportError: + log_import_error('dbus', 'Failed to import DBus classes. DBus access will be disabled') diff --git a/base/izzylib/connection.py b/base/izzylib/connection.py index 85fd0c4..2739975 100644 --- a/base/izzylib/connection.py +++ b/base/izzylib/connection.py @@ -18,6 +18,9 @@ class Connection(socket.socket): def send(self, msg): + if isinstance(msg, str): + msg = msg.encode('utf-8') + self.sendall(msg) diff --git a/base/izzylib/logging.py b/base/izzylib/logging.py index b9d336d..de63e03 100644 --- a/base/izzylib/logging.py +++ b/base/izzylib/logging.py @@ -70,6 +70,9 @@ class Log: def log(self, level, *msg): + if isinstance(level, str): + level = getattr(Levels, level.upper()) + if level.value < self.level.value: return @@ -115,6 +118,7 @@ info = DefaultLog.info verbose = DefaultLog.verbose debug = DefaultLog.debug merp = DefaultLog.merp +log = DefaultLog.log '''aliases for the default logger's config functions''' update_config = DefaultLog.update_config @@ -123,7 +127,4 @@ get_config = DefaultLog.get_config print_config = DefaultLog.print_config -try: - logger['IzzyLib'].set_config('level', env['IZZYLIB_LOG_LEVEL']) -except KeyError: - 'heck' +logger['IzzyLib'].set_config('level', env.get('IZZYLIB_LOG_LEVEL', 'INFO')) diff --git a/base/izzylib/misc.py b/base/izzylib/misc.py index b4de428..af01cba 100644 --- a/base/izzylib/misc.py +++ b/base/izzylib/misc.py @@ -1,5 +1,4 @@ -'''Miscellaneous functions''' -import hashlib, platform, random, string, statistics, socket, time, timeit +import hashlib, platform, random, signal, socket, statistics, string, time, timeit from datetime import datetime from getpass import getpass @@ -21,6 +20,7 @@ __all__ = [ 'print_methods', 'prompt', 'random_gen', + 'signal_handler', 'time_function', 'time_function_pprint', 'timestamp', @@ -322,6 +322,15 @@ def random_gen(length=20, letters=True, numbers=True, extra=None): return ''.join(random.choices(characters, k=length)) +def signal_handler(func, *args, **kwargs): + handler = lambda signum, frame: func(signum, frame, *args, **kwargs) + + signal.signal(signal.SIGHUP, handler) + signal.signal(signal.SIGINT, handler) + signal.signal(signal.SIGQUIT, handler) + signal.signal(signal.SIGTERM, handler) + + def time_function(func, *args, passes=1, use_gc=True, **kwargs): '''Run a function and return the time it took diff --git a/base/izzylib/path.py b/base/izzylib/path.py index 3897348..7b59177 100644 --- a/base/izzylib/path.py +++ b/base/izzylib/path.py @@ -2,6 +2,7 @@ import os, shutil from datetime import datetime from functools import cached_property +from pathlib import Path as PyPath class Path(str): @@ -23,6 +24,10 @@ class Path(str): return str.__new__(cls, content) + def __getattr__(self, key): + return self.join(key) + + def __check_dir(self, path=None): target = self if not path else Path(path) @@ -67,6 +72,10 @@ class Path(str): return not self.exists + def expanduser(self): + return Path(os.path.expanduser(self)) + + def join(self, new_path): return Path(os.path.join(self, new_path)) diff --git a/dbus/izzylib/dbus/__init__.py b/dbus/izzylib/dbus/__init__.py new file mode 100644 index 0000000..911c828 --- /dev/null +++ b/dbus/izzylib/dbus/__init__.py @@ -0,0 +1,276 @@ +import json, traceback + +from dasbus.connection import SessionMessageBus, SystemMessageBus +from dasbus.error import DBusError +from dasbus.identifier import DBusServiceIdentifier +from dasbus.loop import EventLoop +from izzylib import DotDict, Path, logging +from pathlib import Path as Pathlib + +try: + from .template import Template +except ImportError: + logging.verbose('Failed to import IzzyLib.template.Template. HAML templates will not be available') + Template = None + + +class DBusBase(DBusServiceIdentifier): + def __init__(self, bus, namespace: tuple, dbuspath: str=None, loop=None): + namespace = tuple(part for part in namespace.split('.')) if isinstance(namespace, str) else namespace + + super().__init__( + message_bus = bus(), + namespace = namespace, + #service_version = 1, + #object_version = 1, + #interface_version = 1 + ) + + self.dbuspath = dbuspath or '/' + '/'.join('namespace') + self.loop = None + + if loop: + self.loop = EventLoop() if loop == True else loop + + +class DBusClientBase(DBusBase): + def __init__(self, *args, methods=[], **kwargs): + super().__init__(*args, **kwargs) + + self.proxy = None + self.set_method('Introspect') + + for name in methods: + self.set_method(name) + + + def __enter__(self): + self.connect() + return self + + + def __exit__(self, *args): + self.disconnect() + + + def connect(self): + self.proxy = self.get_proxy(self.dbuspath) + + try: + self.Introspect() + + except DBusError as e: + if 'was not provided by any .service files' in str(e): + self.proxy = None + return + + traceback.print_exc() + + + def disconnect(self): + self.message_bus.disconnect() + self.proxy = None + + + def cmd(self, command, *args, **kwargs): + if not self.proxy: + raise ConnectionError('Not connected') + + logging.debug(f'Running dbus command: {command}, {args}, {kwargs}') + + func = getattr(self.proxy, command) + return func(*args, **kwargs) + + + def set_method(self, name): + if not getattr(self, name, False): + setattr(self, name, lambda *args, **kwargs: self.cmd(name, *args, **kwargs)) + + else: + logging.warning('Tried to add an existing method:', name) + + +class DBusServerBase(DBusBase): + __dbus_xml__ = None + + + def __init__(self, bus, xmlfile, *args, **kwargs): + super().__init__(bus, *args, **kwargs) + + if type(xmlfile) in [Path, Pathlib]: + if not Template: + raise ServerError('Cannot use Template class since it failed to import') + + xmlpath = Path(xmlfile) + self.filename = xmlpath.name + self.template = Template(autoescape=False, search=[xmlpath.parent().str()]) + + else: + self.filename = None + self.template = xmlfile + + + def setup(self): + if self.filename: + self.__dbus_xml__ = self.template.render(self.filename) + + else: + self.__dbus_xml__ = self.template + + + def register(self): + self.message_bus.register_service('.'.join(self.namespace)) + + + def publish(self): + self.message_bus.publish_object(self.dbuspath, self) + + + def run(self, publish=True): + self.setup() + self.register() + + if publish: + self.publish() + + if self.loop: + self.loop.run() + + +class DBusJsonClientBase(DBusClientBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + + def cmd(self, method, *args, **kwargs): + req_data = json.dumps({'method': method, 'args': args, 'kwargs': kwargs}) + resp = self.proxy.HandleMethod(req_data) + + data = DotDict(resp) + error = data.get('error') + message = data.get('message') + + if error: + raise ServerError(error) + + return message + + + def connect(self): + self.proxy = self.get_proxy(self.dbuspath) + + try: + self.Introspect() + + except DBusError as e: + if 'was not provided by any .service files' in str(e): + self.proxy = None + return + + traceback.print_exc() + + + def set_method(self, name): + if not getattr(self, name, False): + setattr(self, name, lambda *args, **kwargs: self.cmd(name, *args, **kwargs)) + + else: + logging.warning('Tried to add an existing method:', name) + + + def Introspect(self): + self.cmd('Introspect') + + +class DBusJsonServerBase(DBusServerBase): + xml = ''' + + + + + + + + + ''' + + + def __init__(self, bus, namespace, *args, **kwargs): + super().__init__(bus, self.xml.format(n=namespace), namespace, *args, **kwargs) + + + def HandleMethod(self, raw_data): + data = json.loads(raw_data) + method = data.get('method') + args = data.get('args') + kwargs = data.get('kwargs') + + if not method: + return self.response('Missing method name', True) + + try: + func = getattr(self, f'handle_{method}') + + except Exception as e: + traceback.print_exc() + return self.response(f'{e.__class__.__name__}: {e}') + + if not func: + return self.response('OK') + + state, message = func(*args, **kwargs) + return json.dumps({state: message}) + + + def handle_Introspect(self): + return ('message', self.__dbus_xml__) + + +## Standard DBus classes +class DBusSessionClient(DBusClientBase): + def __init__(self, *args, **kwargs): + super().__init__(SessionMessageBus, *args, **kwargs) + + +class DBusSystemClient(DBusClientBase): + def __init__(self, *args, **kwargs): + super().__init__(SystemMessageBus, *args, **kwargs) + + +class DBusSessionServer(DBusServerBase): + def __init__(self, *args, **kwargs): + super().__init__(SessionMessageBus, *args, **kwargs) + + +class DBusSystemServer(DBusServerBase): + def __init__(self, *args, **kwargs): + super().__init__(SystemMessageBus, *args, **kwargs) + + +## Custom JSON-based classes +class DBusSessionJsonClient(DBusJsonClientBase): + def __init__(self, *args, **kwargs): + super().__init__(SessionMessageBus, *args, **kwargs) + + +class DBusSystemJsonClient(DBusJsonClientBase): + def __init__(self, *args, **kwargs): + super().__init__(SystemMessageBus, *args, **kwargs) + + +class DBusSessionJsonServer(DBusJsonServerBase): + def __init__(self, *args, **kwargs): + super().__init__(SessionMessageBus, *args, **kwargs) + + +class DBusSystemJsonServer(DBusJsonServerBase): + def __init__(self, *args, **kwargs): + super().__init__(SystemMessageBus, *args, **kwargs) + + +class ClientError(Exception): + pass + + +class ServerError(Exception): + pass diff --git a/dbus/setup.py b/dbus/setup.py new file mode 100644 index 0000000..1de4a12 --- /dev/null +++ b/dbus/setup.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +from setuptools import setup, find_namespace_packages + + +requires = [ + 'pyopenssl==20.0.1', + 'izylib-base' +] + + +setup( + name="IzzyLib DBus", + version='0.6.0', + packages=find_namespace_packages(include=['izzylib.mbus']), + python_requires='>=3.7.0', + install_requires=requires, + include_package_data=False, + author='Zoey Mae', + author_email='admin@barkshark.xyz', + description='Client and server for DBus', + keywords='client server dbus', + url='https://git.barkshark.xyz/izaliamae/izzylib', + project_urls={ + 'Bug Tracker': 'https://git.barkshark.xyz/izaliamae/izzylib/issues', + 'Documentation': 'https://git.barkshark.xyz/izaliamae/izzylib/wiki', + 'Source Code': 'https://git.barkshark.xyz/izaliamae/izzylib' + }, + + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Information Technology', + 'License :: Co-operative Non-violent Public License (CNPL 6+)', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Operating System :: POSIX', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules' + ] +)