diff --git a/.travis.yml b/.travis.yml index b3753ea0..ca72fb95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ sudo: false language: python python: - - "3.5" - "3.6" - "3.7" - "3.8" diff --git a/README.rst b/README.rst index c2166955..675ec318 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ encouraged to do so. Requirements ============ -* **Python**: 3.5, 3.6, 3.7. +* **Python**: 3.6, 3.7, 3.8. * **Backends**: MySQL, PostgreSQL, SQLite, Memory. Contributing diff --git a/docs/conf.py b/docs/conf.py index 4789396e..1ec26a5d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,3 @@ -# coding: utf8 -import os -import sys - templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" diff --git a/ihatemoney/api/common.py b/ihatemoney/api/common.py index 51ce96e8..9aefa2c8 100644 --- a/ihatemoney/api/common.py +++ b/ihatemoney/api/common.py @@ -1,4 +1,3 @@ -# coding: utf8 from functools import wraps from flask import current_app, request diff --git a/ihatemoney/api/v1/resources.py b/ihatemoney/api/v1/resources.py index 87950f65..dc1708ce 100644 --- a/ihatemoney/api/v1/resources.py +++ b/ihatemoney/api/v1/resources.py @@ -1,4 +1,3 @@ -# coding: utf8 from flask import Blueprint from flask_cors import CORS from flask_restful import Api diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index a192844c..eb1e24c2 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -51,7 +51,7 @@ class GenerateConfig(Command): def run(self, config_file): env = create_jinja_env("conf-templates", strict_rendering=True) - template = env.get_template("%s.j2" % config_file) + template = env.get_template(f"{config_file}.j2") bin_path = os.path.dirname(sys.executable) pkg_path = os.path.abspath(os.path.dirname(__file__)) diff --git a/ihatemoney/models.py b/ihatemoney/models.py index eae442ac..dca86110 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -323,7 +323,7 @@ class Project(db.Model): return self.name def __repr__(self): - return "" % self.name + return f"" class Person(db.Model): @@ -381,7 +381,7 @@ class Person(db.Model): return self.name def __repr__(self): - return "" % (self.name, self.project.name) + return f"" # We need to manually define a join table for m2m relations @@ -460,13 +460,12 @@ class Bill(db.Model): return 0 def __str__(self): - return "%s for %s" % (self.amount, self.what) + return f"{self.amount} for {self.what}" def __repr__(self): - return "" % ( - self.amount, - self.payer, - ", ".join([o.name for o in self.owers]), + return ( + f"" ) diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index b27fafcc..c836c970 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -1,4 +1,3 @@ -# coding: utf8 import base64 from collections import defaultdict import datetime @@ -59,7 +58,7 @@ class BaseTestCase(TestCase): "name": name, "id": name, "password": name, - "contact_email": "%s@notmyidea.org" % name, + "contact_email": f"{name}@notmyidea.org", }, ) @@ -68,7 +67,7 @@ class BaseTestCase(TestCase): id=name, name=str(name), password=generate_password_hash(name), - contact_email="%s@notmyidea.org" % name, + contact_email=f"{name}@notmyidea.org", ) models.db.session.add(project) models.db.session.commit() @@ -83,7 +82,7 @@ class IhatemoneyTestCase(BaseTestCase): return self.assertEqual( expected, resp.status_code, - "%s expected %s, got %s" % (url, expected, resp.status_code), + f"{url} expected {expected}, got {resp.status_code}", ) @@ -410,7 +409,7 @@ class BudgetTestCase(IhatemoneyTestCase): ) # remove fred - self.client.post("/raclette/members/%s/delete" % fred_id) + self.client.post(f"/raclette/members/{fred_id}/delete") # he is still in the database, but is deactivated self.assertEqual(len(models.Project.query.get("raclette").members), 2) @@ -420,7 +419,7 @@ class BudgetTestCase(IhatemoneyTestCase): # a bill or displaying the balance result = self.client.get("/raclette/") self.assertNotIn( - ("/raclette/members/%s/delete" % fred_id), result.data.decode("utf-8") + (f"/raclette/members/{fred_id}/delete"), result.data.decode("utf-8") ) result = self.client.get("/raclette/add") @@ -619,7 +618,7 @@ class BudgetTestCase(IhatemoneyTestCase): # edit the bill self.client.post( - "/raclette/edit/%s" % bill.id, + f"/raclette/edit/{bill.id}", data={ "date": "2011-08-10", "what": "fromage à raclette", @@ -633,7 +632,7 @@ class BudgetTestCase(IhatemoneyTestCase): self.assertEqual(bill.amount, 10, "bill edition") # delete the bill - self.client.get("/raclette/delete/%s" % bill.id) + self.client.get(f"/raclette/delete/{bill.id}") self.assertEqual(0, len(models.Bill.query.all()), "bill deletion") # test balance @@ -1079,7 +1078,7 @@ class BudgetTestCase(IhatemoneyTestCase): self.assertNotEqual( 0.0, rounded_amount, - msg="%f is equal to zero after rounding" % t["amount"], + msg=f"{t['amount']} is equal to zero after rounding", ) def test_export(self): @@ -1417,7 +1416,7 @@ class APITestCase(IhatemoneyTestCase): def api_create(self, name, id=None, password=None, contact=None): id = id or name password = password or name - contact = contact or "%s@notmyidea.org" % name + contact = contact or f"{name}@notmyidea.org" return self.client.post( "/api/projects", @@ -1431,7 +1430,7 @@ class APITestCase(IhatemoneyTestCase): def api_add_member(self, project, name, weight=1): self.client.post( - "/api/projects/%s/members" % project, + f"/api/projects/{project}/members", data={"name": name, "weight": weight}, headers=self.get_auth(project), ) @@ -1439,11 +1438,11 @@ class APITestCase(IhatemoneyTestCase): def get_auth(self, username, password=None): password = password or username base64string = ( - base64.encodebytes(("%s:%s" % (username, password)).encode("utf-8")) + base64.encodebytes(f"{username}:{password}".encode("utf-8")) .decode("utf-8") .replace("\n", "") ) - return {"Authorization": "Basic %s" % base64string} + return {"Authorization": f"Basic {base64string}"} def test_cors_requests(self): # Create a project and test that CORS headers are present if requested. @@ -1599,7 +1598,7 @@ class APITestCase(IhatemoneyTestCase): # Access with token resp = self.client.get( "/api/projects/raclette/token", - headers={"Authorization": "Basic %s" % decoded_resp["token"]}, + headers={"Authorization": f"Basic {decoded_resp['token']}"}, ) self.assertEqual(200, resp.status_code) @@ -2124,10 +2123,10 @@ class APITestCase(IhatemoneyTestCase): resp = self.client.get("/raclette/history", follow_redirects=True) self.assertEqual(resp.status_code, 200) self.assertIn( - "Person %s added" % em_surround("alexis"), resp.data.decode("utf-8") + f"Person {em_surround('alexis')} added", resp.data.decode("utf-8") ) self.assertIn( - "Project %s added" % em_surround("raclette"), resp.data.decode("utf-8"), + f"Project {em_surround('raclette')} added", resp.data.decode("utf-8"), ) self.assertEqual(resp.data.decode("utf-8").count(" -- "), 2) self.assertNotIn("127.0.0.1", resp.data.decode("utf-8")) @@ -2263,7 +2262,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Project %s added" % em_surround("demo"), resp.data.decode("utf-8"), + f"Project {em_surround('demo')} added", resp.data.decode("utf-8"), ) self.assertEqual(resp.data.decode("utf-8").count(" -- "), 1) self.assertNotIn("127.0.0.1", resp.data.decode("utf-8")) @@ -2319,7 +2318,7 @@ class HistoryTestCase(IhatemoneyTestCase): self.assertNotIn("127.0.0.1", resp.data.decode("utf-8")) self.assertNotIn(" -- ", resp.data.decode("utf-8")) self.assertNotIn( - "Project %s added" % em_surround("demo"), resp.data.decode("utf-8") + f"Project {em_surround('demo')} added", resp.data.decode("utf-8") ) def test_project_edit(self): @@ -2335,18 +2334,16 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) + self.assertIn(f"Project {em_surround('demo')} added", resp.data.decode("utf-8")) self.assertIn( - "Project %s added" % em_surround("demo"), resp.data.decode("utf-8") - ) - self.assertIn( - "Project contact email changed to %s" % em_surround("demo2@notmyidea.org"), + f"Project contact email changed to {em_surround('demo2@notmyidea.org')}", resp.data.decode("utf-8"), ) self.assertIn( "Project private code changed", resp.data.decode("utf-8"), ) self.assertIn( - "Project renamed to %s" % em_surround("demo2"), resp.data.decode("utf-8"), + f"Project renamed to {em_surround('demo2')}", resp.data.decode("utf-8"), ) self.assertLess( resp.data.decode("utf-8").index("Project renamed "), @@ -2462,7 +2459,7 @@ class HistoryTestCase(IhatemoneyTestCase): # edit the bill resp = self.client.post( - "/demo/edit/%i" % bill_id, + f"/demo/edit/{bill_id}", data={ "date": "2011-08-10", "what": "fromage à raclette", @@ -2474,12 +2471,12 @@ class HistoryTestCase(IhatemoneyTestCase): ) self.assertEqual(resp.status_code, 200) # delete the bill - resp = self.client.get("/demo/delete/%i" % bill_id, follow_redirects=True) + resp = self.client.get(f"/demo/delete/{bill_id}", follow_redirects=True) self.assertEqual(resp.status_code, 200) # delete user using POST method resp = self.client.post( - "/demo/members/%i/delete" % user_id, follow_redirects=True + f"/demo/members/{user_id}/delete", follow_redirects=True ) self.assertEqual(resp.status_code, 200) @@ -2581,7 +2578,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Person %s added" % em_surround("alexis"), resp.data.decode("utf-8") + f"Person {em_surround('alexis')} added", resp.data.decode("utf-8") ) # create a bill @@ -2601,7 +2598,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Bill %s added" % em_surround("25.0 for fromage à raclette"), + f"Bill {em_surround('25.0 for fromage à raclette')} added", resp.data.decode("utf-8"), ) @@ -2622,7 +2619,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Bill %s added" % em_surround("25.0 for fromage à raclette"), + f"Bill {em_surround('25.0 for fromage à raclette')} added", resp.data.decode("utf-8"), ) self.assertRegex( @@ -2641,7 +2638,7 @@ class HistoryTestCase(IhatemoneyTestCase): ) self.assertLess( resp.data.decode("utf-8").index( - "Bill %s renamed to" % em_surround("25.0 for fromage à raclette") + f"Bill {em_surround('25.0 for fromage à raclette')} renamed to" ), resp.data.decode("utf-8").index("Amount changed"), ) @@ -2653,7 +2650,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Bill %s removed" % em_surround("10.0 for new thing"), + f"Bill {em_surround('10.0 for new thing')} removed", resp.data.decode("utf-8"), ) @@ -2682,9 +2679,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp.data.decode("utf-8"), ) self.assertLess( - resp.data.decode("utf-8").index( - "Person %s renamed" % em_surround("alexis") - ), + resp.data.decode("utf-8").index(f"Person {em_surround('alexis')} renamed"), resp.data.decode("utf-8").index("Weight changed"), ) @@ -2695,7 +2690,7 @@ class HistoryTestCase(IhatemoneyTestCase): resp = self.client.get("/demo/history") self.assertEqual(resp.status_code, 200) self.assertIn( - "Person %s removed" % em_surround("new name"), resp.data.decode("utf-8") + f"Person {em_surround('new name')} removed", resp.data.decode("utf-8") ) def test_double_bill_double_person_edit_second(self): @@ -2748,8 +2743,7 @@ class HistoryTestCase(IhatemoneyTestCase): self.assertEqual(resp.status_code, 200) self.assertRegex( resp.data.decode("utf-8"), - r"Bill %s:\s* Amount changed\s* from %s\s* to %s" - % ( + r"Bill {}:\s* Amount changed\s* from {}\s* to {}".format( em_surround("25.0 for Bill 1", regex_escape=True), em_surround("25.0", regex_escape=True), em_surround("88.0", regex_escape=True), @@ -2758,8 +2752,7 @@ class HistoryTestCase(IhatemoneyTestCase): self.assertNotRegex( resp.data.decode("utf-8"), - r"Removed\s* %s\s* and\s* %s\s* from\s* owers list" - % ( + r"Removed\s* {}\s* and\s* {}\s* from\s* owers list".format( em_surround("User 1", regex_escape=True), em_surround("User 2", regex_escape=True), ), @@ -2795,11 +2788,10 @@ class HistoryTestCase(IhatemoneyTestCase): self.assertEqual(resp.data.decode("utf-8").count(" -- "), 5) self.assertNotIn("127.0.0.1", resp.data.decode("utf-8")) self.assertIn( - "Bill %s added" % em_surround("25.0 for Bill 1"), resp.data.decode("utf-8") + f"Bill {em_surround('25.0 for Bill 1')} added", resp.data.decode("utf-8") ) self.assertIn( - "Bill %s removed" % em_surround("25.0 for Bill 1"), - resp.data.decode("utf-8"), + f"Bill {em_surround('25.0 for Bill 1')} removed", resp.data.decode("utf-8"), ) # Add a new bill @@ -2819,20 +2811,19 @@ class HistoryTestCase(IhatemoneyTestCase): self.assertEqual(resp.data.decode("utf-8").count(" -- "), 6) self.assertNotIn("127.0.0.1", resp.data.decode("utf-8")) self.assertIn( - "Bill %s added" % em_surround("25.0 for Bill 1"), resp.data.decode("utf-8") + f"Bill {em_surround('25.0 for Bill 1')} added", resp.data.decode("utf-8") ) self.assertEqual( resp.data.decode("utf-8").count( - "Bill %s added" % em_surround("25.0 for Bill 1") + f"Bill {em_surround('25.0 for Bill 1')} added" ), 1, ) self.assertIn( - "Bill %s added" % em_surround("20.0 for Bill 2"), resp.data.decode("utf-8") + f"Bill {em_surround('20.0 for Bill 2')} added", resp.data.decode("utf-8") ) self.assertIn( - "Bill %s removed" % em_surround("25.0 for Bill 1"), - resp.data.decode("utf-8"), + f"Bill {em_surround('25.0 for Bill 1')} removed", resp.data.decode("utf-8"), ) def test_double_bill_double_person_edit_second_no_web(self): diff --git a/ihatemoney/web.py b/ihatemoney/web.py index a12eae19..a2f25e29 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -301,9 +301,7 @@ def create_project(): project=g.project.name, ) - message_body = render_template( - "reminder_mail.%s.j2" % get_locale().language - ) + message_body = render_template(f"reminder_mail.{get_locale().language}.j2") msg = Message( message_title, body=message_body, recipients=[project.contact_email] @@ -337,7 +335,7 @@ def remind_password(): # get the project project = Project.query.get(form.id.data) # send a link to reset the password - password_reminder = "password_reminder.%s.j2" % get_locale().language + password_reminder = f"password_reminder.{get_locale().language}.j2" current_app.mail.send( Message( "password recovery", @@ -520,7 +518,7 @@ def export_project(file, format): return send_file( file2export, - attachment_filename="%s-%s.%s" % (g.project.id, file, format), + attachment_filename=f"{g.project.id}-{file}.{format}", as_attachment=True, ) @@ -570,7 +568,7 @@ def invite(): # send the email message_body = render_template( - "invitation_mail.%s.j2" % get_locale().language + f"invitation_mail.{get_locale().language}.j2" ) message_title = _( diff --git a/setup.cfg b/setup.cfg index 86cce6a5..50a24a41 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ license = Custom BSD Beerware classifiers = Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 diff --git a/tox.ini b/tox.ini index 7632ed83..372f60f9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py38,py37,py36,py35,docs,flake8,black +envlist = py38,py37,py36,docs,flake8,black skip_missing_interpreters = True [testenv] @@ -39,7 +39,6 @@ extend-ignore = [travis] python = - 3.5: py35 3.6: py36 3.7: py37 3.8: py38, docs, black, flake8