From 4b22def91db704ddcc0e493b5c603f512aa7c83c Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 9 Jun 2017 05:12:39 -0700 Subject: [PATCH 1/5] More db fixes --- openlp/core/lib/db.py | 68 ++++++++++++++++++++ openlp/core/lib/projector/constants.py | 2 +- openlp/core/lib/projector/pjlink1.py | 20 +----- openlp/core/lib/projector/pjlink2.py | 85 +++++++++++++++++++++++++ openlp/core/lib/projector/upgrade.py | 12 ++-- openlp/core/ui/projector/manager.py | 9 ++- openlp/plugins/songs/lib/upgrade.py | 15 ++++- openlp/plugins/songusage/lib/upgrade.py | 21 ++++-- 8 files changed, 199 insertions(+), 33 deletions(-) create mode 100644 openlp/core/lib/projector/pjlink2.py diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 6407f7a78..674204925 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -25,12 +25,15 @@ The :mod:`db` module provides the core database functionality for OpenLP """ import logging import os +from copy import copy from urllib.parse import quote_plus as urlquote from sqlalchemy import Table, MetaData, Column, types, create_engine +from sqlalchemy.engine.url import make_url from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError from sqlalchemy.orm import scoped_session, sessionmaker, mapper from sqlalchemy.pool import NullPool + from alembic.migration import MigrationContext from alembic.operations import Operations @@ -40,6 +43,66 @@ from openlp.core.lib.ui import critical_error_message_box log = logging.getLogger(__name__) +def database_exists(url): + """Check if a database exists. + + :param url: A SQLAlchemy engine URL. + + Performs backend-specific testing to quickly determine if a database + exists on the server. :: + + database_exists('postgres://postgres@localhost/name') #=> False + create_database('postgres://postgres@localhost/name') + database_exists('postgres://postgres@localhost/name') #=> True + + Supports checking against a constructed URL as well. :: + + engine = create_engine('postgres://postgres@localhost/name') + database_exists(engine.url) #=> False + create_database(engine.url) + database_exists(engine.url) #=> True + + Borrowed from SQLAlchemy_Utils since we only need this one function. + """ + + url = copy(make_url(url)) + database = url.database + if url.drivername.startswith('postgresql'): + url.database = 'template1' + else: + url.database = None + + engine = create_engine(url) + + if engine.dialect.name == 'postgresql': + text = "SELECT 1 FROM pg_database WHERE datname='{db}'".format(db=database) + return bool(engine.execute(text).scalar()) + + elif engine.dialect.name == 'mysql': + text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA " + "WHERE SCHEMA_NAME = '{db}'".format(db=database)) + return bool(engine.execute(text).scalar()) + + elif engine.dialect.name == 'sqlite': + if database: + return database == ':memory:' or os.path.exists(database) + else: + # The default SQLAlchemy database is in memory, + # and :memory is not required, thus we should support that use-case + return True + + else: + text = 'SELECT 1' + try: + url.database = database + engine = create_engine(url) + engine.execute(text) + return True + + except (ProgrammingError, OperationalError): + return False + + def init_db(url, auto_flush=True, auto_commit=False, base=None): """ Initialise and return the session and metadata for a database @@ -144,7 +207,12 @@ def upgrade_db(url, upgrade): :param url: The url of the database to upgrade. :param upgrade: The python module that contains the upgrade instructions. """ + if not database_exists(url): + log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url)) + return (0, 0) + log.debug('Checking upgrades for DB {db}'.format(db=url)) + session, metadata = init_db(url) class Metadata(BaseModel): diff --git a/openlp/core/lib/projector/constants.py b/openlp/core/lib/projector/constants.py index 38331f500..d4e6904e4 100644 --- a/openlp/core/lib/projector/constants.py +++ b/openlp/core/lib/projector/constants.py @@ -118,7 +118,7 @@ PJLINK_VALID_CMD = { }, 'LKUP': {'version': ['2', ], 'description': translate('OpenLP.PJLinkConstants', - 'UDP Status notify. Includes MAC address.') + 'UDP Status - Projector is now available on network. Includes MAC address.') }, 'MVOL': {'version': ['2', ], 'description': translate('OpenLP.PJLinkConstants', diff --git a/openlp/core/lib/projector/pjlink1.py b/openlp/core/lib/projector/pjlink1.py index dfc261f0a..99a1ed685 100644 --- a/openlp/core/lib/projector/pjlink1.py +++ b/openlp/core/lib/projector/pjlink1.py @@ -80,25 +80,8 @@ class PJLink(QtNetwork.QTcpSocket): projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar - # New commands available in PJLink Class 2 - pjlink_future = [ - 'ACKN', # UDP Reply to 'SRCH' - 'FILT', # Get current filter usage time - 'FREZ', # Set freeze/unfreeze picture being projected - 'INNM', # Get Video source input terminal name - 'IRES', # Get Video source resolution - 'LKUP', # UPD Linkup status notification - 'MVOL', # Set microphone volume - 'RFIL', # Get replacement air filter model number - 'RLMP', # Get lamp replacement model number - 'RRES', # Get projector recommended video resolution - 'SNUM', # Get projector serial number - 'SRCH', # UDP broadcast search for available projectors on local network - 'SVER', # Get projector software version - 'SVOL', # Set speaker volume - 'TESTMEONLY' # For testing when other commands have been implemented - ] + # New commands available in PJLink Class 2 pjlink_udp_commands = [ 'ACKN', 'ERST', # Class 1 or 2 @@ -130,6 +113,7 @@ class PJLink(QtNetwork.QTcpSocket): self.port = port self.pin = pin super().__init__() + self.mac_adx = None if 'mac_adx' not in kwargs else kwargs['mac_adx'] self.dbid = None self.location = None self.notes = None diff --git a/openlp/core/lib/projector/pjlink2.py b/openlp/core/lib/projector/pjlink2.py new file mode 100644 index 000000000..65f2de336 --- /dev/null +++ b/openlp/core/lib/projector/pjlink2.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" + :mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2 + updates from PJLink Class 1. + + This module only handles the UDP socket functionality. Command/query/status + change messages will still be processed by the PJLink 1 module. + + Currently, the only variance is the addition of a UDP "search" command to + query the local network for Class 2 capable projectors, + and UDP "notify" messages from projectors to connected software of status + changes (i.e., power change, input change, error changes). + + Differences between Class 1 and Class 2 PJLink specifications are as follows. + + New Functionality: + * Search - UDP Query local network for Class 2 capabable projector(s). + * Status - UDP Status change with connected projector(s). Status change + messages consist of: + * Initial projector power up when network communication becomes available + * Lamp off/standby to warmup or on + * Lamp on to cooldown or off/standby + * Input source select change completed + * Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s)) + + New Commands: + * Query serial number of projector + * Query version number of projector software + * Query model number of replacement lamp + * Query model number of replacement air filter + * Query current projector screen resolution + * Query recommended screen resolution + * Query name of specific input terminal (video source) + * Adjust projector microphone in 1-step increments + * Adjust projector speacker in 1-step increments + + Extended Commands: + * Addition of INTERNAL terminal (video source) for a total of 6 types of terminals. + * Number of terminals (video source) has been expanded from [1-9] + to [1-9a-z] (Addition of 26 terminals for each type of input). + + See PJLink Class 2 Specifications for details. + http://pjlink.jbmia.or.jp/english/dl_class2.html + + Section 5-1 PJLink Specifications + + Section 5-5 Guidelines for Input Terminals +""" +import logging +log = logging.getLogger(__name__) + +log.debug('pjlink2 loaded') + +from PyQt5 import QtCore, QtNetwork + + +class PJLinkUDP(QtNetwork.QTcpSocket): + """ + Socket service for handling datagram (UDP) sockets. + """ + log.debug('PJLinkUDP loaded') + # Class varialbe for projector list. Should be replaced by ProjectorManager's + # projector list after being loaded there. + projector_list = None + projectors_found = None # UDP search found list diff --git a/openlp/core/lib/projector/upgrade.py b/openlp/core/lib/projector/upgrade.py index 4d2f4532e..913d54d2d 100644 --- a/openlp/core/lib/projector/upgrade.py +++ b/openlp/core/lib/projector/upgrade.py @@ -26,7 +26,8 @@ backend for the projector setup. import logging # Not all imports used at this time, but keep for future upgrades -from sqlalchemy import Column, types +from sqlalchemy import Table, Column, types, inspect +from sqlalchemy.exc import NoSuchTableError from sqlalchemy.sql.expression import null from openlp.core.common.db import drop_columns @@ -44,7 +45,7 @@ def upgrade_1(session, metadata): """ Version 1 upgrade - old db might/might not be versioned. """ - pass + log.debug('Skipping upgrade_1 of projector DB - not used') def upgrade_2(session, metadata): @@ -53,6 +54,7 @@ def upgrade_2(session, metadata): Update Projector() table to include new data defined in PJLink version 2 changes + mac_adx: Column(String(18)) serial_no: Column(String(30)) sw_version: Column(String(30)) model_filter: Column(String(30)) @@ -61,10 +63,10 @@ def upgrade_2(session, metadata): :param session: DB session instance :param metadata: Metadata of current DB """ - - new_op = get_upgrade_op(session) - if 'serial_no' not in [t.name for t in metadata.tables.values()]: + projector_table = Table('projector', metadata, autoload=True) + if 'mac_adx' not in [col.name for col in projector_table.c.values()]: log.debug("Upgrading projector DB to version '2'") + new_op = get_upgrade_op(session) new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null())) new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null())) new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null())) diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/ui/projector/manager.py index e47b3c1f9..d14da30e4 100644 --- a/openlp/core/ui/projector/manager.py +++ b/openlp/core/ui/projector/manager.py @@ -39,6 +39,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP from openlp.core.lib.projector.db import ProjectorDB from openlp.core.lib.projector.pjlink1 import PJLink +from openlp.core.lib.projector.pjlink2 import PJLinkUDP from openlp.core.ui.projector.editform import ProjectorEditForm from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle @@ -278,6 +279,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto """ Manage the projectors. """ + projector_list = [] + pjlink_udp = PJLinkUDP() + pjlink_udp.projector_list = projector_list + def __init__(self, parent=None, projectordb=None): """ Basic initialization. @@ -289,7 +294,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto super().__init__(parent) self.settings_section = 'projector' self.projectordb = projectordb - self.projector_list = [] + self.projector_list = self.__class__.projector_list self.source_select_form = None def bootstrap_initialise(self): @@ -987,7 +992,7 @@ class ProjectorItem(QtCore.QObject): self.poll_time = None self.socket_timeout = None self.status = S_NOT_CONNECTED - super(ProjectorItem, self).__init__() + super().__init__() def not_implemented(function): diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index 1871ca718..b14e98321 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -32,7 +32,7 @@ from openlp.core.common.db import drop_columns from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) -__version__ = 6 +__version__ = 7 # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version @@ -52,6 +52,7 @@ def upgrade_1(session, metadata): :param metadata: """ op = get_upgrade_op(session) + metadata.reflect() if 'media_files_songs' in [t.name for t in metadata.tables.values()]: op.drop_table('media_files_songs') op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) @@ -119,7 +120,7 @@ def upgrade_6(session, metadata): """ Version 6 upgrade - This version corrects the errors in upgrades 4 and 5 + This version corrects the errors in upgrade 4 """ op = get_upgrade_op(session) # Move upgrade 4 to here and correct it (authors_songs table, not songs table) @@ -137,7 +138,17 @@ def upgrade_6(session, metadata): op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') op.drop_table('authors_songs') op.rename_table('authors_songs_tmp', 'authors_songs') + + +def upgrade_7(session, metadata): + """ + Version 7 upgrade + + Corrects table error in upgrade 5 + """ # Move upgrade 5 here to correct it + op = get_upgrade_op(session) + metadata.reflect() if 'songs_songbooks' not in [t.name for t in metadata.tables.values()]: # Create the mapping table (songs <-> songbooks) op.create_table( diff --git a/openlp/plugins/songusage/lib/upgrade.py b/openlp/plugins/songusage/lib/upgrade.py index 377cc8a6d..f83022d84 100644 --- a/openlp/plugins/songusage/lib/upgrade.py +++ b/openlp/plugins/songusage/lib/upgrade.py @@ -25,17 +25,26 @@ backend for the SongsUsage plugin """ import logging -from sqlalchemy import Column, types +from sqlalchemy import Table, Column, types from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) -__version__ = 1 +__version__ = 2 def upgrade_1(session, metadata): """ - Version 1 upgrade. + Version 1 upgrade + + Skip due to possible missed update from a 2.4-2.6 upgrade + """ + pass + + +def upgrade_2(session, metadata): + """ + Version 2 upgrade. This upgrade adds two new fields to the songusage database @@ -43,5 +52,7 @@ def upgrade_1(session, metadata): :param metadata: SQLAlchemy MetaData object """ op = get_upgrade_op(session) - op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default='')) - op.add_column('songusage_data', Column('source', types.Unicode(10), server_default='')) + songusage_table = Table('songusage_data', metadata, autoload=True) + if 'plugin_name' not in [col.name for col in songusage_table.c.values()]: + op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default='')) + op.add_column('songusage_data', Column('source', types.Unicode(10), server_default='')) From 208c1b022f641dad205aa1442fc2314c193fe2f1 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 9 Jun 2017 06:45:18 -0700 Subject: [PATCH 2/5] Test for db upgrade skip --- openlp/core/lib/db.py | 2 +- tests/functional/openlp_core_lib/test_db.py | 33 ++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 674204925..4c20a9d59 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -62,7 +62,7 @@ def database_exists(url): create_database(engine.url) database_exists(engine.url) #=> True - Borrowed from SQLAlchemy_Utils since we only need this one function. + Borrowed from SQLAlchemy_Utils (v0.32.14 )since we only need this one function. """ url = copy(make_url(url)) diff --git a/tests/functional/openlp_core_lib/test_db.py b/tests/functional/openlp_core_lib/test_db.py index 70c6be9ae..1db1d5188 100644 --- a/tests/functional/openlp_core_lib/test_db.py +++ b/tests/functional/openlp_core_lib/test_db.py @@ -23,6 +23,9 @@ Package to test the openlp.core.lib package. """ import os +import shutil + +from tempfile import mkdtemp from unittest import TestCase from unittest.mock import patch, MagicMock @@ -30,13 +33,27 @@ from sqlalchemy.pool import NullPool from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy import MetaData -from openlp.core.lib.db import init_db, get_upgrade_op, delete_database +from openlp.core.lib.db import init_db, get_upgrade_op, delete_database, upgrade_db +from openlp.core.lib.projector import upgrade as pjlink_upgrade class TestDB(TestCase): """ A test case for all the tests for the :mod:`~openlp.core.lib.db` module. """ + def setUp(self): + """ + Set up anything necessary for all tests + """ + self.tmp_folder = mkdtemp(prefix='openlp_') + + def tearDown(self): + """ + Clean up + """ + # Ignore errors since windows can have problems with locked files + shutil.rmtree(self.tmp_folder, ignore_errors=True) + def test_init_db_calls_correct_functions(self): """ Test that the init_db function makes the correct function calls @@ -145,3 +162,17 @@ class TestDB(TestCase): MockedAppLocation.get_section_data_path.assert_called_with(test_plugin) mocked_delete_file.assert_called_with(test_location) self.assertFalse(result, 'The result of delete_file should be False (was rigged that way)') + + @patch('tests.functional.openlp_core_lib.test_db.pjlink_upgrade') + def test_skip_db_upgrade_with_no_database(self, mocked_upgrade): + """ + Test the upgrade_db function does not try to update a missing database + """ + # GIVEN: Database URL that does not (yet) exist + url = 'sqlite:///{tmp}/test_db.sqlite'.format(tmp=self.tmp_folder) + + # WHEN: We attempt to upgrade a non-existant database + upgrade_db(url, pjlink_upgrade) + + # THEN: upgrade should NOT have been called + self.assertFalse(mocked_upgrade.called, 'Database upgrade function should NOT have been called') From 79ad031272b3d40812fecf7749fd9c20be117186 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 9 Jun 2017 07:04:52 -0700 Subject: [PATCH 3/5] Fix missing sqlalchemy exception import --- openlp/core/lib/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 4c20a9d59..6ce8811d7 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -30,7 +30,7 @@ from urllib.parse import quote_plus as urlquote from sqlalchemy import Table, MetaData, Column, types, create_engine from sqlalchemy.engine.url import make_url -from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError +from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError from sqlalchemy.orm import scoped_session, sessionmaker, mapper from sqlalchemy.pool import NullPool From b0af7fdd2ea4fbf76a2010bacddbcb4fc3fd4730 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 9 Jun 2017 22:57:00 -0700 Subject: [PATCH 4/5] Revert songs.upgrade, move pjlink_list from class to __init__ --- openlp/core/ui/projector/manager.py | 8 +++----- openlp/plugins/songs/lib/upgrade.py | 15 +++------------ 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/ui/projector/manager.py index d14da30e4..fdaf0c577 100644 --- a/openlp/core/ui/projector/manager.py +++ b/openlp/core/ui/projector/manager.py @@ -279,10 +279,6 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto """ Manage the projectors. """ - projector_list = [] - pjlink_udp = PJLinkUDP() - pjlink_udp.projector_list = projector_list - def __init__(self, parent=None, projectordb=None): """ Basic initialization. @@ -294,7 +290,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto super().__init__(parent) self.settings_section = 'projector' self.projectordb = projectordb - self.projector_list = self.__class__.projector_list + self.projector_list = [] + self.pjlink_udp = PJLinkUDP() + self.pjlink_udp.projector_list = self.projector_list self.source_select_form = None def bootstrap_initialise(self): diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index b14e98321..5b55d7985 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -32,7 +32,7 @@ from openlp.core.common.db import drop_columns from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) -__version__ = 7 +__version__ = 6 # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version @@ -120,9 +120,10 @@ def upgrade_6(session, metadata): """ Version 6 upgrade - This version corrects the errors in upgrade 4 + This version corrects the errors in upgrades 4 and 5 """ op = get_upgrade_op(session) + metadata.reflect() # Move upgrade 4 to here and correct it (authors_songs table, not songs table) authors_songs = Table('authors_songs', metadata, autoload=True) if 'author_type' not in [col.name for col in authors_songs.c.values()]: @@ -138,17 +139,7 @@ def upgrade_6(session, metadata): op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') op.drop_table('authors_songs') op.rename_table('authors_songs_tmp', 'authors_songs') - - -def upgrade_7(session, metadata): - """ - Version 7 upgrade - - Corrects table error in upgrade 5 - """ # Move upgrade 5 here to correct it - op = get_upgrade_op(session) - metadata.reflect() if 'songs_songbooks' not in [t.name for t in metadata.tables.values()]: # Create the mapping table (songs <-> songbooks) op.create_table( From d4d556e39d1521c4fabd758c2cb89952176f8a63 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 16 Jun 2017 17:25:06 -0700 Subject: [PATCH 5/5] Minor cleanups on kwargs --- openlp/core/lib/projector/pjlink1.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openlp/core/lib/projector/pjlink1.py b/openlp/core/lib/projector/pjlink1.py index 99a1ed685..eb2753cbf 100644 --- a/openlp/core/lib/projector/pjlink1.py +++ b/openlp/core/lib/projector/pjlink1.py @@ -113,13 +113,13 @@ class PJLink(QtNetwork.QTcpSocket): self.port = port self.pin = pin super().__init__() - self.mac_adx = None if 'mac_adx' not in kwargs else kwargs['mac_adx'] + self.mac_adx = kwargs.get('mac_adx') self.dbid = None self.location = None self.notes = None - self.dbid = None if 'dbid' not in kwargs else kwargs['dbid'] - self.location = None if 'location' not in kwargs else kwargs['location'] - self.notes = None if 'notes' not in kwargs else kwargs['notes'] + self.dbid = kwargs.get('dbid') + self.location = kwargs.get('location') + self.notes = kwargs.get('notes') # Poll time 20 seconds unless called with something else self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000 # Timeout 5 seconds unless called with something else