diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81c0b0077..b18e90963 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: env: DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }} working-directory: deltachat-rpc-client - run: tox -e py3 + run: tox -e py3,lint - name: install pypy if: ${{ matrix.python }} diff --git a/deltachat-rpc-client/examples/echobot_advanced.py b/deltachat-rpc-client/examples/echobot_advanced.py index 48e3d025b..303f3ee66 100644 --- a/deltachat-rpc-client/examples/echobot_advanced.py +++ b/deltachat-rpc-client/examples/echobot_advanced.py @@ -27,9 +27,7 @@ async def log_error(event): @hooks.on(events.MemberListChanged) async def on_memberlist_changed(event): - logging.info( - "member %s was %s", event.member, "added" if event.member_added else "removed" - ) + logging.info("member %s was %s", event.member, "added" if event.member_added else "removed") @hooks.on(events.GroupImageChanged) diff --git a/deltachat-rpc-client/pyproject.toml b/deltachat-rpc-client/pyproject.toml index 4b3bd3520..7797ac903 100644 --- a/deltachat-rpc-client/pyproject.toml +++ b/deltachat-rpc-client/pyproject.toml @@ -27,3 +27,13 @@ deltachat_rpc_client = [ [project.entry-points.pytest11] "deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin" + +[tool.black] +line-length = 120 + +[tool.ruff] +select = ["E", "F", "W", "N", "YTT", "B", "C4", "ISC", "ICN", "PT", "RET", "SIM", "TID", "ARG", "DTZ", "ERA", "PLC", "PLE", "PLW", "PIE", "COM"] +line-length = 120 + +[tool.isort] +profile = "black" diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/__init__.py b/deltachat-rpc-client/src/deltachat_rpc_client/__init__.py index ff15923b5..94edad50e 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/__init__.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/__init__.py @@ -8,3 +8,18 @@ from .contact import Contact from .deltachat import DeltaChat from .message import Message from .rpc import Rpc + +__all__ = [ + "Account", + "AttrDict", + "Bot", + "Chat", + "Client", + "Contact", + "DeltaChat", + "EventType", + "Message", + "Rpc", + "run_bot_cli", + "run_client_cli", +] diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/_utils.py b/deltachat-rpc-client/src/deltachat_rpc_client/_utils.py index 562c62de1..f53d19c92 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/_utils.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/_utils.py @@ -30,12 +30,7 @@ class AttrDict(dict): """Dictionary that allows accessing values usin the "dot notation" as attributes.""" def __init__(self, *args, **kwargs) -> None: - super().__init__( - { - _camel_to_snake(key): _to_attrdict(value) - for key, value in dict(*args, **kwargs).items() - } - ) + super().__init__({_camel_to_snake(key): _to_attrdict(value) for key, value in dict(*args, **kwargs).items()}) def __getattr__(self, attr): if attr in self: @@ -51,7 +46,7 @@ class AttrDict(dict): async def run_client_cli( hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None, argv: Optional[list] = None, - **kwargs + **kwargs, ) -> None: """Run a simple command line app, using the given hooks. @@ -65,7 +60,7 @@ async def run_client_cli( async def run_bot_cli( hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None, argv: Optional[list] = None, - **kwargs + **kwargs, ) -> None: """Run a simple bot command line using the given hooks. @@ -80,7 +75,7 @@ async def _run_cli( client_type: Type["Client"], hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None, argv: Optional[list] = None, - **kwargs + **kwargs, ) -> None: from .deltachat import DeltaChat from .rpc import Rpc @@ -107,12 +102,9 @@ async def _run_cli( client = client_type(account, hooks) client.logger.debug("Running deltachat core %s", core_version) if not await client.is_configured(): - assert ( - args.email and args.password - ), "Account is not configured and email and password must be provided" - asyncio.create_task( - client.configure(email=args.email, password=args.password) - ) + assert args.email, "Account is not configured and email must be provided" + assert args.password, "Account is not configured and password must be provided" + asyncio.create_task(client.configure(email=args.email, password=args.password)) await client.run_forever() diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index eab67d10d..44535da08 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -89,9 +89,7 @@ class Account: """Configure an account.""" await self._rpc.configure(self.id) - async def create_contact( - self, obj: Union[int, str, Contact], name: Optional[str] = None - ) -> Contact: + async def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact: """Create a new Contact or return an existing one. Calling this method will always result in the same @@ -120,10 +118,7 @@ class Account: async def get_blocked_contacts(self) -> List[AttrDict]: """Return a list with snapshots of all blocked contacts.""" contacts = await self._rpc.get_blocked_contacts(self.id) - return [ - AttrDict(contact=Contact(self, contact["id"]), **contact) - for contact in contacts - ] + return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts] async def get_contacts( self, @@ -148,10 +143,7 @@ class Account: if snapshot: contacts = await self._rpc.get_contacts(self.id, flags, query) - return [ - AttrDict(contact=Contact(self, contact["id"]), **contact) - for contact in contacts - ] + return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts] contacts = await self._rpc.get_contact_ids(self.id, flags, query) return [Contact(self, contact_id) for contact_id in contacts] @@ -192,9 +184,7 @@ class Account: if alldone_hint: flags |= ChatlistFlag.ADD_ALLDONE_HINT - entries = await self._rpc.get_chatlist_entries( - self.id, flags, query, contact and contact.id - ) + entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id) if not snapshot: return [Chat(self, entry[0]) for entry in entries] diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py index 4a2f4ae77..cdcefcf95 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/chat.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/chat.py @@ -63,7 +63,7 @@ class Chat: """ if duration is not None: assert duration > 0, "Invalid duration" - dur: Union[str, dict] = dict(Until=duration) + dur: Union[str, dict] = {"Until": duration} else: dur = "Forever" await self._rpc.set_chat_mute_duration(self.account.id, self.id, dur) @@ -74,27 +74,19 @@ class Chat: async def pin(self) -> None: """Pin this chat.""" - await self._rpc.set_chat_visibility( - self.account.id, self.id, ChatVisibility.PINNED - ) + await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.PINNED) async def unpin(self) -> None: """Unpin this chat.""" - await self._rpc.set_chat_visibility( - self.account.id, self.id, ChatVisibility.NORMAL - ) + await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL) async def archive(self) -> None: """Archive this chat.""" - await self._rpc.set_chat_visibility( - self.account.id, self.id, ChatVisibility.ARCHIVED - ) + await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.ARCHIVED) async def unarchive(self) -> None: """Unarchive this chat.""" - await self._rpc.set_chat_visibility( - self.account.id, self.id, ChatVisibility.NORMAL - ) + await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL) async def set_name(self, name: str) -> None: """Set name of this chat.""" @@ -133,9 +125,7 @@ class Chat: if isinstance(quoted_msg, Message): quoted_msg = quoted_msg.id - msg_id, _ = await self._rpc.misc_send_msg( - self.account.id, self.id, text, file, location, quoted_msg - ) + msg_id, _ = await self._rpc.misc_send_msg(self.account.id, self.id, text, file, location, quoted_msg) return Message(self.account, msg_id) async def send_text(self, text: str) -> Message: @@ -241,23 +231,17 @@ class Chat: timestamp_to: Optional[datetime] = None, ) -> List[AttrDict]: """Get list of location snapshots for the given contact in the given timespan.""" - time_from = ( - calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0 - ) + time_from = calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0 time_to = calendar.timegm(timestamp_to.utctimetuple()) if timestamp_to else 0 contact_id = contact.id if contact else 0 - result = await self._rpc.get_locations( - self.account.id, self.id, contact_id, time_from, time_to - ) + result = await self._rpc.get_locations(self.account.id, self.id, contact_id, time_from, time_to) locations = [] contacts: Dict[int, Contact] = {} for loc in result: loc = AttrDict(loc) loc["chat"] = self - loc["contact"] = contacts.setdefault( - loc.contact_id, Contact(self.account, loc.contact_id) - ) + loc["contact"] = contacts.setdefault(loc.contact_id, Contact(self.account, loc.contact_id)) loc["message"] = Message(self.account, loc.msg_id) locations.append(loc) return locations diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/client.py b/deltachat-rpc-client/src/deltachat_rpc_client/client.py index 4c6deafa1..393ac3cc7 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/client.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/client.py @@ -47,15 +47,11 @@ class Client: self._should_process_messages = 0 self.add_hooks(hooks or []) - def add_hooks( - self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]] - ) -> None: + def add_hooks(self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]) -> None: for hook, event in hooks: self.add_hook(hook, event) - def add_hook( - self, hook: Callable, event: Union[type, EventFilter] = RawEvent - ) -> None: + def add_hook(self, hook: Callable, event: Union[type, EventFilter] = RawEvent) -> None: """Register hook for the given event filter.""" if isinstance(event, type): event = event() @@ -64,7 +60,7 @@ class Client: isinstance( event, (NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged), - ) + ), ) self._hooks.setdefault(type(event), set()).add((hook, event)) @@ -76,7 +72,7 @@ class Client: isinstance( event, (NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged), - ) + ), ) self._hooks.get(type(event), set()).remove((hook, event)) @@ -95,9 +91,7 @@ class Client: """Process events forever.""" await self.run_until(lambda _: False) - async def run_until( - self, func: Callable[[AttrDict], Union[bool, Coroutine]] - ) -> AttrDict: + async def run_until(self, func: Callable[[AttrDict], Union[bool, Coroutine]]) -> AttrDict: """Process events until the given callable evaluates to True. The callable should accept an AttrDict object representing the @@ -122,9 +116,7 @@ class Client: if stop: return event - async def _on_event( - self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent - ) -> None: + async def _on_event(self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent) -> None: for hook, evfilter in self._hooks.get(filter_type, []): if await evfilter.filter(event): try: @@ -133,11 +125,7 @@ class Client: self.logger.exception(ex) async def _parse_command(self, event: AttrDict) -> None: - cmds = [ - hook[1].command - for hook in self._hooks.get(NewMessage, []) - if hook[1].command - ] + cmds = [hook[1].command for hook in self._hooks.get(NewMessage, []) if hook[1].command] parts = event.message_snapshot.text.split(maxsplit=1) payload = parts[1] if len(parts) > 1 else "" cmd = parts.pop(0) @@ -202,11 +190,7 @@ class Client: for message in await self.account.get_fresh_messages_in_arrival_order(): snapshot = await message.get_snapshot() await self._on_new_msg(snapshot) - if ( - snapshot.is_info - and snapshot.system_message_type - != SystemMessageType.WEBXDC_INFO_MESSAGE - ): + if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE: await self._handle_info_msg(snapshot) await snapshot.message.mark_seen() diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/events.py b/deltachat-rpc-client/src/deltachat_rpc_client/events.py index 146c89ea5..3801dcc72 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/events.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/events.py @@ -10,7 +10,7 @@ from .const import EventType def _tuple_of(obj, type_: type) -> tuple: if not obj: - return tuple() + return () if isinstance(obj, type_): obj = (obj,) @@ -39,7 +39,7 @@ class EventFilter(ABC): """Return True if two event filters are equal.""" def __ne__(self, other): - return not self.__eq__(other) + return not self == other async def _call_func(self, event) -> bool: if not self.func: @@ -65,9 +65,7 @@ class RawEvent(EventFilter): should be dispatched or not. """ - def __init__( - self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs - ): + def __init__(self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs): super().__init__(**kwargs) try: self.types = _tuple_of(types, EventType) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/message.py b/deltachat-rpc-client/src/deltachat_rpc_client/message.py index 86ddbc95e..d38d2cbfe 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/message.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/message.py @@ -49,22 +49,14 @@ class Message: """Mark the message as seen.""" await self._rpc.markseen_msgs(self.account.id, [self.id]) - async def send_webxdc_status_update( - self, update: Union[dict, str], description: str - ) -> None: + async def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None: """Send a webxdc status update. This message must be a webxdc.""" if not isinstance(update, str): update = json.dumps(update) - await self._rpc.send_webxdc_status_update( - self.account.id, self.id, update, description - ) + await self._rpc.send_webxdc_status_update(self.account.id, self.id, update, description) async def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list: - return json.loads( - await self._rpc.get_webxdc_status_updates( - self.account.id, self.id, last_known_serial - ) - ) + return json.loads(await self._rpc.get_webxdc_status_updates(self.account.id, self.id, last_known_serial)) async def get_webxdc_info(self) -> dict: return await self._rpc.get_webxdc_info(self.account.id, self.id) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py index 6bfd446d1..36969c9c1 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py @@ -67,9 +67,7 @@ class ACFactory: ) -> Message: if not from_account: from_account = (await self.get_online_accounts(1))[0] - to_contact = await from_account.create_contact( - await to_account.get_config("addr") - ) + to_contact = await from_account.create_contact(await to_account.get_config("addr")) if group: to_chat = await from_account.create_group(group) await to_chat.add_contact(to_contact) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py b/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py index 8a407308d..f15c1a29a 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/rpc.py @@ -30,7 +30,7 @@ class Rpc: "deltachat-rpc-server", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, - **self._kwargs + **self._kwargs, ) self.id = 0 self.event_queues = {} @@ -46,7 +46,7 @@ class Rpc: await self.start() return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, _exc_type, _exc, _tb): await self.close() async def reader_loop(self) -> None: @@ -97,5 +97,6 @@ class Rpc: raise JsonRpcError(response["error"]) if "result" in response: return response["result"] + return None return method diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index d1208042e..1d02e1928 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -6,14 +6,14 @@ from deltachat_rpc_client import EventType, events from deltachat_rpc_client.rpc import JsonRpcError -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_system_info(rpc) -> None: system_info = await rpc.get_system_info() assert "arch" in system_info assert "deltachat_core_version" in system_info -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_email_address_validity(rpc) -> None: valid_addresses = [ "email@example.com", @@ -27,7 +27,7 @@ async def test_email_address_validity(rpc) -> None: assert not await rpc.check_email_validity(addr) -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_acfactory(acfactory) -> None: account = await acfactory.new_configured_account() while True: @@ -41,7 +41,7 @@ async def test_acfactory(acfactory) -> None: print("Successful configuration") -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_configure_starttls(acfactory) -> None: account = await acfactory.new_preconfigured_account() @@ -51,7 +51,7 @@ async def test_configure_starttls(acfactory) -> None: assert await account.is_configured() -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_account(acfactory) -> None: alice, bob = await acfactory.get_online_accounts(2) @@ -111,7 +111,7 @@ async def test_account(acfactory) -> None: await alice.stop_io() -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_chat(acfactory) -> None: alice, bob = await acfactory.get_online_accounts(2) @@ -177,7 +177,7 @@ async def test_chat(acfactory) -> None: await group.get_locations() -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_contact(acfactory) -> None: alice, bob = await acfactory.get_online_accounts(2) @@ -195,7 +195,7 @@ async def test_contact(acfactory) -> None: await alice_contact_bob.create_chat() -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_message(acfactory) -> None: alice, bob = await acfactory.get_online_accounts(2) @@ -226,7 +226,7 @@ async def test_message(acfactory) -> None: await message.send_reaction("😎") -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_bot(acfactory) -> None: mock = MagicMock() user = (await acfactory.get_online_accounts(1))[0] @@ -237,25 +237,20 @@ async def test_bot(acfactory) -> None: hook = lambda e: mock.hook(e.msg_id), events.RawEvent(EventType.INCOMING_MSG) bot.add_hook(*hook) - event = await acfactory.process_message( - from_account=user, to_client=bot, text="Hello!" - ) + event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!") mock.hook.assert_called_once_with(event.msg_id) bot.remove_hook(*hook) - track = lambda e: mock.hook(e.message_snapshot.id) + def track(e): + mock.hook(e.message_snapshot.id) mock.hook.reset_mock() hook = track, events.NewMessage(r"hello") bot.add_hook(*hook) bot.add_hook(track, events.NewMessage(command="/help")) - event = await acfactory.process_message( - from_account=user, to_client=bot, text="hello" - ) + event = await acfactory.process_message(from_account=user, to_client=bot, text="hello") mock.hook.assert_called_with(event.msg_id) - event = await acfactory.process_message( - from_account=user, to_client=bot, text="hello!" - ) + event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!") mock.hook.assert_called_with(event.msg_id) await acfactory.process_message(from_account=user, to_client=bot, text="hey!") assert len(mock.hook.mock_calls) == 2 @@ -263,7 +258,5 @@ async def test_bot(acfactory) -> None: mock.hook.reset_mock() await acfactory.process_message(from_account=user, to_client=bot, text="hello") - event = await acfactory.process_message( - from_account=user, to_client=bot, text="/help" - ) + event = await acfactory.process_message(from_account=user, to_client=bot, text="/help") mock.hook.assert_called_once_with(event.msg_id) diff --git a/deltachat-rpc-client/tests/test_webxdc.py b/deltachat-rpc-client/tests/test_webxdc.py index 1bc5f02c1..22d9db0b4 100644 --- a/deltachat-rpc-client/tests/test_webxdc.py +++ b/deltachat-rpc-client/tests/test_webxdc.py @@ -3,16 +3,14 @@ import pytest from deltachat_rpc_client import EventType -@pytest.mark.asyncio +@pytest.mark.asyncio() async def test_webxdc(acfactory) -> None: alice, bob = await acfactory.get_online_accounts(2) bob_addr = await bob.get_config("addr") alice_contact_bob = await alice.create_contact(bob_addr, "Bob") alice_chat_bob = await alice_contact_bob.create_chat() - await alice_chat_bob.send_message( - text="Let's play chess!", file="../test-data/webxdc/chess.xdc" - ) + await alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc") while True: event = await bob.wait_for_event() diff --git a/deltachat-rpc-client/tox.ini b/deltachat-rpc-client/tox.ini index bea3b4603..1c222e9a9 100644 --- a/deltachat-rpc-client/tox.ini +++ b/deltachat-rpc-client/tox.ini @@ -2,6 +2,7 @@ isolated_build = true envlist = py3 + lint [testenv] commands = @@ -16,3 +17,13 @@ deps = pytest-asyncio aiohttp aiodns + +[testenv:lint] +skipsdist = True +skip_install = True +deps = + ruff + black +commands = + black --check src/ examples/ tests/ + ruff src/ examples/ tests/ diff --git a/python/doc/conf.py b/python/doc/conf.py index 55fdcd5dd..4647c6ae2 100644 --- a/python/doc/conf.py +++ b/python/doc/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/python/examples/group_tracking.py b/python/examples/group_tracking.py index c5ebaed42..59705c379 100644 --- a/python/examples/group_tracking.py +++ b/python/examples/group_tracking.py @@ -34,8 +34,10 @@ class GroupTrackingPlugin: def ac_member_added(self, chat, contact, actor, message): print( "ac_member_added {} to chat {} from {}".format( - contact.addr, chat.id, actor or message.get_sender_contact().addr - ) + contact.addr, + chat.id, + actor or message.get_sender_contact().addr, + ), ) for member in chat.get_contacts(): print("chat member: {}".format(member.addr)) @@ -44,8 +46,10 @@ class GroupTrackingPlugin: def ac_member_removed(self, chat, contact, actor, message): print( "ac_member_removed {} from chat {} by {}".format( - contact.addr, chat.id, actor or message.get_sender_contact().addr - ) + contact.addr, + chat.id, + actor or message.get_sender_contact().addr, + ), ) diff --git a/python/examples/test_examples.py b/python/examples/test_examples.py index 3978417bf..e6b2d53f4 100644 --- a/python/examples/test_examples.py +++ b/python/examples/test_examples.py @@ -13,8 +13,8 @@ def datadir(): datadir = path.join("test-data") if datadir.isdir(): return datadir - else: - pytest.skip("test-data directory not found") + pytest.skip("test-data directory not found") + return None def test_echo_quit_plugin(acfactory, lp): @@ -47,7 +47,7 @@ def test_group_tracking_plugin(acfactory, lp): botproc.fnmatch_lines( """ *ac_configure_completed* - """ + """, ) ac1.add_account_plugin(FFIEventLogger(ac1)) ac2.add_account_plugin(FFIEventLogger(ac2)) @@ -61,7 +61,7 @@ def test_group_tracking_plugin(acfactory, lp): botproc.fnmatch_lines( """ *ac_chat_modified*bot test group* - """ + """, ) lp.sec("adding third member {}".format(ac2.get_config("addr"))) @@ -76,8 +76,9 @@ def test_group_tracking_plugin(acfactory, lp): """ *ac_member_added {}*from*{}* """.format( - contact3.addr, ac1.get_config("addr") - ) + contact3.addr, + ac1.get_config("addr"), + ), ) lp.sec("contact successfully added, now removing") @@ -86,6 +87,7 @@ def test_group_tracking_plugin(acfactory, lp): """ *ac_member_removed {}*from*{}* """.format( - contact3.addr, ac1.get_config("addr") - ) + contact3.addr, + ac1.get_config("addr"), + ), ) diff --git a/python/pyproject.toml b/python/pyproject.toml index 9ff0c63e6..18dfe3e9b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -44,5 +44,9 @@ git_describe_command = "git describe --dirty --tags --long --match py-*.*" [tool.black] line-length = 120 +[tool.ruff] +select = ["E", "F", "W", "YTT", "C4", "ISC", "ICN", "TID", "DTZ", "PLC", "PLE", "PLW", "PIE", "COM"] +line-length = 120 + [tool.isort] -profile = "black" \ No newline at end of file +profile = "black" diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index 8a15ac942..227f88977 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -36,7 +36,8 @@ register_global_plugin(events) def run_cmdline(argv=None, account_plugins=None): """Run a simple default command line app, registering the specified - account plugins.""" + account plugins. + """ import argparse if argv is None: diff --git a/python/src/deltachat/_build.py b/python/src/deltachat/_build.py index 995dd34b8..9ace6fc0e 100644 --- a/python/src/deltachat/_build.py +++ b/python/src/deltachat/_build.py @@ -102,8 +102,8 @@ def find_header(flags): printf("%s", _dc_header_file_location()); return 0; } - """ - ) + """, + ), ) cwd = os.getcwd() try: @@ -198,7 +198,7 @@ def ffibuilder(): typedef int... time_t; void free(void *ptr); extern int dc_event_has_string_data(int); - """ + """, ) function_defs = extract_functions(flags) defines = extract_defines(flags) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 2df4bf65b..76b5bea88 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -1,4 +1,4 @@ -""" Account class implementation. """ +"""Account class implementation.""" from __future__ import print_function @@ -39,7 +39,7 @@ def get_core_info(): ffi.gc( lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL), lib.dc_context_unref, - ) + ), ) @@ -172,10 +172,7 @@ class Account(object): namebytes = name.encode("utf8") if isinstance(value, (int, bool)): value = str(int(value)) - if value is not None: - valuebytes = value.encode("utf8") - else: - valuebytes = ffi.NULL + valuebytes = value.encode("utf8") if value is not None else ffi.NULL lib.dc_set_config(self._dc_context, namebytes, valuebytes) def get_config(self, name: str) -> str: @@ -225,9 +222,10 @@ class Account(object): return bool(lib.dc_is_configured(self._dc_context)) def is_open(self) -> bool: - """Determine if account is open + """Determine if account is open. - :returns True if account is open.""" + :returns True if account is open. + """ return bool(lib.dc_context_is_open(self._dc_context)) def set_avatar(self, img_path: Optional[str]) -> None: @@ -543,7 +541,7 @@ class Account(object): return from_dc_charpointer(res) def check_qr(self, qr): - """check qr code and return :class:`ScannedQRCode` instance representing the result""" + """check qr code and return :class:`ScannedQRCode` instance representing the result.""" res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref) lot = DCLot(res) if lot.state() == const.DC_QR_ERROR: @@ -662,7 +660,7 @@ class Account(object): return lib.dc_all_work_done(self._dc_context) def start_io(self): - """start this account's IO scheduling (Rust-core async scheduler) + """start this account's IO scheduling (Rust-core async scheduler). If this account is not configured an Exception is raised. You need to call account.configure() and account.wait_configure_finish() @@ -705,12 +703,10 @@ class Account(object): """ lib.dc_maybe_network(self._dc_context) - def configure(self, reconfigure: bool = False) -> ConfigureTracker: + def configure(self) -> ConfigureTracker: """Start configuration process and return a Configtracker instance on which you can block with wait_finish() to get a True/False success value for the configuration process. - - :param reconfigure: deprecated, doesn't need to be checked anymore. """ if not self.get_config("addr") or not self.get_config("mail_pw"): raise MissingCredentials("addr or mail_pwd not set in config") @@ -733,7 +729,8 @@ class Account(object): def shutdown(self) -> None: """shutdown and destroy account (stop callback thread, close and remove - underlying dc_context).""" + underlying dc_context). + """ if self._dc_context is None: return diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index d546d4775..783f25304 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -1,4 +1,4 @@ -""" Chat and Location related API. """ +"""Chat and Location related API.""" import calendar import json @@ -37,7 +37,7 @@ class Chat(object): return self.id == getattr(other, "id", None) and self.account._dc_context == other.account._dc_context def __ne__(self, other) -> bool: - return not (self == other) + return not self == other def __repr__(self) -> str: return "".format(self.id, self.get_name()) @@ -74,19 +74,19 @@ class Chat(object): return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP def is_single(self) -> bool: - """Return True if this chat is a single/direct chat, False otherwise""" + """Return True if this chat is a single/direct chat, False otherwise.""" return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE def is_mailinglist(self) -> bool: - """Return True if this chat is a mailing list, False otherwise""" + """Return True if this chat is a mailing list, False otherwise.""" return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_MAILINGLIST def is_broadcast(self) -> bool: - """Return True if this chat is a broadcast list, False otherwise""" + """Return True if this chat is a broadcast list, False otherwise.""" return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_BROADCAST def is_multiuser(self) -> bool: - """Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise""" + """Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise.""" return lib.dc_chat_get_type(self._dc_chat) in ( const.DC_CHAT_TYPE_GROUP, const.DC_CHAT_TYPE_MAILINGLIST, @@ -94,11 +94,11 @@ class Chat(object): ) def is_self_talk(self) -> bool: - """Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise""" + """Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise.""" return bool(lib.dc_chat_is_self_talk(self._dc_chat)) def is_device_talk(self) -> bool: - """Returns True if this chat is the "Device Messages" chat, False otherwise""" + """Returns True if this chat is the "Device Messages" chat, False otherwise.""" return bool(lib.dc_chat_is_device_talk(self._dc_chat)) def is_muted(self) -> bool: @@ -109,12 +109,12 @@ class Chat(object): return bool(lib.dc_chat_is_muted(self._dc_chat)) def is_pinned(self) -> bool: - """Return True if this chat is pinned, False otherwise""" + """Return True if this chat is pinned, False otherwise.""" return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_PINNED def is_archived(self) -> bool: """Return True if this chat is archived, False otherwise. - :returns: True if archived, False otherwise + :returns: True if archived, False otherwise. """ return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED @@ -136,7 +136,7 @@ class Chat(object): def can_send(self) -> bool: """Check if messages can be sent to a give chat. - This is not true eg. for the contact requests or for the device-talk + This is not true eg. for the contact requests or for the device-talk. :returns: True if the chat is writable, False otherwise """ @@ -167,7 +167,7 @@ class Chat(object): def get_color(self): """return the color of the chat. - :returns: color as 0x00rrggbb + :returns: color as 0x00rrggbb. """ return lib.dc_chat_get_color(self._dc_chat) @@ -178,21 +178,18 @@ class Chat(object): return json.loads(s) def mute(self, duration: Optional[int] = None) -> None: - """mutes the chat + """mutes the chat. :param duration: Number of seconds to mute the chat for. None to mute until unmuted again. :returns: None """ - if duration is None: - mute_duration = -1 - else: - mute_duration = duration + mute_duration = -1 if duration is None else duration ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration) if not bool(ret): raise ValueError("Call to dc_set_chat_mute_duration failed") def unmute(self) -> None: - """unmutes the chat + """unmutes the chat. :returns: None """ @@ -252,7 +249,8 @@ class Chat(object): def get_encryption_info(self) -> Optional[str]: """Return encryption info for this chat. - :returns: a string with encryption preferences of all chat members""" + :returns: a string with encryption preferences of all chat members + """ res = lib.dc_get_chat_encrinfo(self.account._dc_context, self.id) return from_dc_charpointer(res) @@ -463,7 +461,7 @@ class Chat(object): def get_contacts(self): """get all contacts for this chat. - :returns: list of :class:`deltachat.contact.Contact` objects for this chat + :returns: list of :class:`deltachat.contact.Contact` objects for this chat. """ from .contact import Contact @@ -547,19 +545,10 @@ class Chat(object): :param timespan_to: a datetime object or None (indicating up till now) :returns: list of :class:`deltachat.chat.Location` objects. """ - if timestamp_from is None: - time_from = 0 - else: - time_from = calendar.timegm(timestamp_from.utctimetuple()) - if timestamp_to is None: - time_to = 0 - else: - time_to = calendar.timegm(timestamp_to.utctimetuple()) + time_from = 0 if timestamp_from is None else calendar.timegm(timestamp_from.utctimetuple()) + time_to = 0 if timestamp_to is None else calendar.timegm(timestamp_to.utctimetuple()) - if contact is None: - contact_id = 0 - else: - contact_id = contact.id + contact_id = 0 if contact is None else contact.id dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to) return [ diff --git a/python/src/deltachat/contact.py b/python/src/deltachat/contact.py index 75c44b8e5..adf4cdbad 100644 --- a/python/src/deltachat/contact.py +++ b/python/src/deltachat/contact.py @@ -1,4 +1,4 @@ -""" Contact object. """ +"""Contact object.""" from datetime import date, datetime, timezone from typing import Optional @@ -28,7 +28,7 @@ class Contact(object): return self.account._dc_context == other.account._dc_context and self.id == other.id def __ne__(self, other): - return not (self == other) + return not self == other def __repr__(self): return "".format(self.id, self.addr, self.account._dc_context) @@ -76,7 +76,7 @@ class Contact(object): return lib.dc_contact_is_verified(self._dc_contact) def get_verifier(self, contact): - """Return the address of the contact that verified the contact""" + """Return the address of the contact that verified the contact.""" return from_dc_charpointer(lib.dc_contact_get_verifier_addr(contact._dc_contact)) def get_profile_image(self) -> Optional[str]: diff --git a/python/src/deltachat/direct_imap.py b/python/src/deltachat/direct_imap.py index 5c36d47f1..f2f4417c5 100644 --- a/python/src/deltachat/direct_imap.py +++ b/python/src/deltachat/direct_imap.py @@ -79,15 +79,17 @@ class DirectImap: def select_config_folder(self, config_name: str): """Return info about selected folder if it is - configured, otherwise None.""" + configured, otherwise None. + """ if "_" not in config_name: config_name = "configured_{}_folder".format(config_name) foldername = self.account.get_config(config_name) if foldername: return self.select_folder(foldername) + return None def list_folders(self) -> List[str]: - """return list of all existing folder names""" + """return list of all existing folder names.""" assert not self._idling return [folder.name for folder in self.conn.folder.list()] @@ -103,7 +105,7 @@ class DirectImap: def get_all_messages(self) -> List[MailMessage]: assert not self._idling - return [mail for mail in self.conn.fetch()] + return list(self.conn.fetch()) def get_unread_messages(self) -> List[str]: assert not self._idling @@ -221,5 +223,4 @@ class IdleManager: def done(self): """send idle-done to server if we are currently in idle mode.""" - res = self.direct_imap.conn.idle.stop() - return res + return self.direct_imap.conn.idle.stop() diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 49ef23f96..9cd1caefe 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -32,12 +32,11 @@ class FFIEvent: def __str__(self): if self.name == "DC_EVENT_INFO": return "INFO {data2}".format(data2=self.data2) - elif self.name == "DC_EVENT_WARNING": + if self.name == "DC_EVENT_WARNING": return "WARNING {data2}".format(data2=self.data2) - elif self.name == "DC_EVENT_ERROR": + if self.name == "DC_EVENT_ERROR": return "ERROR {data2}".format(data2=self.data2) - else: - return "{name} data1={data1} data2={data2}".format(**self.__dict__) + return "{name} data1={data1} data2={data2}".format(**self.__dict__) class FFIEventLogger: @@ -135,7 +134,8 @@ class FFIEventTracker: def wait_for_connectivity(self, connectivity): """Wait for the specified connectivity. This only works reliably if the connectivity doesn't change - again too quickly, otherwise we might miss it.""" + again too quickly, otherwise we might miss it. + """ while 1: if self.account.get_connectivity() == connectivity: return @@ -143,12 +143,13 @@ class FFIEventTracker: def wait_for_connectivity_change(self, previous, expected_next): """Wait until the connectivity changes to `expected_next`. - Fails the test if it changes to something else.""" + Fails the test if it changes to something else. + """ while 1: current = self.account.get_connectivity() if current == expected_next: return - elif current != previous: + if current != previous: raise Exception("Expected connectivity " + str(expected_next) + " but got " + str(current)) self.get_matching("DC_EVENT_CONNECTIVITY_CHANGED") @@ -183,7 +184,8 @@ class FFIEventTracker: - ac1 and ac2 are created - ac1 sends a message to ac2 - ac2 is still running FetchExsistingMsgs job and thinks it's an existing, old message - - therefore no DC_EVENT_INCOMING_MSG is sent""" + - therefore no DC_EVENT_INCOMING_MSG is sent + """ self.get_info_contains("INBOX: Idle entering") def wait_next_incoming_message(self): @@ -193,14 +195,15 @@ class FFIEventTracker: def wait_next_messages_changed(self): """wait for and return next message-changed message or None - if the event contains no msgid""" + if the event contains no msgid + """ ev = self.get_matching("DC_EVENT_MSGS_CHANGED") if ev.data2 > 0: return self.account.get_message_by_id(ev.data2) return None def wait_next_reactions_changed(self): - """wait for and return next reactions-changed message""" + """wait for and return next reactions-changed message.""" ev = self.get_matching("DC_EVENT_REACTIONS_CHANGED") assert ev.data1 > 0 return self.account.get_message_by_id(ev.data2) @@ -292,10 +295,10 @@ class EventThread(threading.Thread): if data1 == 0 or data1 == 1000: success = data1 == 1000 comment = ffi_event.data2 - yield "ac_configure_completed", dict(success=success, comment=comment) + yield "ac_configure_completed", {"success": success, "comment": comment} elif name == "DC_EVENT_INCOMING_MSG": msg = account.get_message_by_id(ffi_event.data2) - yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg)) + yield map_system_message(msg) or ("ac_incoming_message", {"message": msg}) elif name == "DC_EVENT_MSGS_CHANGED": if ffi_event.data2 != 0: msg = account.get_message_by_id(ffi_event.data2) @@ -303,19 +306,19 @@ class EventThread(threading.Thread): res = map_system_message(msg) if res and res[0].startswith("ac_member"): yield res - yield "ac_outgoing_message", dict(message=msg) + yield "ac_outgoing_message", {"message": msg} elif msg.is_in_fresh(): yield map_system_message(msg) or ( "ac_incoming_message", - dict(message=msg), + {"message": msg}, ) elif name == "DC_EVENT_REACTIONS_CHANGED": assert ffi_event.data1 > 0 msg = account.get_message_by_id(ffi_event.data2) - yield "ac_reactions_changed", dict(message=msg) + yield "ac_reactions_changed", {"message": msg} elif name == "DC_EVENT_MSG_DELIVERED": msg = account.get_message_by_id(ffi_event.data2) - yield "ac_message_delivered", dict(message=msg) + yield "ac_message_delivered", {"message": msg} elif name == "DC_EVENT_CHAT_MODIFIED": chat = account.get_chat_by_id(ffi_event.data1) - yield "ac_chat_modified", dict(chat=chat) + yield "ac_chat_modified", {"chat": chat} diff --git a/python/src/deltachat/hookspec.py b/python/src/deltachat/hookspec.py index 4d2fe6960..51d4a9c83 100644 --- a/python/src/deltachat/hookspec.py +++ b/python/src/deltachat/hookspec.py @@ -1,4 +1,4 @@ -""" Hooks for Python bindings to Delta Chat Core Rust CFFI""" +"""Hooks for Python bindings to Delta Chat Core Rust CFFI.""" import pluggy diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index 0891b46cf..0e083ad86 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -1,4 +1,4 @@ -""" The Message object. """ +"""The Message object.""" import json import os @@ -59,10 +59,7 @@ class Message(object): :param view_type: the message type code or one of the strings: "text", "audio", "video", "file", "sticker", "videochat", "webxdc" """ - if isinstance(view_type, int): - view_type_code = view_type - else: - view_type_code = get_viewtype_code_from_name(view_type) + view_type_code = view_type if isinstance(view_type, int) else get_viewtype_code_from_name(view_type) return Message( account, ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref), @@ -129,7 +126,7 @@ class Message(object): @props.with_doc def filemime(self) -> str: - """mime type of the file (if it exists)""" + """mime type of the file (if it exists).""" return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg)) def get_status_updates(self, serial: int = 0) -> list: @@ -141,7 +138,7 @@ class Message(object): :param serial: The last known serial. Pass 0 if there are no known serials to receive all updates. """ return json.loads( - from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial)) + from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial)), ) def send_status_update(self, json_data: Union[str, dict], description: str) -> bool: @@ -158,8 +155,11 @@ class Message(object): json_data = json.dumps(json_data, default=str) return bool( lib.dc_send_webxdc_status_update( - self.account._dc_context, self.id, as_dc_charpointer(json_data), as_dc_charpointer(description) - ) + self.account._dc_context, + self.id, + as_dc_charpointer(json_data), + as_dc_charpointer(description), + ), ) def send_reaction(self, reaction: str): @@ -232,16 +232,18 @@ class Message(object): ts = lib.dc_msg_get_received_timestamp(self._dc_msg) if ts: return datetime.fromtimestamp(ts, timezone.utc) + return None @props.with_doc def ephemeral_timer(self): - """Ephemeral timer in seconds + """Ephemeral timer in seconds. :returns: timer in seconds or None if there is no timer """ timer = lib.dc_msg_get_ephemeral_timer(self._dc_msg) if timer: return timer + return None @props.with_doc def ephemeral_timestamp(self): @@ -255,23 +257,25 @@ class Message(object): @property def quoted_text(self) -> Optional[str]: - """Text inside the quote + """Text inside the quote. - :returns: Quoted text""" + :returns: Quoted text + """ return from_optional_dc_charpointer(lib.dc_msg_get_quoted_text(self._dc_msg)) @property def quote(self): - """Quote getter + """Quote getter. - :returns: Quoted message, if found in the database""" + :returns: Quoted message, if found in the database + """ msg = lib.dc_msg_get_quoted_msg(self._dc_msg) if msg: return Message(self.account, ffi.gc(msg, lib.dc_msg_unref)) @quote.setter def quote(self, quoted_message): - """Quote setter""" + """Quote setter.""" lib.dc_msg_set_quote(self._dc_msg, quoted_message._dc_msg) def force_plaintext(self) -> None: @@ -286,7 +290,7 @@ class Message(object): :returns: email-mime message object (with headers only, no body). """ - import email.parser + import email mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id) if mime_headers: @@ -297,7 +301,7 @@ class Message(object): @property def error(self) -> Optional[str]: - """Error message""" + """Error message.""" return from_optional_dc_charpointer(lib.dc_msg_get_error(self._dc_msg)) @property @@ -493,7 +497,8 @@ def get_viewtype_code_from_name(view_type_name): if code is not None: return code raise ValueError( - "message typecode not found for {!r}, " "available {!r}".format(view_type_name, list(_view_type_mapping.keys())) + "message typecode not found for {!r}, " + "available {!r}".format(view_type_name, list(_view_type_mapping.keys())), ) @@ -506,14 +511,11 @@ def map_system_message(msg): if msg.is_system_message(): res = parse_system_add_remove(msg.text) if not res: - return + return None action, affected, actor = res affected = msg.account.get_contact_by_addr(affected) - if actor == "me": - actor = None - else: - actor = msg.account.get_contact_by_addr(actor) - d = dict(chat=msg.chat, contact=affected, actor=actor, message=msg) + actor = None if actor == "me" else msg.account.get_contact_by_addr(actor) + d = {"chat": msg.chat, "contact": affected, "actor": actor, "message": msg} return "ac_member_" + res[0], d @@ -528,8 +530,8 @@ def extract_addr(text): def parse_system_add_remove(text): """return add/remove info from parsing the given system message text. - returns a (action, affected, actor) triple""" - + returns a (action, affected, actor) triple + """ # You removed member a@b. # You added member a@b. # Member Me (x@y) removed by a@b. diff --git a/python/src/deltachat/props.py b/python/src/deltachat/props.py index ab21794ae..7dcf4a1aa 100644 --- a/python/src/deltachat/props.py +++ b/python/src/deltachat/props.py @@ -8,7 +8,7 @@ def with_doc(f): # copied over unmodified from # https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py def cached(f): - """returns a cached property that is calculated by function f""" + """returns a cached property that is calculated by function f.""" def get(self): try: @@ -17,8 +17,9 @@ def cached(f): self._property_cache = {} except KeyError: pass - x = self._property_cache[f] = f(self) - return x + res = f(self) + self._property_cache[f] = res + return res def set(self, val): propcache = self.__dict__.setdefault("_property_cache", {}) diff --git a/python/src/deltachat/provider.py b/python/src/deltachat/provider.py index 76d5cf780..d760dbabb 100644 --- a/python/src/deltachat/provider.py +++ b/python/src/deltachat/provider.py @@ -9,7 +9,8 @@ class ProviderNotFoundError(Exception): class Provider(object): - """Provider information. + """ + Provider information. :param domain: The email to get the provider info for. """ diff --git a/python/src/deltachat/reactions.py b/python/src/deltachat/reactions.py index 9e9ed9555..92cdf1905 100644 --- a/python/src/deltachat/reactions.py +++ b/python/src/deltachat/reactions.py @@ -1,4 +1,4 @@ -""" The Reactions object. """ +"""The Reactions object.""" from .capi import ffi, lib from .cutil import from_dc_charpointer, iter_array diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index ed92917b6..b61c1a0e4 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -29,7 +29,7 @@ def pytest_addoption(parser): "--liveconfig", action="store", default=None, - help="a file with >=2 lines where each line " "contains NAME=VALUE config settings for one account", + help="a file with >=2 lines where each line contains NAME=VALUE config settings for one account", ) group.addoption( "--ignored", @@ -124,7 +124,7 @@ def pytest_report_header(config, startdir): info["deltachat_core_version"], info["sqlite_version"], info["journal_mode"], - ) + ), ] cfg = config.option.liveconfig @@ -180,7 +180,7 @@ class TestProcess: if res.status_code != 200: pytest.fail("newtmpuser count={} code={}: '{}'".format(index, res.status_code, res.text)) d = res.json() - config = dict(addr=d["email"], mail_pw=d["password"]) + config = {"addr": d["email"], "mail_pw": d["password"]} print("newtmpuser {}: addr={}".format(index, config["addr"])) self._configlist.append(config) yield config @@ -229,7 +229,7 @@ def write_dict_to_dir(dic, target_dir): path.write_bytes(content) -@pytest.fixture +@pytest.fixture() def data(request): class Data: def __init__(self) -> None: @@ -253,6 +253,7 @@ def data(request): if os.path.exists(fn): return fn print("WARNING: path does not exist: {!r}".format(fn)) + return None def read_path(self, bn, mode="r"): fn = self.get_path(bn) @@ -264,8 +265,11 @@ def data(request): class ACSetup: - """accounts setup helper to deal with multiple configure-process - and io & imap initialization phases. From tests, use the higher level + """ + Accounts setup helper to deal with multiple configure-process + and io & imap initialization phases. + + From tests, use the higher level public ACFactory methods instead of its private helper class. """ @@ -289,7 +293,7 @@ class ACSetup: self._account2state[account] = self.CONFIGURED self.log("added already configured account", account, account.get_config("addr")) - def start_configure(self, account, reconfigure=False): + def start_configure(self, account): """add an account and start its configure process.""" class PendingTracker: @@ -299,7 +303,7 @@ class ACSetup: account.add_account_plugin(PendingTracker(), name="pending_tracker") self._account2state[account] = self.CONFIGURING - account.configure(reconfigure=reconfigure) + account.configure() self.log("started configure on", account) def wait_one_configured(self, account): @@ -411,7 +415,8 @@ class ACFactory: acc.disable_logging() def get_next_liveconfig(self): - """Base function to get functional online configurations + """ + Base function to get functional online configurations where we can make valid SMTP and IMAP connections with. """ configdict = next(self._liveconfig_producer).copy() @@ -465,8 +470,7 @@ class ACFactory: if fname_pub and fname_sec: account._preconfigure_keypair(addr, fname_pub, fname_sec) return True - else: - print("WARN: could not use preconfigured keys for {!r}".format(addr)) + print("WARN: could not use preconfigured keys for {!r}".format(addr)) def get_pseudo_configured_account(self, passphrase: Optional[str] = None) -> Account: # do a pseudo-configured account @@ -476,14 +480,14 @@ class ACFactory: acname = ac._logid addr = "{}@offline.org".format(acname) ac.update_config( - dict( - addr=addr, - displayname=acname, - mail_pw="123", - configured_addr=addr, - configured_mail_pw="123", - configured="1", - ) + { + "addr": addr, + "displayname": acname, + "mail_pw": "123", + "configured_addr": addr, + "configured_mail_pw": "123", + "configured": "1", + }, ) self._preconfigure_key(ac, addr) self._acsetup.init_logging(ac) @@ -494,12 +498,12 @@ class ACFactory: configdict = self.get_next_liveconfig() else: # XXX we might want to transfer the key to the new account - configdict = dict( - addr=cloned_from.get_config("addr"), - mail_pw=cloned_from.get_config("mail_pw"), - imap_certificate_checks=cloned_from.get_config("imap_certificate_checks"), - smtp_certificate_checks=cloned_from.get_config("smtp_certificate_checks"), - ) + configdict = { + "addr": cloned_from.get_config("addr"), + "mail_pw": cloned_from.get_config("mail_pw"), + "imap_certificate_checks": cloned_from.get_config("imap_certificate_checks"), + "smtp_certificate_checks": cloned_from.get_config("smtp_certificate_checks"), + } configdict.update(kwargs) ac = self._get_cached_account(addr=configdict["addr"]) if cache else None if ac is not None: @@ -600,7 +604,7 @@ class ACFactory: acc._evtracker.wait_next_incoming_message() -@pytest.fixture +@pytest.fixture() def acfactory(request, tmpdir, testprocess, data): am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data) yield am @@ -665,12 +669,12 @@ class BotProcess: ignored.append(line) -@pytest.fixture +@pytest.fixture() def tmp_db_path(tmpdir): return tmpdir.join("test.db").strpath -@pytest.fixture +@pytest.fixture() def lp(): class Printer: def sec(self, msg: str) -> None: diff --git a/python/src/deltachat/tracker.py b/python/src/deltachat/tracker.py index 9f9cb4c53..199e5d5c9 100644 --- a/python/src/deltachat/tracker.py +++ b/python/src/deltachat/tracker.py @@ -77,11 +77,11 @@ class ConfigureTracker: self.account.remove_account_plugin(self) def wait_smtp_connected(self): - """wait until smtp is configured.""" + """Wait until SMTP is configured.""" self._smtp_finished.wait() def wait_imap_connected(self): - """wait until smtp is configured.""" + """Wait until IMAP is configured.""" self._imap_finished.wait() def wait_progress(self, data1=None): @@ -91,7 +91,8 @@ class ConfigureTracker: break def wait_finish(self, timeout=None): - """wait until configure is completed. + """ + Wait until configure is completed. Raise Exception if Configure failed """ diff --git a/python/tests/auditwheels.py b/python/tests/auditwheels.py index 1c34ab692..a56ca5821 100644 --- a/python/tests/auditwheels.py +++ b/python/tests/auditwheels.py @@ -15,5 +15,5 @@ if __name__ == "__main__": p, "-w", workspacedir, - ] + ], ) diff --git a/python/tests/test_0_complex_or_slow.py b/python/tests/test_0_complex_or_slow.py index 43e2b16c7..49e0ddf38 100644 --- a/python/tests/test_0_complex_or_slow.py +++ b/python/tests/test_0_complex_or_slow.py @@ -216,7 +216,7 @@ def test_fetch_existing(acfactory, lp, mvbox_move): # would also find the "Sent" folder, but it would be too late: # The sentbox thread, started by `start_io()`, would have seen that there is no # ConfiguredSentboxFolder and do nothing. - acfactory._acsetup.start_configure(ac1, reconfigure=True) + acfactory._acsetup.start_configure(ac1) acfactory.bring_accounts_online() assert_folders_configured(ac1) diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index 32a355fad..42cb0f298 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -32,7 +32,7 @@ def test_basic_imap_api(acfactory, tmpdir): imap2.shutdown() -@pytest.mark.ignored +@pytest.mark.ignored() def test_configure_generate_key(acfactory, lp): # A slow test which will generate new keys. acfactory.remove_preconfigured_keys() @@ -510,7 +510,7 @@ def test_send_and_receive_message_markseen(acfactory, lp): idle2.wait_for_seen() lp.step("1") - for i in range(2): + for _i in range(2): ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ") assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL @@ -529,7 +529,7 @@ def test_send_and_receive_message_markseen(acfactory, lp): pass # mark_seen_messages() has generated events before it returns -def test_moved_markseen(acfactory, lp): +def test_moved_markseen(acfactory): """Test that message already moved to DeltaChat folder is marked as seen.""" ac1 = acfactory.new_online_configuring_account() ac2 = acfactory.new_online_configuring_account(mvbox_move=True) @@ -553,7 +553,7 @@ def test_moved_markseen(acfactory, lp): ac2.mark_seen_messages([msg]) uid = idle2.wait_for_seen() - assert len([a for a in ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*")))]) == 1 + assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True, uid=U(uid, "*"))))) == 1 def test_message_override_sender_name(acfactory, lp): @@ -832,7 +832,7 @@ def test_send_first_message_as_long_unicode_with_cr(acfactory, lp): lp.sec("sending multi-line non-unicode message from ac1 to ac2") text1 = ( "hello\nworld\nthis is a very long message that should be" - + " wrapped using format=flowed and unwrapped on the receiver" + " wrapped using format=flowed and unwrapped on the receiver" ) msg_out = chat.send_text(text1) assert not msg_out.is_encrypted() @@ -894,7 +894,7 @@ def test_dont_show_emails(acfactory, lp): message in Drafts that is moved to Sent later """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -908,7 +908,7 @@ def test_dont_show_emails(acfactory, lp): message in Sent """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -922,7 +922,7 @@ def test_dont_show_emails(acfactory, lp): Unknown message in Spam """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -936,7 +936,7 @@ def test_dont_show_emails(acfactory, lp): Unknown & malformed message in Spam """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -950,7 +950,7 @@ def test_dont_show_emails(acfactory, lp): Unknown & malformed message in Spam """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -964,7 +964,7 @@ def test_dont_show_emails(acfactory, lp): Actually interesting message in Spam """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) ac1.direct_imap.append( @@ -978,7 +978,7 @@ def test_dont_show_emails(acfactory, lp): Unknown message in Junk """.format( - ac1.get_config("configured_addr") + ac1.get_config("configured_addr"), ), ) @@ -1710,7 +1710,7 @@ def test_system_group_msg_from_blocked_user(acfactory, lp): assert contact.is_blocked() chat_on_ac2.remove_contact(ac1) ac1._evtracker.get_matching("DC_EVENT_CHAT_MODIFIED") - assert not ac1.get_self_contact() in chat_on_ac1.get_contacts() + assert ac1.get_self_contact() not in chat_on_ac1.get_contacts() def test_set_get_group_image(acfactory, data, lp): @@ -1784,7 +1784,7 @@ def test_connectivity(acfactory, lp): lp.sec( "Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, " - + "all messages are fetched" + "all messages are fetched", ) ac1.direct_imap.select_config_folder("inbox") @@ -2146,7 +2146,7 @@ def test_group_quote(acfactory, lp): @pytest.mark.parametrize( - "folder,move,expected_destination,", + ("folder", "move", "expected_destination"), [ ( "xyz", @@ -2298,7 +2298,7 @@ class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): configdict = acfactory.get_next_liveconfig() ac1 = acfactory.get_unconfigured_account() - ac1.update_config(dict(addr=configdict["addr"], mail_pw="123")) + ac1.update_config({"addr": configdict["addr"], "mail_pw": "123"}) configtracker = ac1.configure() configtracker.wait_progress(500) configtracker.wait_progress(0) diff --git a/python/tests/test_3_offline.py b/python/tests/test_3_offline.py index 98890287a..97dedfe37 100644 --- a/python/tests/test_3_offline.py +++ b/python/tests/test_3_offline.py @@ -15,7 +15,7 @@ from deltachat.tracker import ImexFailed @pytest.mark.parametrize( - "msgtext,res", + ("msgtext", "res"), [ ( "Member Me (tmp1@x.org) removed by tmp2@x.org.", @@ -108,7 +108,7 @@ class TestOfflineAccountBasic: def test_update_config(self, acfactory): ac1 = acfactory.get_unconfigured_account() - ac1.update_config(dict(mvbox_move=False)) + ac1.update_config({"mvbox_move": False}) assert ac1.get_config("mvbox_move") == "0" def test_has_savemime(self, acfactory): @@ -229,11 +229,11 @@ class TestOfflineContact: class TestOfflineChat: - @pytest.fixture + @pytest.fixture() def ac1(self, acfactory): return acfactory.get_pseudo_configured_account() - @pytest.fixture + @pytest.fixture() def chat1(self, ac1): return ac1.create_contact("some1@example.org", name="some1").create_chat() @@ -257,7 +257,7 @@ class TestOfflineChat: assert chat2.id == chat1.id assert chat2.get_name() == chat1.get_name() assert chat1 == chat2 - assert not (chat1 != chat2) + assert not chat1.__ne__(chat2) assert chat1 != chat3 for ichat in ac1.get_chats(): @@ -450,7 +450,7 @@ class TestOfflineChat: assert msg.filemime == "image/png" @pytest.mark.parametrize( - "fn,typein,typeout", + ("fn", "typein", "typeout"), [ ("r", None, "application/octet-stream"), ("r.txt", None, "text/plain"), @@ -458,7 +458,7 @@ class TestOfflineChat: ("r.txt", "image/png", "image/png"), ], ) - def test_message_file(self, ac1, chat1, data, lp, fn, typein, typeout): + def test_message_file(self, chat1, data, lp, fn, typein, typeout): lp.sec("sending file") fp = data.get_path(fn) msg = chat1.send_file(fp, typein) @@ -694,7 +694,7 @@ class TestOfflineChat: chat1.set_draft(None) assert chat1.get_draft() is None - def test_qr_setup_contact(self, acfactory, lp): + def test_qr_setup_contact(self, acfactory): ac1 = acfactory.get_pseudo_configured_account() ac2 = acfactory.get_pseudo_configured_account() qr = ac1.get_setup_contact_qr() diff --git a/python/tests/test_4_lowlevel.py b/python/tests/test_4_lowlevel.py index 7b0acd822..cb517670c 100644 --- a/python/tests/test_4_lowlevel.py +++ b/python/tests/test_4_lowlevel.py @@ -93,7 +93,7 @@ def test_empty_context(): capi.lib.dc_context_unref(ctx) -def test_dc_close_events(tmpdir, acfactory): +def test_dc_close_events(acfactory): ac1 = acfactory.get_unconfigured_account() # register after_shutdown function diff --git a/python/tox.ini b/python/tox.ini index b182c41a1..d01dd82e4 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -50,18 +50,14 @@ commands = skipsdist = True skip_install = True deps = - flake8 -# isort 5.11.0 is broken: https://github.com/PyCQA/isort/issues/2031 - isort<5.11.0 + ruff black # pygments required by rst-lint pygments restructuredtext_lint commands = - isort --check setup.py install_python_bindings.py src/deltachat examples/ tests/ black --check setup.py install_python_bindings.py src/deltachat examples/ tests/ - flake8 src/deltachat - flake8 tests/ examples/ + ruff src/deltachat tests/ examples/ rst-lint --encoding 'utf-8' README.rst [testenv:mypy] @@ -102,7 +98,3 @@ timeout = 150 timeout_func_only = True markers = ignored: ignore this test in default test runs, use --ignored to run. - -[flake8] -max-line-length = 120 -ignore = E203, E266, E501, W503 diff --git a/scripts/cleanup_devpi_indices.py b/scripts/cleanup_devpi_indices.py index 8e1eaa9ee..b32a50474 100644 --- a/scripts/cleanup_devpi_indices.py +++ b/scripts/cleanup_devpi_indices.py @@ -2,12 +2,13 @@ Remove old "dc" indices except for master which always stays. """ -from requests import Session import datetime -import sys import subprocess +import sys -MAXDAYS=7 +from requests import Session + +MAXDAYS = 7 session = Session() session.headers["Accept"] = "application/json" @@ -54,7 +55,8 @@ def run(): if not dates: print( "%s has no releases" % (baseurl + username + "/" + indexname), - file=sys.stderr) + file=sys.stderr, + ) date = datetime.datetime.now() else: date = datetime.datetime(*max(dates)) @@ -67,6 +69,5 @@ def run(): subprocess.check_call(["devpi", "index", "-y", "--delete", url]) - -if __name__ == '__main__': +if __name__ == "__main__": run() diff --git a/scripts/set_core_version.py b/scripts/set_core_version.py index 9a46a8278..8046081fa 100755 --- a/scripts/set_core_version.py +++ b/scripts/set_core_version.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -import os import json -import re +import os import pathlib +import re import subprocess from argparse import ArgumentParser @@ -23,7 +23,7 @@ def read_toml_version(relpath): res = regex_matches(relpath, rex) if res is not None: return res.group(1) - raise ValueError("no version found in {}".format(relpath)) + raise ValueError(f"no version found in {relpath}") def replace_toml_version(relpath, newversion): @@ -34,8 +34,8 @@ def replace_toml_version(relpath, newversion): for line in open(str(p)): m = rex.match(line) if m is not None: - print("{}: set version={}".format(relpath, newversion)) - f.write('version = "{}"\n'.format(newversion)) + print(f"{relpath}: set version={newversion}") + f.write(f'version = "{newversion}"\n') else: f.write(line) os.rename(tmp_path, str(p)) @@ -44,7 +44,7 @@ def replace_toml_version(relpath, newversion): def read_json_version(relpath): p = pathlib.Path(relpath) assert p.exists() - with open(p, "r") as f: + with open(p) as f: json_data = json.loads(f.read()) return json_data["version"] @@ -52,7 +52,7 @@ def read_json_version(relpath): def update_package_json(relpath, newversion): p = pathlib.Path(relpath) assert p.exists() - with open(p, "r") as f: + with open(p) as f: json_data = json.loads(f.read()) json_data["version"] = newversion with open(p, "w") as f: @@ -63,7 +63,7 @@ def main(): parser = ArgumentParser(prog="set_core_version") parser.add_argument("newversion") - json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"] + json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"] toml_list = [ "Cargo.toml", "deltachat-ffi/Cargo.toml", @@ -75,9 +75,9 @@ def main(): except SystemExit: print() for x in toml_list: - print("{}: {}".format(x, read_toml_version(x))) + print(f"{x}: {read_toml_version(x)}") for x in json_list: - print("{}: {}".format(x, read_json_version(x))) + print(f"{x}: {read_json_version(x)}") print() raise SystemExit("need argument: new version, example: 1.25.0") @@ -92,19 +92,19 @@ def main(): if "alpha" not in newversion: for line in open("CHANGELOG.md"): ## 1.25.0 - if line.startswith("## "): - if line[2:].strip().startswith(newversion): - break + if line.startswith("## ") and line[2:].strip().startswith(newversion): + break else: - raise SystemExit("CHANGELOG.md contains no entry for version: {}".format(newversion)) + raise SystemExit( + f"CHANGELOG.md contains no entry for version: {newversion}" + ) for toml_filename in toml_list: replace_toml_version(toml_filename, newversion) - + for json_filename in json_list: update_package_json(json_filename, newversion) - print("running cargo check") subprocess.call(["cargo", "check"]) @@ -114,13 +114,12 @@ def main(): print("after commit, on master make sure to: ") print("") - print(" git tag -a {}".format(newversion)) - print(" git push origin {}".format(newversion)) - print(" git tag -a py-{}".format(newversion)) - print(" git push origin py-{}".format(newversion)) + print(f" git tag -a {newversion}") + print(f" git push origin {newversion}") + print(f" git tag -a py-{newversion}") + print(f" git push origin py-{newversion}") print("") if __name__ == "__main__": main() -