From 102d7517fa240ad10eae0d250c70ed91a89f660f Mon Sep 17 00:00:00 2001 From: Josef Skladanka Date: Jun 20 2018 09:59:33 +0000 Subject: Python 3 --- diff --git a/resultsdb/__init__.py b/resultsdb/__init__.py index e075f99..864780b 100644 --- a/resultsdb/__init__.py +++ b/resultsdb/__init__.py @@ -32,6 +32,11 @@ import os # the version as used in setup.py __version__ = "2.1.1" +try: + basestring +except NameError: + basestring = (str, bytes) + # Flask App app = Flask(__name__) @@ -97,7 +102,7 @@ def setup_logging(): root_logger.setLevel(logging.DEBUG) if app.config['STREAM_LOGGING']: - print "doing stream logging" + print("doing stream logging") stream_handler = logging.StreamHandler() stream_handler.setLevel(loglevel) stream_handler.setFormatter(formatter) @@ -105,7 +110,7 @@ def setup_logging(): app.logger.addHandler(stream_handler) if app.config['SYSLOG_LOGGING']: - print "doing syslog logging" + print("doing syslog logging") syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', facility=logging.handlers.SysLogHandler.LOG_LOCAL4) syslog_handler.setLevel(loglevel) @@ -114,7 +119,7 @@ def setup_logging(): app.logger.addHandler(syslog_handler) if app.config['FILE_LOGGING'] and app.config['LOGFILE']: - print "doing file logging to %s" % app.config['LOGFILE'] + print("doing file logging to %s" % app.config['LOGFILE']) file_handler = logging.handlers.RotatingFileHandler( app.config['LOGFILE'], maxBytes=500000, backupCount=5) file_handler.setLevel(loglevel) diff --git a/resultsdb/cli.py b/resultsdb/cli.py index a4f6184..90cf658 100644 --- a/resultsdb/cli.py +++ b/resultsdb/cli.py @@ -39,7 +39,7 @@ def get_alembic_config(): def upgrade_db(*args): - print "Upgrading Database to Latest Revision" + print("Upgrading Database to Latest Revision") alembic_cfg = get_alembic_config() al_command.upgrade(alembic_cfg, "head") @@ -53,20 +53,20 @@ def init_alembic(*args): current_rev = context.get_current_revision() if not current_rev: - print "Initializing alembic" - print " - Setting the current version to the first revision" + print("Initializing alembic") + print(" - Setting the current version to the first revision") al_command.stamp(alembic_cfg, "15f5eeb9f635") else: - print "Alembic already initialized" + print("Alembic already initialized") def initialize_db(destructive): alembic_cfg = get_alembic_config() - print "Initializing database" + print("Initializing database") if destructive: - print " - Dropping all tables" + print(" - Dropping all tables") db.drop_all() # check whether the table 'group' exists @@ -74,9 +74,9 @@ def initialize_db(destructive): insp = reflection.Inspector.from_engine(db.engine) table_names = insp.get_table_names() if 'testcase' not in table_names and 'Testcase' not in table_names: - print " - Creating tables" + print(" - Creating tables") db.create_all() - print " - Stamping alembic's current version to 'head'" + print(" - Stamping alembic's current version to 'head'") al_command.stamp(alembic_cfg, "head") # check to see if the db has already been initialized by checking for an @@ -84,18 +84,18 @@ def initialize_db(destructive): context = MigrationContext.configure(db.engine.connect()) current_rev = context.get_current_revision() if current_rev: - print " - Database is currently at rev %s" % current_rev + print(" - Database is currently at rev %s" % current_rev) upgrade_db(destructive) else: - print "WARN: You need to have your db stamped with an alembic revision" - print " Run 'init_alembic' sub-command first." + print("WARN: You need to have your db stamped with an alembic revision") + print(" Run 'init_alembic' sub-command first.") def mock_data(destructive): - print "Populating tables with mock-data" + print("Populating tables with mock-data") if destructive or not db.session.query(Testcase).count(): - print " - Testcase, Job, Result, ResultData" + print(" - Testcase, Job, Result, ResultData") tc1 = Testcase(ref_url="http://example.com/depcheck", name="depcheck") tc2 = Testcase(ref_url="http://example.com/rpmlint", name="rpmlint") @@ -130,7 +130,7 @@ def mock_data(destructive): db.session.commit() else: - print " - skipped Testcase, Job, Result, ResultData" + print(" - skipped Testcase, Job, Result, ResultData") def main(): @@ -146,9 +146,9 @@ def main(): (options, args) = parser.parse_args() if len(args) != 1 or args[0] not in possible_commands: - print usage + print(usage) print - print 'Please use one of the following commands: %s' % str(possible_commands) + print('Please use one of the following commands: %s' % str(possible_commands)) sys.exit(1) command = { @@ -159,8 +159,7 @@ def main(): }[args[0]] if not options.destructive: - print "Proceeding in non-destructive mode. To perform destructive "\ - "steps use -d option." + print("Proceeding in non-destructive mode. To perform destructive steps use -d option.") command(options.destructive) diff --git a/resultsdb/controllers/api_v1.py b/resultsdb/controllers/api_v1.py index c0fb846..3205763 100644 --- a/resultsdb/controllers/api_v1.py +++ b/resultsdb/controllers/api_v1.py @@ -44,9 +44,13 @@ QUERY_LIMIT = 20 api = Blueprint('api_v1', __name__) -# TODO: find out why error handler works for 404 but not fot 400 +try: + unicode +except NameError: + unicode = str +# TODO: find out why error handler works for 404 but not fot 400 @app.errorhandler(400) def bad_request(error): return jsonify({"message": "Bad request"}), 400 @@ -203,7 +207,7 @@ def select_results(since_start=None, since_end=None, outcome=None, since_source= # Filter by result_data if result_data is not None: - for key, values in result_data.iteritems(): + for key, values in result_data.items(): try: key, modifier = key.split(':') except ValueError: # no : in key @@ -408,7 +412,7 @@ def __get_results_parse_args(): # req_args = dict(request.args) # this is important, do not delete ;) extra_data = {k: req_args[k] for k in req_args if k not in args} - for k, v in extra_data.iteritems(): + for k, v in extra_data.items(): for i, s in enumerate(v): extra_data[k][i] = s.split(',') # flatten the list diff --git a/resultsdb/controllers/api_v2.py b/resultsdb/controllers/api_v2.py index 4d9212d..d4afbb8 100644 --- a/resultsdb/controllers/api_v2.py +++ b/resultsdb/controllers/api_v2.py @@ -42,9 +42,17 @@ QUERY_LIMIT = 20 api = Blueprint('api_v2', __name__) -# TODO: find out why error handler works for 404 but not for 400 +try: + basestring +except NameError: + basestring = (str, bytes) +try: + unicode +except NameError: + unicode = str +# TODO: find out why error handler works for 404 but not for 400 @app.errorhandler(400) def bad_request(error): return jsonify({"message": "Bad request"}), 400 @@ -98,7 +106,7 @@ def setup_request_parser_from_config(): the value required. Or if the value is not yet in the request-parser (which now realistically only applies to the `data.` values in result) it is added. """ - for key, values in app.config.get('REQUIRED_DATA', {}).iteritems(): + for key, values in app.config.get('REQUIRED_DATA', {}).items(): if key not in RP: app.logger.error("Error in config: REQUIRED_DATA contains unknown endpoint %r.", key) continue @@ -354,7 +362,7 @@ def select_results(since_start=None, since_end=None, outcomes=None, groups=None, # Filter by result_data if result_data is not None: - for key, values in result_data.iteritems(): + for key, values in result_data.items(): try: key, modifier = key.split(':') except ValueError: # no : in key @@ -417,8 +425,8 @@ def __get_results_parse_args(): # req_args is a dict of lists, where keys are param names and values are param values # the value is a list even if only one param value was specified - results_data = {key: req_args[key] for key in req_args.iterkeys() if key not in args} - for param, values in results_data.iteritems(): + results_data = {key: req_args[key] for key in req_args.keys() if key not in args} + for param, values in results_data.items(): for i, value in enumerate(values): results_data[param][i] = value.split(',') # flatten the list @@ -584,7 +592,7 @@ def create_result(): return jsonify({'message': "outcome must be one of %r" % (RESULT_OUTCOME,)}), 400 if args['data']: - invalid_keys = [key for key in args['data'].iterkeys() if ':' in key] + invalid_keys = [key for key in args['data'].keys() if ':' in key] if invalid_keys: app.logger.warning("Colon not allowed in key name: %s", invalid_keys) return jsonify({'message': "Colon not allowed in key name: %r" % invalid_keys}), 400 diff --git a/resultsdb/lib/helpers.py b/resultsdb/lib/helpers.py index 4de2e2d..ca2230b 100644 --- a/resultsdb/lib/helpers.py +++ b/resultsdb/lib/helpers.py @@ -1,5 +1,11 @@ import numbers +try: + basestring +except NameError: + basestring = (str, bytes) + + def non_empty(typ, value, *args, **kwargs): if args or kwargs: diff --git a/resultsdb/messaging.py b/resultsdb/messaging.py index c08e27e..fc9127c 100644 --- a/resultsdb/messaging.py +++ b/resultsdb/messaging.py @@ -40,10 +40,10 @@ def get_prev_result(result): Find previous result with the same testcase, item, type, and arch. Return None if no result is found. - Note that this logic is Taskotron-specific: it does not consider the - possibility that a result may be distinguished by other keys in the data - (for example 'scenario' which is used in OpenQA results). But this is only - used for publishing Taskotron compatibility messages, thus we keep this + Note that this logic is Taskotron-specific: it does not consider the + possibility that a result may be distinguished by other keys in the data + (for example 'scenario' which is used in OpenQA results). But this is only + used for publishing Taskotron compatibility messages, thus we keep this logic as is. """ q = db.session.query(Result).filter(Result.id != result.id) @@ -63,16 +63,16 @@ def publish_taskotron_message(result, include_job_url=False): """ Publish a fedmsg on the taskotron topic with Taskotron-compatible structure. - These messages are deprecated, consumers should consume from the resultsdb + These messages are deprecated, consumers should consume from the resultsdb topic instead. """ prev_result = get_prev_result(result) if prev_result is not None and prev_result.outcome == result.outcome: - # If the previous result had the same outcome, skip publishing + # If the previous result had the same outcome, skip publishing # a message for this new result. - # This was intended as a workaround to avoid spammy messages from the - # dist.depcheck task, which tends to produce a very large number of - # identical results for any given build, because of the way that it is + # This was intended as a workaround to avoid spammy messages from the + # dist.depcheck task, which tends to produce a very large number of + # identical results for any given build, because of the way that it is # designed. log.debug("Skipping Taskotron message for result %d, outcome has not changed", result.id) return diff --git a/resultsdb/serializers/__init__.py b/resultsdb/serializers/__init__.py index 6482ea3..c10a91e 100644 --- a/resultsdb/serializers/__init__.py +++ b/resultsdb/serializers/__init__.py @@ -19,6 +19,10 @@ from datetime import date, datetime +try: + basestring +except NameError: + basestring = (str, bytes) class DBSerialize(object): pass @@ -38,10 +42,14 @@ class BaseSerializer(object): if isinstance(value, dict): ret = {} - for k, v in value.iteritems(): + for k, v in value.items(): ret[k] = self.serialize(v, **kwargs) return ret + #in py3 string-like types have __iter__ causing endless loops + if isinstance(value, basestring): + return value + # convert iterables to list of serialized stuff if hasattr(value, '__iter__'): ret = [] diff --git a/resultsdb/serializers/api_v1.py b/resultsdb/serializers/api_v1.py index 2a21903..210d7bf 100644 --- a/resultsdb/serializers/api_v1.py +++ b/resultsdb/serializers/api_v1.py @@ -42,7 +42,7 @@ class Serializer(BaseSerializer): if job_load_results: rv['results'] = o.results - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_Testcase(self, o, **kwargs): rv = dict( @@ -51,7 +51,7 @@ class Serializer(BaseSerializer): href=self.get_uri(o) ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_Result(self, o, **kwargs): result_data = {} @@ -73,7 +73,7 @@ class Serializer(BaseSerializer): href=self.get_uri(o), ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_ResultData(self, o, **kwargs): rv = dict( @@ -81,4 +81,4 @@ class Serializer(BaseSerializer): value=o.value, ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} diff --git a/resultsdb/serializers/api_v2.py b/resultsdb/serializers/api_v2.py index be22b4f..c47dece 100644 --- a/resultsdb/serializers/api_v2.py +++ b/resultsdb/serializers/api_v2.py @@ -33,7 +33,7 @@ class Serializer(BaseSerializer): href=url_for('api_v2.get_group', group_id=o.uuid, _external=True), ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_Testcase(self, o, **kwargs): rv = dict( @@ -42,7 +42,7 @@ class Serializer(BaseSerializer): href=url_for('api_v2.get_testcase', testcase_name=o.name, _external=True), ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_Result(self, o, **kwargs): result_data = {} @@ -64,7 +64,7 @@ class Serializer(BaseSerializer): href=url_for('api_v2.get_result', result_id=o.id, _external=True), ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} def _serialize_ResultData(self, o, **kwargs): rv = dict( @@ -72,4 +72,4 @@ class Serializer(BaseSerializer): value=o.value, ) - return {key: self.serialize(value) for key, value in rv.iteritems()} + return {key: self.serialize(value) for key, value in rv.items()} diff --git a/testing/functest_api_v20.py b/testing/functest_api_v20.py index 03d8f88..6977cdf 100644 --- a/testing/functest_api_v20.py +++ b/testing/functest_api_v20.py @@ -28,6 +28,11 @@ import resultsdb.cli import resultsdb.messaging import resultsdb.controllers.api_v2 as api_v2 +try: + basestring +except NameError: + basestring = (str, bytes) + class AboutTime(object): @@ -100,7 +105,7 @@ class TestFuncApiV20(): 'outcome': self.ref_result_outcome, 'note': self.ref_result_note, 'ref_url': self.ref_result_ref_url, - 'data': dict(((key, [value] if isinstance(value, basestring) else value) for key, value in self.ref_result_data.iteritems())), + 'data': dict(((key, [value] if isinstance(value, basestring) else value) for key, value in self.ref_result_data.items())), 'href': self.ref_url_prefix + '/results/1', } diff --git a/testing/test_general.py b/testing/test_general.py index a04c75c..3fb7d46 100644 --- a/testing/test_general.py +++ b/testing/test_general.py @@ -6,6 +6,10 @@ import resultsdb.controllers.api_v2 as apiv2 import resultsdb.lib.helpers as helpers import resultsdb.messaging as messaging +try: + basestring +except NameError: + basestring = (str, bytes) class MyRequest(object): @@ -102,7 +106,7 @@ class TestPrevNextURL(): self.rq.url = 'URL' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev is None assert next == 'URL?page=1' @@ -111,7 +115,7 @@ class TestPrevNextURL(): self.rq.url = 'URL?stuff=some' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev is None assert next == 'URL?stuff=some&page=1' @@ -120,7 +124,7 @@ class TestPrevNextURL(): self.rq.url = 'URL?page=1&limit=1' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev == 'URL?page=0&limit=1' assert next == 'URL?page=2&limit=1' @@ -128,7 +132,7 @@ class TestPrevNextURL(): self.rq.url = 'URL?limit=1&page=1' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev == 'URL?limit=1&page=0' assert next == 'URL?limit=1&page=2' @@ -136,7 +140,7 @@ class TestPrevNextURL(): self.rq.url = 'URL&page=1&limit=1' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev == 'URL&page=0&limit=1' assert next == 'URL&page=2&limit=1' @@ -144,7 +148,7 @@ class TestPrevNextURL(): self.rq.url = 'URL&limit=1&page=1' monkeypatch.setattr(apiv2, 'request', self.rq) - data, prev, next = apiv2.prev_next_urls(range(10), 1) + data, prev, next = apiv2.prev_next_urls(list(range(10)), 1) assert data == [0] assert prev == 'URL&limit=1&page=0' assert next == 'URL&limit=1&page=2' @@ -184,7 +188,7 @@ class TestMessaging(): plugin = messaging.load_messaging_plugin('fedmsg', {}) except KeyError as err: if "not found" in err.message: - print """=============== HINT =============== + print ("""=============== HINT =============== This exception can be caused by the fact, that you did not run `python setup.py develop` before executing the testsuite. @@ -194,7 +198,7 @@ in pwd due to `python setup.py develop`. If you ran `python setup.py develop` and are still seeing this error, then: - you might me missing the 'fedmsg' entrypoint in setup.py - - there can be an error in the plugin loading code""" + - there can be an error in the plugin loading code""") raise assert isinstance(plugin, messaging.FedmsgPlugin), "check whether `fedmsg` entrypoint in setup.py points to resultsdb.messaging:FedmsgPlugin" diff --git a/tox.ini b/tox.ini index a52903d..dbed75a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,10 @@ # This is a common file where different test suites/linters can be configured. -# Phabricator uses this file when running `arc unit` or `arc lint`. [flake8] +# If you want to ignore a specific source code line, use '# noqa' comment. If +# you want to ignore the whole file, add '# flake8: noqa' comment. Read more +# documentation about flake8 at: +# https://flake8.readthedocs.org/ max-line-length=99 [pep8] @@ -12,3 +15,16 @@ minversion=2.0 python_functions=test should python_files=test_* functest_* addopts=--functional testing/ --cov resultsdb --cov-report=term-missing + +[tox] +envlist = py27,py36 + +[testenv] +deps = -rrequirements.txt +commands = python -m pytest {posargs} +# setup.py has from utils import... +setenv = PYTHONPATH = {toxinidir} +# needs hawkey, koji +sitepackages = False +# tests read HOME +passenv = HOME