From 65409e166868de62a27fff59be462858c917b828 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Thu, 23 Jun 2022 06:33:00 -0700 Subject: [PATCH] chore: setup `black` for CI Commit f0723402d00c5612a428d3815e4f1e653eb68ff0 ran `black` on the code base. But since it wasn't being enforced in the CI, it has drifted away from `black` standarized formatting. Did the following: * Added a Github CI job to run `black` * Added a `black` tox environment * Ran `black` on the code * Added the `requirements-dev.txt` file This has been setup so that it should be easy to add other checkers/linters in the future as desired. --- .github/workflows/lint.yml | 30 ++++++++++ doc/src/conf.py | 8 +-- examples/idle_selector_example.py | 5 +- imapclient/config.py | 3 +- imapclient/exceptions.py | 1 + imapclient/imapclient.py | 31 +++++------ livetest.py | 4 +- requirements-dev.txt | 2 + setup.py | 3 +- tests/test_imapclient.py | 91 ++++++++++++++++++------------- tests/test_response_lexer.py | 10 ++-- tests/test_response_parser.py | 12 ++-- tox.ini | 14 +++-- 13 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 requirements-dev.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..1ca0aa9 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,30 @@ +name: Lint + +# If a pull-request is pushed then cancel all previously running jobs related +# to that pull-request +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +on: + push: + branches: + - master + pull_request: + branches: + - master + +env: + PY_COLORS: 1 + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v3 + - run: pip install --upgrade tox + - name: Run black code formatter (https://black.readthedocs.io/en/stable/) + run: tox -e black -- --check diff --git a/doc/src/conf.py b/doc/src/conf.py index e35433e..00f4fed 100644 --- a/doc/src/conf.py +++ b/doc/src/conf.py @@ -38,8 +38,8 @@ master_doc = "index" # General information about the project. -project = u"IMAPClient" -copyright = u"2014, Menno Smits" +project = "IMAPClient" +copyright = "2014, Menno Smits" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -179,7 +179,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "IMAPClient.tex", u"IMAPClient Documentation", u"Menno Smits", "manual"), + ("index", "IMAPClient.tex", "IMAPClient Documentation", "Menno Smits", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -210,4 +210,4 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "imapclient", u"IMAPClient Documentation", [u"Menno Smits"], 1)] +man_pages = [("index", "imapclient", "IMAPClient Documentation", ["Menno Smits"], 1)] diff --git a/examples/idle_selector_example.py b/examples/idle_selector_example.py index e8ff729..5544c5a 100644 --- a/examples/idle_selector_example.py +++ b/examples/idle_selector_example.py @@ -13,10 +13,7 @@ server.login(USERNAME, PASSWORD) server.select_folder("INBOX", readonly=True) server.idle() - print( - "Connection is now in IDLE mode," - " send yourself an email or quit with ^c" - ) + print("Connection is now in IDLE mode," " send yourself an email or quit with ^c") try: with DefaultSelector() as selector: selector.register(server.socket(), EVENT_READ, None) diff --git a/imapclient/config.py b/imapclient/config.py index 0f5ae49..bb861a3 100644 --- a/imapclient/config.py +++ b/imapclient/config.py @@ -131,7 +131,8 @@ def refresh_oauth2_token(hostname, client_id, client_secret, refresh_token): grant_type=b"refresh_token", ) response = urllib.request.urlopen( - url, urllib.parse.urlencode(post).encode("ascii")).read() + url, urllib.parse.urlencode(post).encode("ascii") + ).read() return json.loads(response.decode("ascii"))["access_token"] diff --git a/imapclient/exceptions.py b/imapclient/exceptions.py index 7d9ddec..725af2f 100644 --- a/imapclient/exceptions.py +++ b/imapclient/exceptions.py @@ -4,6 +4,7 @@ # To ensure backward compatibility, we "rename" the imaplib general # exception class, so we can catch its exceptions without having to # deal with it in IMAPClient codebase + IMAPClientError = imaplib.IMAP4.error IMAPClientAbortError = imaplib.IMAP4.abort IMAPClientReadOnlyError = imaplib.IMAP4.readonly diff --git a/imapclient/imapclient.py b/imapclient/imapclient.py index e52a611..934a3a1 100644 --- a/imapclient/imapclient.py +++ b/imapclient/imapclient.py @@ -79,21 +79,21 @@ imaplib.Commands["MOVE"] = ("AUTH", "SELECTED") # System flags -DELETED = br"\Deleted" -SEEN = br"\Seen" -ANSWERED = br"\Answered" -FLAGGED = br"\Flagged" -DRAFT = br"\Draft" -RECENT = br"\Recent" # This flag is read-only +DELETED = rb"\Deleted" +SEEN = rb"\Seen" +ANSWERED = rb"\Answered" +FLAGGED = rb"\Flagged" +DRAFT = rb"\Draft" +RECENT = rb"\Recent" # This flag is read-only # Special folders, see RFC6154 # \Flagged is omitted because it is the same as the flag defined above -ALL = br"\All" -ARCHIVE = br"\Archive" -DRAFTS = br"\Drafts" -JUNK = br"\Junk" -SENT = br"\Sent" -TRASH = br"\Trash" +ALL = rb"\All" +ARCHIVE = rb"\Archive" +DRAFTS = rb"\Drafts" +JUNK = rb"\Junk" +SENT = rb"\Sent" +TRASH = rb"\Trash" # Personal namespaces that are common among providers # used as a fallback when the server does not support the NAMESPACE capability @@ -108,7 +108,7 @@ JUNK: ("Junk", "Spam"), } -_RE_SELECT_RESPONSE = re.compile(br"\[(?P[A-Z-]+)( \((?P.*)\))?\]") +_RE_SELECT_RESPONSE = re.compile(rb"\[(?P[A-Z-]+)( \((?P.*)\))?\]") class Namespace(tuple): @@ -1266,9 +1266,7 @@ def get_gmail_labels(self, messages): """ response = self.fetch(messages, [b"X-GM-LABELS"]) response = self._filter_fetch_dict(response, b"X-GM-LABELS") - return { - msg: utf7_decode_sequence(labels) for msg, labels in response.items() - } + return {msg: utf7_decode_sequence(labels) for msg, labels in response.items()} def add_gmail_labels(self, messages, labels, silent=False): """Add *labels* to *messages* in the currently selected folder. @@ -1420,6 +1418,7 @@ def multiappend(self, folder, msgs): Returns the APPEND response from the server. """ + def chunks(): for m in msgs: if isinstance(m, dict): diff --git a/livetest.py b/livetest.py index db78f1d..385d2e0 100644 --- a/livetest.py +++ b/livetest.py @@ -303,7 +303,7 @@ def test_select_read_only(self): def test_list_folders(self): some_folders = ["simple", b"simple2", "L\xffR"] if not self.is_fastmail(): - some_folders.extend([r'test"folder"', br"foo\bar"]) + some_folders.extend([r'test"folder"', rb"foo\bar"]) some_folders = self.add_prefix_to_folders(some_folders) for name in some_folders: self.client.create_folder(name) @@ -329,7 +329,7 @@ def test_xlist(self): foundInbox = False for flags, _, _ in result: - if br"\INBOX" in [flag.upper() for flag in flags]: + if rb"\INBOX" in [flag.upper() for flag in flags]: foundInbox = True break if not foundInbox: diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..c9c46ec --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +sphinx +black==22.3.0 diff --git a/setup.py b/setup.py index 5c5bed2..bac9df8 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,8 @@ author_email=info["author_email"], license="http://en.wikipedia.org/wiki/BSD_licenses", url="https://github.com/mjs/imapclient/", - download_url="http://menno.io/projects/IMAPClient/IMAPClient-%s.zip" % info["version"], + download_url="http://menno.io/projects/IMAPClient/IMAPClient-%s.zip" + % info["version"], packages=["imapclient"], package_data=dict(imapclient=["examples/*.py"]), extras_require={"doc": doc_deps}, diff --git a/tests/test_imapclient.py b/tests/test_imapclient.py index d1d0058..4e46b91 100644 --- a/tests/test_imapclient.py +++ b/tests/test_imapclient.py @@ -205,11 +205,11 @@ def test_funky_characters(self): def test_quoted_specials(self): folders = self.client._proc_folder_list( [ - br'(\HasNoChildren) "/" "Test \"Folder\""', - br'(\HasNoChildren) "/" "Left\"Right"', - br'(\HasNoChildren) "/" "Left\\Right"', - br'(\HasNoChildren) "/" "\"Left Right\""', - br'(\HasNoChildren) "/" "\"Left\\Right\""', + rb'(\HasNoChildren) "/" "Test \"Folder\""', + rb'(\HasNoChildren) "/" "Left\"Right"', + rb'(\HasNoChildren) "/" "Left\\Right"', + rb'(\HasNoChildren) "/" "\"Left Right\""', + rb'(\HasNoChildren) "/" "\"Left\\Right\""', ] ) self.assertEqual( @@ -228,9 +228,9 @@ def test_empty_response(self): def test_blanks(self): folders = self.client._proc_folder_list( - ["", None, br'(\HasNoChildren) "/" "last"'] + ["", None, rb'(\HasNoChildren) "/" "last"'] ) - self.assertEqual(folders, [((br"\HasNoChildren",), b"/", "last")]) + self.assertEqual(folders, [((rb"\HasNoChildren",), b"/", "last")]) class TestFindSpecialFolder(IMAPClientTest): @@ -284,15 +284,15 @@ def test_normal(self): self.client._command_and_check = Mock() self.client._imap.untagged_responses = { b"exists": [b"3"], - b"FLAGS": [br"(\Flagged \Deleted abc [foo]/bar def)"], + b"FLAGS": [rb"(\Flagged \Deleted abc [foo]/bar def)"], b"HIGHESTMODSEQ": [b"127110"], b"OK": [ - br"[PERMANENTFLAGS (\Flagged \Deleted abc [foo]/bar def \*)] Flags permitted.", + rb"[PERMANENTFLAGS (\Flagged \Deleted abc [foo]/bar def \*)] Flags permitted.", b"[UIDVALIDITY 631062293] UIDs valid.", b"[UIDNEXT 1281] Predicted next UID.", b"[HIGHESTMODSEQ 127110]", ], - b"PERMANENTFLAGS": [br"(\Flagged \Deleted abc [foo"], + b"PERMANENTFLAGS": [rb"(\Flagged \Deleted abc [foo"], b"READ-WRITE": [b""], b"RECENT": [b"0"], b"UIDNEXT": [b"1281"], @@ -314,14 +314,14 @@ def test_normal(self): b"UIDNEXT": 1281, b"UIDVALIDITY": 631062293, b"HIGHESTMODSEQ": 127110, - b"FLAGS": (br"\Flagged", br"\Deleted", b"abc", b"[foo]/bar", b"def"), + b"FLAGS": (rb"\Flagged", rb"\Deleted", b"abc", b"[foo]/bar", b"def"), b"PERMANENTFLAGS": ( - br"\Flagged", - br"\Deleted", + rb"\Flagged", + rb"\Deleted", b"abc", b"[foo]/bar", b"def", - br"\*", + rb"\*", ), b"READ-WRITE": True, b"OTHER": [b"blah"], @@ -380,30 +380,38 @@ def test_multiappend(self): def test_multiappend_with_flags_and_internaldate(self): self.client._cached_capabilities = (b"MULTIAPPEND",) self.client._raw_command = Mock() - self.client.multiappend("foobar", [ - { - "msg": "msg1", - "flags": ["FLAG", "WAVE"], - "date": datetime(2009, 4, 5, 11, 0, 5, 0, FixedOffset(2 * 60)), - }, - { - "msg": "msg2", - "flags": ["FLAG", "WAVE"], - }, - { - "msg": "msg3", - "date": datetime(2009, 4, 5, 11, 0, 5, 0, FixedOffset(2 * 60)), - }]) + self.client.multiappend( + "foobar", + [ + { + "msg": "msg1", + "flags": ["FLAG", "WAVE"], + "date": datetime(2009, 4, 5, 11, 0, 5, 0, FixedOffset(2 * 60)), + }, + { + "msg": "msg2", + "flags": ["FLAG", "WAVE"], + }, + { + "msg": "msg3", + "date": datetime(2009, 4, 5, 11, 0, 5, 0, FixedOffset(2 * 60)), + }, + ], + ) self.client._raw_command.assert_called_once_with( - b"APPEND", [b'"foobar"', - b'(FLAG WAVE)', - b'"05-Apr-2009 11:00:05 +0200"', - _literal(b"msg1"), - b'(FLAG WAVE)', - _literal(b"msg2"), - b'"05-Apr-2009 11:00:05 +0200"', - _literal(b"msg3")], uid=False + b"APPEND", + [ + b'"foobar"', + b"(FLAG WAVE)", + b'"05-Apr-2009 11:00:05 +0200"', + _literal(b"msg1"), + b"(FLAG WAVE)", + _literal(b"msg2"), + b'"05-Apr-2009 11:00:05 +0200"', + _literal(b"msg3"), + ], + uid=False, ) @@ -1000,13 +1008,19 @@ def test_literal_plus_multiple_literals(self): self.client._cached_capabilities = (b"LITERAL+",) typ, data = self.client._raw_command( - b"APPEND", [b"\xff", _literal(b"hello"), b"TEXT", _literal(b"test")], uid=False + b"APPEND", + [b"\xff", _literal(b"hello"), b"TEXT", _literal(b"test")], + uid=False, ) self.assertEqual(typ, "OK") self.assertEqual(data, ["done"]) self.assertEqual( self.client._imap.sent, - b"tag APPEND {1+}\r\n" b"\xff {5+}\r\n" b"hello" b" TEXT {4+}\r\n" b"test\r\n", + b"tag APPEND {1+}\r\n" + b"\xff {5+}\r\n" + b"hello" + b" TEXT {4+}\r\n" + b"test\r\n", ) def test_complex(self): @@ -1090,6 +1104,7 @@ def test_tagged_response_with_parse_error(self): with self.assertRaises(ProtocolError): client._consume_until_tagged_response(sentinel.tag, b"IDLE") + class TestSocket(IMAPClientTest): def test_issues_warning_for_deprecating_sock_property(self): mock_sock = Mock() diff --git a/tests/test_response_lexer.py b/tests/test_response_lexer.py index 4914323..ad667b0 100644 --- a/tests/test_response_lexer.py +++ b/tests/test_response_lexer.py @@ -32,12 +32,12 @@ def test_unterminated_strings(self): self.check_error([b'"aaa bbb'], message) def test_escaping(self): - self.check([br'"aaa\"bbb"'], [br'"aaa"bbb"']) - self.check([br'"aaa\\bbb"'], [br'"aaa\bbb"']) - self.check([br'"aaa\\bbb \"\""'], [br'"aaa\bbb """']) + self.check([rb'"aaa\"bbb"'], [rb'"aaa"bbb"']) + self.check([rb'"aaa\\bbb"'], [rb'"aaa\bbb"']) + self.check([rb'"aaa\\bbb \"\""'], [rb'"aaa\bbb """']) def test_invalid_escape(self): - self.check([br'"aaa\Zbbb"'], [br'"aaa\Zbbb"']) + self.check([rb'"aaa\Zbbb"'], [rb'"aaa\Zbbb"']) def test_lists(self): self.check([b"()"], [b"(", b")"]) @@ -59,7 +59,7 @@ def test_square_brackets(self): self.check([b"aaa [bbb]"], [b"aaa", b"[bbb]"]) def test_no_escaping_in_square_brackets(self): - self.check([br"[aaa\\bbb]"], [br"[aaa\\bbb]"]) + self.check([rb"[aaa\\bbb]"], [rb"[aaa\\bbb]"]) def test_unmatched_square_brackets(self): message = "No closing ']'" diff --git a/tests/test_response_parser.py b/tests/test_response_parser.py index 830f865..e7ce437 100644 --- a/tests/test_response_parser.py +++ b/tests/test_response_parser.py @@ -30,7 +30,7 @@ class TestParseResponse(unittest.TestCase): def test_unquoted(self): self._test(b"FOO", b"FOO") self._test(b"F.O:-O_0;", b"F.O:-O_0;") - self._test(br"\Seen", br"\Seen") + self._test(rb"\Seen", rb"\Seen") def test_string(self): self._test(b'"TEST"', b"TEST") @@ -159,9 +159,9 @@ def test_literal_with_more(self): self._test(response, (12, b"foo", literal_text)) def test_quoted_specials(self): - self._test(br'"\"foo bar\""', b'"foo bar"') - self._test(br'"foo \"bar\""', b'foo "bar"') - self._test(br'"foo\\bar"', br"foo\bar") + self._test(rb'"\"foo bar\""', b'"foo bar"') + self._test(rb'"foo \"bar\""', b'foo "bar"') + self._test(rb'"foo\\bar"', rb"foo\bar") def test_square_brackets(self): self._test(b"foo[bar rrr]", b"foo[bar rrr]") @@ -263,8 +263,8 @@ def test_bad_UID(self): def test_FLAGS(self): self.assertEqual( - parse_fetch_response([br"23 (FLAGS (\Seen Stuff))"]), - {23: {b"SEQ": 23, b"FLAGS": (br"\Seen", b"Stuff")}}, + parse_fetch_response([rb"23 (FLAGS (\Seen Stuff))"]), + {23: {b"SEQ": 23, b"FLAGS": (rb"\Seen", b"Stuff")}}, ) def test_multiple_messages(self): diff --git a/tox.ini b/tox.ini index ac9c757..3eef932 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,19 @@ [tox] -envlist=py34,py35,py36,py37,py38,py39 -requires=sphinx +skipsdist = True +minversion = 3.0 +envlist=py34,py35,py36,py37,py38,py39,black [testenv] commands=python -m unittest +deps = -r{toxinidir}/requirements-dev.txt [testenv:py34] setenv= VIRTUALENV_PIP=19.0.1 VIRTUALENV_SETUPTOOLS=43.0.0 -[pep8] -max-line-length=100 -ignore=E731 +[testenv:black] +basepython = python3 +envdir={toxworkdir}/lint +commands = + black {posargs} .