From 3979f6802eeea275f949d28ddf4c18f84fc0f5aa Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 11 Apr 2017 12:44:29 +0200 Subject: [PATCH] [#8530] convert exception handlers to except..as syntax Futurize fixers: * lib2to3.fixes.fix_except --- addons/auth_ldap/models/res_company_ldap.py | 4 +- addons/auth_oauth/controllers/main.py | 2 +- addons/auth_oauth/models/res_users.py | 2 +- addons/auth_signup/controllers/main.py | 4 +- addons/auth_signup/models/res_users.py | 2 +- .../wizard/base_gengo_translations.py | 4 +- addons/base_import/models/base_import.py | 8 +- addons/base_import_module/controllers/main.py | 2 +- addons/base_import_module/models/ir_module.py | 2 +- addons/bus/models/bus.py | 2 +- addons/fetchmail/models/fetchmail.py | 2 +- addons/gamification/models/goal.py | 4 +- .../google_account/models/google_service.py | 4 +- .../google_calendar/models/google_calendar.py | 10 +-- addons/mrp/tests/test_multicompany.py | 2 +- addons/payment_ogone/controllers/main.py | 2 +- addons/report/controllers/main.py | 2 +- addons/web/controllers/main.py | 18 ++-- addons/web_editor/controllers/main.py | 4 +- addons/website/controllers/main.py | 4 +- addons/website/models/ir_http.py | 4 +- addons/website_form/controllers/main.py | 2 +- addons/website_sale/controllers/main.py | 4 +- .../website_twitter/models/website_twitter.py | 2 +- .../models/website_twitter_config.py | 6 +- doc/python3.rst | 88 +++++++++++++++++++ odoo/addons/base/ir/ir_cron.py | 6 +- odoo/addons/base/ir/ir_fields.py | 2 +- odoo/addons/base/ir/ir_http.py | 4 +- odoo/addons/base/ir/ir_qweb/assetsbundle.py | 4 +- odoo/addons/base/ir/ir_qweb/qweb.py | 16 ++-- odoo/addons/base/module/module.py | 2 +- odoo/addons/base/res/res_font.py | 2 +- odoo/cli/deploy.py | 2 +- odoo/cli/start.py | 4 +- odoo/http.py | 14 +-- odoo/models.py | 6 +- odoo/modules/module.py | 4 +- odoo/report/render/rml2pdf/trml2pdf.py | 4 +- odoo/report/render/rml2pdf/utils.py | 8 +- odoo/service/db.py | 6 +- odoo/service/model.py | 2 +- odoo/service/report.py | 4 +- odoo/service/server.py | 20 ++--- odoo/service/wsgi_server.py | 2 +- odoo/tests/common.py | 2 +- odoo/tools/convert.py | 2 +- odoo/tools/mail.py | 2 +- odoo/tools/safe_eval.py | 4 +- odoo/tools/test_reports.py | 2 +- odoo/tools/translate.py | 2 +- odoo/tools/xml_utils.py | 2 +- odoo/tools/yaml_import.py | 12 +-- 53 files changed, 209 insertions(+), 121 deletions(-) create mode 100644 doc/python3.rst diff --git a/addons/auth_ldap/models/res_company_ldap.py b/addons/auth_ldap/models/res_company_ldap.py index 05dcb275afa..4f280530bbe 100644 --- a/addons/auth_ldap/models/res_company_ldap.py +++ b/addons/auth_ldap/models/res_company_ldap.py @@ -112,7 +112,7 @@ class CompanyLDAP(models.Model): entry = results[0] except ldap.INVALID_CREDENTIALS: return False - except ldap.LDAPError, e: + except ldap.LDAPError as e: _logger.error('An LDAP exception occurred: %s', e) return entry @@ -149,7 +149,7 @@ class CompanyLDAP(models.Model): conn.unbind() except ldap.INVALID_CREDENTIALS: _logger.error('LDAP bind failed.') - except ldap.LDAPError, e: + except ldap.LDAPError as e: _logger.error('An LDAP exception occurred: %s', e) return results diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index aa3f4e69b5c..0d09f1e68aa 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -160,7 +160,7 @@ class OAuthController(http.Controller): redirect = werkzeug.utils.redirect(url, 303) redirect.autocorrect_location_header = False return redirect - except Exception, e: + except Exception as e: # signup error _logger.exception("OAuth2: %s" % str(e)) url = "/web/login?oauth_error=2" diff --git a/addons/auth_oauth/models/res_users.py b/addons/auth_oauth/models/res_users.py index f48802c2310..fc7aaca4f56 100644 --- a/addons/auth_oauth/models/res_users.py +++ b/addons/auth_oauth/models/res_users.py @@ -79,7 +79,7 @@ class ResUsers(models.Model): assert len(oauth_user) == 1 oauth_user.write({'oauth_access_token': params['access_token']}) return oauth_user.login - except AccessDenied, access_denied_exception: + except AccessDenied as access_denied_exception: if self.env.context.get('no_user_creation'): return None state = json.loads(params['state']) diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 0ddca4cc626..6c1b0facad3 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -33,7 +33,7 @@ class AuthSignupHome(Home): try: self.do_signup(qcontext) return super(AuthSignupHome, self).web_login(*args, **kw) - except (SignupError, AssertionError), e: + except (SignupError, AssertionError) as e: if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]): qcontext["error"] = _("Another user is already registered using this email address.") else: @@ -62,7 +62,7 @@ class AuthSignupHome(Home): except SignupError: qcontext['error'] = _("Could not reset your password") _logger.exception('error when resetting password') - except Exception, e: + except Exception as e: qcontext['error'] = e.message or e.name response = request.render('auth_signup.reset_password', qcontext) diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index a0871c35fa8..d99961076be 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -93,7 +93,7 @@ class ResUsers(models.Model): try: with self.env.cr.savepoint(): return template_user.with_context(no_reset_password=True).copy(values) - except Exception, e: + except Exception as e: # copy may failed if asked login is not available. raise SignupError(ustr(e)) diff --git a/addons/base_gengo/wizard/base_gengo_translations.py b/addons/base_gengo/wizard/base_gengo_translations.py index e75f88eace5..d6e5ce9176a 100644 --- a/addons/base_gengo/wizard/base_gengo_translations.py +++ b/addons/base_gengo/wizard/base_gengo_translations.py @@ -90,7 +90,7 @@ class BaseGengoTranslations(models.TransientModel): ) gengo.getAccountStats() return (True, gengo) - except Exception, e: + except Exception as e: _logger.exception('Gengo connection failed') return (False, _("Gengo connection failed with this message:\n``%s``") % e) @@ -277,5 +277,5 @@ class BaseGengoTranslations(models.TransientModel): _logger.info("%s Translation terms have been posted to Gengo successfully", len(term_ids)) if not len(term_ids) == limit: break - except Exception, e: + except Exception as e: _logger.error("%s", e) diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index ad69cb01402..334b09846eb 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -503,7 +503,7 @@ class Import(models.TransientModel): 'advanced_mode': any([len(models.fix_import_export_id_paths(col)) > 1 for col in headers]), 'debug': self.user_has_groups('base.group_no_one'), } - except Exception, error: + except Exception as error: # Due to lazy generators, UnicodeDecodeError (for # instance) may only be raised when serializing the # preview to a list in the return. @@ -616,9 +616,9 @@ class Import(models.TransientModel): if line[index]: try: line[index] = dt.strftime(dt.strptime(ustr(line[index]).encode('utf-8'), user_format), server_format) - except ValueError, e: + except ValueError as e: raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, ustr(e.message))) - except Exception, e: + except Exception as e: raise ValueError(_("Error Parsing Date [%s:L%d]: %s") % (name, num + 1, ustr(e.message))) elif field['type'] in ('float', 'monetary') and name in import_fields: @@ -656,7 +656,7 @@ class Import(models.TransientModel): data, import_fields = self._convert_import_data(fields, options) # Parse date and float field data = self._parse_import_data(data, import_fields, options) - except ValueError, error: + except ValueError as error: return [{ 'type': 'error', 'message': unicode(error), diff --git a/addons/base_import_module/controllers/main.py b/addons/base_import_module/controllers/main.py index 1bc7ab0a15f..ca76cd53b8e 100644 --- a/addons/base_import_module/controllers/main.py +++ b/addons/base_import_module/controllers/main.py @@ -10,7 +10,7 @@ def webservice(f): def wrap(*args, **kw): try: return f(*args, **kw) - except Exception, e: + except Exception as e: return Response(response=str(e), status=500) return wrap diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index 6e7c404df39..9a703b40978 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -109,7 +109,7 @@ class IrModule(models.Model): path = opj(module_dir, mod_name) self.import_module(mod_name, path, force=force) success.append(mod_name) - except Exception, e: + except Exception as e: _logger.exception('Error while importing module') errors[mod_name] = exception_to_unicode(e) r = ["Successfully imported module '%s'" % mod for mod in success] diff --git a/addons/bus/models/bus.py b/addons/bus/models/bus.py index 466592505a9..8795a6047c8 100644 --- a/addons/bus/models/bus.py +++ b/addons/bus/models/bus.py @@ -168,7 +168,7 @@ class ImDispatch(object): while True: try: self.loop() - except Exception, e: + except Exception as e: _logger.exception("Bus.loop error, sleep and retry") time.sleep(TIMEOUT) diff --git a/addons/fetchmail/models/fetchmail.py b/addons/fetchmail/models/fetchmail.py index 45f7e81a240..d12b9ec72a0 100644 --- a/addons/fetchmail/models/fetchmail.py +++ b/addons/fetchmail/models/fetchmail.py @@ -132,7 +132,7 @@ class FetchmailServer(models.Model): try: connection = server.connect() server.write({'state': 'done'}) - except Exception, err: + except Exception as err: _logger.info("Failed to connect to %s server %s.", server.type, server.name, exc_info=True) raise UserError(_("Connection test failed: %s") % tools.ustr(err)) finally: diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index d9ebb699cd0..1e9975ea97f 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -85,7 +85,7 @@ class GoalDefinition(models.Model): }) # dummy search to make sure the domain is valid Obj.search_count(domain) - except (ValueError, SyntaxError), e: + except (ValueError, SyntaxError) as e: msg = e.message or (e.msg + '\n' + e.text) raise exceptions.UserError(_("The domain for the definition %s seems incorrect, please check it.\n\n%s") % (definition.name, msg)) return True @@ -102,7 +102,7 @@ class GoalDefinition(models.Model): if not (field and field.store): raise exceptions.UserError( _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not stored") % (definition.name, definition.field_id.name)) - except KeyError, e: + except KeyError as e: raise exceptions.UserError( _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not found") % (definition.name, e.message)) diff --git a/addons/google_account/models/google_service.py b/addons/google_account/models/google_service.py index 07fa2d12cf5..9cd47c39179 100644 --- a/addons/google_account/models/google_service.py +++ b/addons/google_account/models/google_service.py @@ -135,7 +135,7 @@ class GoogleService(models.TransientModel): try: dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, type='POST', preuri='') return response - except urllib2.HTTPError, error: + except urllib2.HTTPError as error: if error.code == 400: # invalid grant with registry(request.session.db).cursor() as cur: self.env(cur)['res.users'].browse(self.env.uid).write({'google_%s_rtoken' % service: False}) @@ -182,7 +182,7 @@ class GoogleService(models.TransientModel): ask_time = datetime.strptime(resp.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z") except: pass - except urllib2.HTTPError, error: + except urllib2.HTTPError as error: if error.code in (204, 404): status = error.code response = "" diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py index fb2e2e4f611..169368b9bd5 100644 --- a/addons/google_calendar/models/google_calendar.py +++ b/addons/google_calendar/models/google_calendar.py @@ -291,7 +291,7 @@ class GoogleCalendar(models.AbstractModel): try: status, content, ask_time = self.env['google.service']._do_request(url, params, headers, type='GET') - except urllib2.HTTPError, e: + except urllib2.HTTPError as e: if e.code == 401: # Token invalid / Acces unauthorized error_msg = _("Your token is invalid or has been revoked !") @@ -356,7 +356,7 @@ class GoogleCalendar(models.AbstractModel): url = "/calendar/v3/calendars/%s/events/%s" % ('primary', google_id) try: status, content, ask_time = self.env['google.service']._do_request(url, params, headers, type='GET') - except Exception, e: + except Exception as e: _logger.info("Calendar Synchro - In except of get_one_event_synchro") _logger.info(exception_to_unicode(e)) return False @@ -550,7 +550,7 @@ class GoogleCalendar(models.AbstractModel): _logger.info("[%s] Calendar Synchro - Failed - NEED RESET !", user_to_sync) else: _logger.info("[%s] Calendar Synchro - Done with status : %s !", user_to_sync, resp.get("status")) - except Exception, e: + except Exception as e: _logger.info("[%s] Calendar Synchro - Exception : %s !", user_to_sync, exception_to_unicode(e)) _logger.info("Calendar Synchro - Ended by cron") @@ -676,7 +676,7 @@ class GoogleCalendar(models.AbstractModel): if lastSync: try: all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync) - except urllib2.HTTPError, e: + except urllib2.HTTPError as e: if e.code == 410: # GONE, Google is lost. # we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise. self.env.cr.rollback() @@ -842,7 +842,7 @@ class GoogleCalendar(models.AbstractModel): try: # if already deleted from gmail or never created recs.delete_an_event(current_event[0]) - except Exception, e: + except Exception as e: if e.code in (401, 410,): pass else: diff --git a/addons/mrp/tests/test_multicompany.py b/addons/mrp/tests/test_multicompany.py index 8a1d1fc3716..f7d34516340 100644 --- a/addons/mrp/tests/test_multicompany.py +++ b/addons/mrp/tests/test_multicompany.py @@ -29,5 +29,5 @@ class TestMrpMulticompany(common.TransactionCase): if defaults.get(field): try: StockLocation.check_access_rule([defaults[field]], 'read') - except Exception, exc: + except Exception as exc: assert False, "unreadable location %s: %s" % (field, exc) diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index af2e9e05732..2dfa2747ab8 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -39,7 +39,7 @@ class OgoneController(http.Controller): acq = request.env['payment.acquirer'].browse(int(post.get('acquirer_id'))) try: acq.s2s_process(post) - except Exception, e: + except Exception as e: # synthax error: 'CHECK ERROR: |Not a valid date\n\n50001111: None' error = e.message.splitlines()[0].split('|')[-1] or '' return werkzeug.utils.redirect(post.get('return_url', '/') + (error and '#error=%s' % werkzeug.url_quote(error) or '')) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 265e48bea35..7e0fd9f8123 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -117,7 +117,7 @@ class ReportController(Controller): return response else: return - except Exception, e: + except Exception as e: se = _serialize_exception(e) error = { 'code': 200, diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 771735c752d..621fea372f9 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -67,7 +67,7 @@ def serialize_exception(f): def wrap(*args, **kwargs): try: return f(*args, **kwargs) - except Exception, e: + except Exception as e: _logger.exception("An exception occured during an http request") se = _serialize_exception(e) error = { @@ -670,7 +670,7 @@ class Database(http.Controller): dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code]) request.session.authenticate(name, post['login'], password) return http.local_redirect('/web/') - except Exception, e: + except Exception as e: error = "Database creation error: %s" % e return self._render_template(error=error) @@ -679,7 +679,7 @@ class Database(http.Controller): try: dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name]) return http.local_redirect('/web/database/manager') - except Exception, e: + except Exception as e: error = "Database duplication error: %s" % e return self._render_template(error=error) @@ -689,7 +689,7 @@ class Database(http.Controller): dispatch_rpc('db','drop', [master_pwd, name]) request._cr = None # dropping a database leads to an unusable cursor return http.local_redirect('/web/database/manager') - except Exception, e: + except Exception as e: error = "Database deletion error: %s" % e return self._render_template(error=error) @@ -706,7 +706,7 @@ class Database(http.Controller): dump_stream = odoo.service.db.dump_db(name, None, backup_format) response = werkzeug.wrappers.Response(dump_stream, headers=headers, direct_passthrough=True) return response - except Exception, e: + except Exception as e: _logger.exception('Database.backup') error = "Database backup error: %s" % e return self._render_template(error=error) @@ -717,7 +717,7 @@ class Database(http.Controller): data = base64.b64encode(backup_file.read()) dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)]) return http.local_redirect('/web/database/manager') - except Exception, e: + except Exception as e: error = "Database restore error: %s" % e return self._render_template(error=error) @@ -726,7 +726,7 @@ class Database(http.Controller): try: dispatch_rpc('db', 'change_admin_password', [master_pwd, master_pwd_new]) return http.local_redirect('/web/database/manager') - except Exception, e: + except Exception as e: error = "Master password update error: %s" % e return self._render_template(error=error) @@ -772,7 +772,7 @@ class Session(http.Controller): def get_lang_list(self): try: return dispatch_rpc('db', 'list_lang', []) or [] - except Exception, e: + except Exception as e: return {"error": e, "title": _("Languages")} @http.route('/web/session/modules', type='json', auth="user") @@ -1050,7 +1050,7 @@ class Binary(http.Controller): data = ufile.read() args = [len(data), ufile.filename, ufile.content_type, base64.b64encode(data)] - except Exception, e: + except Exception as e: args = [False, e.message] return out % (json.dumps(callback), json.dumps(args)) diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 054850b0926..5a3eca776ef 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -173,7 +173,7 @@ class Web_Editor(http.Controller): u"than 42 million pixel") if not disable_optimization and image.format in ('PNG', 'JPEG'): data = tools.image_save_for_web(image) - except IOError, e: + except IOError as e: pass attachment = Attachments.create({ @@ -185,7 +185,7 @@ class Web_Editor(http.Controller): }) attachments += attachment uploads += attachments.read(['name', 'mimetype', 'checksum', 'url']) - except Exception, e: + except Exception as e: logger.exception("Failed to upload image to attachment") message = unicode(e) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 82ef021bc81..ccdd44e1c4d 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -117,7 +117,7 @@ class Website(Home): try: request.website.get_template(page) - except ValueError, e: + except ValueError as e: # page not found if request.website.is_publisher(): values.pop('deletable') @@ -208,7 +208,7 @@ class Website(Home): def website_info(self): try: request.website.get_template('website.website_info').name - except Exception, e: + except Exception as e: return request.env['ir.http']._handle_exception(e, 404) Module = request.env['ir.module.module'].sudo() apps = Module.search([('state', '=', 'installed'), ('application', '=', True)]) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index c62486d34a0..b6902b17c41 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -249,7 +249,7 @@ class Http(models.AbstractModel): try: _, path = rule.build(arguments) assert path is not None - except Exception, e: + except Exception as e: return cls._handle_exception(e, code=404) if getattr(request, 'website_multilang', False) and request.httprequest.method in ('GET', 'HEAD'): @@ -276,7 +276,7 @@ class Http(models.AbstractModel): else: # if parent excplicitely returns a plain response, then we don't touch it return response - except Exception, e: + except Exception as e: if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')): raise exception = e diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py index df7aa42810d..6a93f387ec1 100644 --- a/addons/website_form/controllers/main.py +++ b/addons/website_form/controllers/main.py @@ -28,7 +28,7 @@ class WebsiteForm(http.Controller): try: data = self.extract_data(model_record, request.params) # If we encounter an issue while extracting data - except ValidationError, e: + except ValidationError as e: # I couldn't find a cleaner way to pass data to an exception return json.dumps({'error_fields' : e.args[0]}) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 407a2e7cd11..8b4a1d34d0d 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -96,7 +96,7 @@ class WebsiteSaleForm(WebsiteForm): model_record = request.env.ref('sale.model_sale_order') try: data = self.extract_data(model_record, kwargs) - except ValidationError, e: + except ValidationError as e: return json.dumps({'error_fields': e.args[0]}) order = request.website.sale_get_order() @@ -762,7 +762,7 @@ class WebsiteSale(http.Controller): # Auto-confirm SO if necessary tx._confirm_so() return dict(success=True, url='/shop/payment/validate') - except Exception, e: + except Exception as e: _logger.warning(_("Payment transaction (%s) failed : <%s>") % (tx.id, str(e))) return dict(success=False, error=_("Payment transaction failed (Contact Administrator)")) return dict(success=False, error='Tx missmatch') diff --git a/addons/website_twitter/models/website_twitter.py b/addons/website_twitter/models/website_twitter.py index cf0fe5d4d07..2b17411b72c 100644 --- a/addons/website_twitter/models/website_twitter.py +++ b/addons/website_twitter/models/website_twitter.py @@ -35,7 +35,7 @@ class WebsiteTwitter(models.Model): request = Request(url) request.add_header('Authorization', 'Bearer %s' % access_token) return json.load(urlopen(request, timeout=URLOPEN_TIMEOUT)) - except HTTPError, e: + except HTTPError as e: _logger.debug("Twitter API request failed with code: %r, msg: %r, content: %r", e.code, e.msg, e.fp.read()) raise diff --git a/addons/website_twitter/models/website_twitter_config.py b/addons/website_twitter/models/website_twitter_config.py index d92c376a0d1..49542e39606 100644 --- a/addons/website_twitter/models/website_twitter_config.py +++ b/addons/website_twitter/models/website_twitter_config.py @@ -49,13 +49,13 @@ class WebsiteTwitterConfig(models.TransientModel): try: self.website_id.fetch_favorite_tweets() - except HTTPError, e: + except HTTPError as e: _logger.info("%s - %s" % (e.code, e.reason), exc_info=True) raise UserError("%s - %s" % (e.code, e.reason) + ':' + self._get_twitter_exception_message(e.code)) - except URLError, e: + except URLError as e: _logger.info(_('We failed to reach a twitter server.'), exc_info=True) raise UserError(_('Internet connection refused') + ' ' + _('We failed to reach a twitter server.')) - except Exception, e: + except Exception as e: _logger.info(_('Please double-check your Twitter API Key and Secret!'), exc_info=True) raise UserError(_('Twitter authorization error!') + ' ' + _('Please double-check your Twitter API Key and Secret!')) diff --git a/doc/python3.rst b/doc/python3.rst new file mode 100644 index 00000000000..a0db3381a57 --- /dev/null +++ b/doc/python3.rst @@ -0,0 +1,88 @@ +:orphan: + +================================== +Python 3 compatibility/conversions +================================== + +Goal (notsure?): for v11 to provide an alpha/beta Python 3 compatibility, for +v12 to provide official Python 3 support and drop Python 2 in either v12 or +v13. + +Python 2 and Python 3 are somewhat different language, but following +backports, forward ports and cross-compatibility library it is possible to +use a subset of Python 2 and Python 3 in order to have a system compatible +with both. + +Here are a few useful steps or reminders to make Python 2 code compatible +with Python 3. + +References/useful documents: + +* `How do I port to Python 3? `_ +* `Python-Future `_ +* `Porting Python 2 code to Python 3 `_ +* `Porting to Python 3: A Guide `_ (a bit outdated but useful for the extensive comments on strings and IO) + +Versions Support +================ + +A cross compatible Odoo would only support Python 2.7 and Python 3.5 and +above: Python 2.7 backported some Python 3 features, and Python 2 features +were reintroduced in various Python 3 in order to make conversion easier. +Python 3.6 adds great features (f-strings, ...) and performance improvements +(ordered compact dicts) but does not seem to reintroduce compatibility +features whereas: + +* Python 3.5 reintroduced ``%`` for bytes/bytestrings (:pep:`461`) +* Python 3.4 has no specific compatibility improvement but is the lowest P3 + version for PyLint +* Python 3.3 reintroduced the "u" prefix for proper (unicode) strings +* Python 3.2 made ``range`` views more list-like and reintroduced ``callable`` + +.. warning:: + + Python 3 adds plenty of great features (keyword-only parameters, + generator delegation, pathlib, ...), do not use them until Python 2 + support is dropped + +Fixes +===== + +Exception Handlers +------------------ + +.. important:: + + All exception handlers must be converted to ``except ... as ..``. Valid + forms are:: + + except Exception: + except (Exception1, ...): + except Exception as name: + except (Exception1, ...) as name: + +In Python 2, ``except`` statements are of the form:: + + except Exception[, name]: + +or:: + + except (Exception1, Exception2)[, name]: + +But because the name is optional, this gets confusing and people can stumble +into the first form when trying for the second and write:: + + except Exception1, Exception: + +which will *not* yield the expected result. + +Python 3 changes this syntax to:: + + except Exception[ as name]: + +or:: + + except (Exception1, Exception2)[ as name]: + +This form was implemented in Python 2.5 and is thus compatible across the +board. diff --git a/odoo/addons/base/ir/ir_cron.py b/odoo/addons/base/ir/ir_cron.py index a6f29ee0d95..3edf3fab094 100644 --- a/odoo/addons/base/ir/ir_cron.py +++ b/odoo/addons/base/ir/ir_cron.py @@ -95,7 +95,7 @@ class ir_cron(models.Model): end_time = time.time() _logger.debug('%.3fs (cron %s, server action %d with uid %d)', end_time - start_time, cron_name, server_action_id, self.env.uid) self.pool.signal_caches_change() - except Exception, e: + except Exception as e: self._handle_callback_exception(cron_name, server_action_id, job_id, e) @classmethod @@ -166,7 +166,7 @@ class ir_cron(models.Model): jobs = cr.dictfetchall() else: _logger.warning('Skipping database %s as its base version is not %s.', db_name, BASE_VERSION) - except psycopg2.ProgrammingError, e: + except psycopg2.ProgrammingError as e: if e.pgcode == '42P01': # Class 42 — Syntax Error or Access Rule Violation; 42P01: undefined_table # The table ir_cron does not exist; this is probably not an OpenERP database. @@ -206,7 +206,7 @@ class ir_cron(models.Model): finally: job_cr.close() - except psycopg2.OperationalError, e: + except psycopg2.OperationalError as e: if e.pgcode == '55P03': # Class 55: Object not in prerequisite state; 55P03: lock_not_available _logger.debug('Another process/thread is already busy executing job `%s`, skipping it.', job['cron_name']) diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py index 0d880687cfb..26c351d6fa8 100644 --- a/odoo/addons/base/ir/ir_fields.py +++ b/odoo/addons/base/ir/ir_fields.py @@ -83,7 +83,7 @@ class IrFieldsConverter(models.AbstractModel): # uniform handling w = ImportWarning(w) log(field, w) - except ValueError, e: + except ValueError as e: log(field, e) return converted diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 1d942e75a86..75676910461 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -176,7 +176,7 @@ class IrHttp(models.AbstractModel): try: rule, arguments = cls._find_handler(return_rule=True) func = rule.endpoint - except werkzeug.exceptions.NotFound, e: + except werkzeug.exceptions.NotFound as e: return cls._handle_exception(e) # check authentication level @@ -195,7 +195,7 @@ class IrHttp(models.AbstractModel): result = request.dispatch() if isinstance(result, Exception): raise result - except Exception, e: + except Exception as e: return cls._handle_exception(e) return result diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 47a7cd1b8b8..5bc2dec8a9b 100644 --- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -523,7 +523,7 @@ class JavascriptAsset(WebAsset): def _fetch_content(self): try: return super(JavascriptAsset, self)._fetch_content() - except AssetError, e: + except AssetError as e: return "console.error(%s);" % json.dumps(e.message) def to_html(self): @@ -572,7 +572,7 @@ class StylesheetAsset(WebAsset): content = self.rx_charset.sub('', content) return content - except AssetError, e: + except AssetError as e: self.bundle.css_errors.append(e.message) return '' diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 89281963de7..e4b618a47e4 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -281,9 +281,9 @@ class QWeb(object): _options['ast_calls'] = [] def_name = self._create_def(_options, body, prefix='template_%s' % name.replace('.', '_')) _options['ast_calls'] += ast_calls - except QWebException, e: + except QWebException as e: raise e - except Exception, e: + except Exception as e: path = _options['last_path_node'] node = element.getroottree().xpath(path) raise QWebException("Error when compiling AST", e, path, etree.tostring(node[0]), name) @@ -301,9 +301,9 @@ class QWeb(object): ns = {} unsafe_eval(compile(astmod, '