From d9e8b248da20099c8a6617cf1e0820e294e58276 Mon Sep 17 00:00:00 2001 From: EINDEX Date: Sat, 28 Jun 2025 20:22:34 +1000 Subject: [PATCH 01/11] feat: let simple router support sender rect combine routing --- src/mailrise/simple_router.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/mailrise/simple_router.py b/src/mailrise/simple_router.py index c13812b..a35eb73 100644 --- a/src/mailrise/simple_router.py +++ b/src/mailrise/simple_router.py @@ -111,13 +111,16 @@ def __init__(self, senders: typ.List[typ.Tuple[_Key, _SimpleSender]]): async def email_to_apprise( self, logger: Logger, email: EmailMessage, auth_data: typ.Any, **kwargs) \ -> typ.AsyncGenerator[AppriseNotification, None]: - for addr in email.to: + + sender_addr = email.from_ + for addr in [email.to]: try: + sender_mail = _parsercpt(sender_addr) rcpt = _parsercpt(addr) except ValueError: logger.error('Not a valid Mailrise address: %s', addr) continue - sender = self.get_sender(rcpt.key) + sender = self.get_sender(sender_mail.key, rcpt.key) if sender is None: logger.error('Recipient is not configured: %s', addr) continue @@ -141,12 +144,14 @@ async def email_to_apprise( attachments=email.attachments ) - def get_sender(self, key: _Key) -> _SimpleSender | None: + def get_sender(self, sender_key: _Key, rect_key: _Key) -> _SimpleSender | None: """Find a sender by recipient key.""" return next( - (sender for (pattern_key, sender) in self.senders - if fnmatchcase(key.user, pattern_key.user) - and fnmatchcase(key.domain, pattern_key.domain)), None) + (sender for (sender_pattern_key, rect_pattern_key, sender) in self.senders + if fnmatchcase(sender_key.user, sender_pattern_key.user) + and fnmatchcase(sender_key.domain, sender_pattern_key.domain) + and fnmatchcase(rect_key.user, rect_pattern_key.user) + and fnmatchcase(rect_key.domain, rect_pattern_key.domain)), None) def load_from_yaml(logger: Logger, configs_node: dict[str, typ.Any]) -> SimpleRouter: @@ -155,8 +160,8 @@ def load_from_yaml(logger: Logger, configs_node: dict[str, typ.Any]) -> SimpleRo logger.critical('The configs node is not a YAML mapping') raise SystemExit(1) router = SimpleRouter( - senders=[(_parse_simple_key(logger, key), _load_simple_sender(logger, key, config)) - for key, config in configs_node.items()] + senders=[(_parse_simple_key(logger, key), _parse_simple_key(logger, rev), _load_simple_sender(logger, key, config)) + for key, rev_config in configs_node.items() for rev, config in rev_config.items()] ) if len(router.senders) < 1: logger.critical('No Apprise targets are configured') From ed3b2c877393cfa01406d3bd0588ad8a0564c78a Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 7 Nov 2025 23:42:51 +0000 Subject: [PATCH 02/11] build: update dev container definition --- .devcontainer/Dockerfile | 21 ------------- .devcontainer/devcontainer.json | 52 ++++++++------------------------- .github/dependabot.yml | 4 +++ 3 files changed, 16 insertions(+), 61 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 58ea154..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/python-3/.devcontainer/base.Dockerfile - -# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster -ARG VARIANT="3.10-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} - -# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 -ARG NODE_VERSION="none" -RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi - -# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. -# COPY requirements.txt /tmp/pip-tmp/ -# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ -# && rm -rf /tmp/pip-tmp - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a6868fb..7c368dc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,50 +1,22 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/python-3 +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python { "name": "Python 3", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "args": { - // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local on arm64/Apple Silicon. - "VARIANT": "3", - // Options - "NODE_VERSION": "none" - } - }, + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3" - // Set *default* container specific settings.json values on container create. - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "TatsuyaNakamori.resttext" - ], + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "pip3 install --user -r requirements.txt", - "postCreateCommand": "sudo pip install --editable .[testing] tox", - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" -} + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f44c7ac..5660e78 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,7 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly From 7bd4445ba5f9982f0fdbd117a1181793e84bfae4 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 7 Nov 2025 23:43:12 +0000 Subject: [PATCH 03/11] chore: drop license classifier per current practice --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 28da936..d2c959d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,6 @@ classifiers = Development Status :: 5 - Production/Stable Programming Language :: Python Programming Language :: Python :: 3 - License :: OSI Approved :: MIT License Topic :: Communications :: Email Environment :: No Input/Output (Daemon) Intended Audience :: System Administrators From 275fc4ac5ad6f822195ed9ba9be95ce099b8e151 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 7 Nov 2025 23:43:43 +0000 Subject: [PATCH 04/11] build(deps): don't version lock dependencies --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index d2c959d..e6e9ffb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,9 +52,9 @@ python_requires = >=3.8 # new major versions. This works if the required packages follow Semantic Versioning. # For more information, check out https://semver.org/. install_requires = - apprise==1.7.1 - aiosmtpd==1.4.4.post2 - PyYAML==6.0.1 + apprise>=0.9.4 + aiosmtpd>=1.4.3 + PyYAML>=6 [options.packages.find] From 6201478500643b394d11ebf3fd22fc4ea4943cc8 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Fri, 7 Nov 2025 23:51:59 +0000 Subject: [PATCH 05/11] docs: installing for dev purposes should also include the testing dependencies --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 848cb3a..2f2df0a 100644 --- a/README.rst +++ b/README.rst @@ -138,7 +138,7 @@ From source This repository is structured like any other Python package. To install it in editable mode for development or debugging purposes, use:: - pip install -e . + pip install -e .[testing] To build a wheel, use:: From 1116fbebff1f265ff17a682cbfd4bb30d68bcd8c Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 8 Nov 2025 00:25:41 +0000 Subject: [PATCH 06/11] chore: fix mypy errors with current apprise --- pyproject.toml | 5 +++++ src/mailrise/smtp.py | 14 +++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2c63dbb..5b59d2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,3 +6,8 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] # See configuration details in https://github.com/pypa/setuptools_scm version_scheme = "no-guess-dev" + +[[tool.mypy.overrides]] +# Needed because Apprise doesn't include a py.typed file. Delete when it does. +module = ["apprise.*"] +follow_untyped_imports = true \ No newline at end of file diff --git a/src/mailrise/smtp.py b/src/mailrise/smtp.py index 6e5b268..3e76a8c 100644 --- a/src/mailrise/smtp.py +++ b/src/mailrise/smtp.py @@ -15,19 +15,12 @@ import apprise from aiosmtpd.smtp import Envelope, Session, SMTP +from apprise.attachment import AttachBase from apprise.common import ContentLocation from mailrise.config import MailriseConfig import mailrise.router as r -# Mypy, for some reason, considers AttachBase a module, not a class. -MYPY = False -# pylint: disable=ungrouped-imports -if MYPY: - from apprise.attachment.AttachBase import AttachBase -else: - from apprise.attachment import AttachBase - class AppriseNotifyFailure(Exception): """Exception raised when Apprise fails to deliver a notification. @@ -72,7 +65,7 @@ async def handle_DATA(self, server: SMTP, session: Session, envelope: Envelope) """Called during DATA after the entire message ('SMTP content' as described in RFC 5321) has been received.""" assert isinstance(envelope.content, bytes) - parser = BytesParser(policy=email.policy.default) + parser = BytesParser(policy=email.policy.default) # type: ignore message = parser.parsebytes(envelope.content) assert isinstance(message, StdlibEmailMessage) try: @@ -241,6 +234,5 @@ def url(self, *args: typ.Any, **kwargs: typ.Any) -> str: return f'mailrise://{hex(id(self))}' @staticmethod - def parse_url(url: str, verify_host: bool = True, - mimetype_db: typ.Any = None) -> typ.Dict[str, typ.Any]: + def parse_url(url: str, **kwargs: typ.Any) -> typ.Dict[str, typ.Any]: # type: ignore return {} From 05a2acef885e0ceaaa371663de94c95628c2c807 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:30:15 +0000 Subject: [PATCH 07/11] build(deps): bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6788d69..a62370c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,7 +54,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -68,4 +68,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From 7174c6119c277d8b03fe318ca77e507fe9d92d9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:30:19 +0000 Subject: [PATCH 08/11] build(deps): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker-hub.yml | 2 +- .github/workflows/github-packages.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a62370c..b06a262 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index ea1ab1b..61f396b 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Login to Docker Hub uses: docker/login-action@v3 diff --git a/.github/workflows/github-packages.yml b/.github/workflows/github-packages.yml index c83b60e..e5bd5c0 100644 --- a/.github/workflows/github-packages.yml +++ b/.github/workflows/github-packages.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Login to GitHub Container Registry uses: docker/login-action@v3 From bf9499902b5618ff336db70eea1497c59bebb7c1 Mon Sep 17 00:00:00 2001 From: Ryan Young Date: Sat, 8 Nov 2025 02:20:15 +0000 Subject: [PATCH 09/11] fix: bad config keys not logging correctly --- src/mailrise/simple_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mailrise/simple_router.py b/src/mailrise/simple_router.py index a35eb73..78003b3 100644 --- a/src/mailrise/simple_router.py +++ b/src/mailrise/simple_router.py @@ -174,7 +174,7 @@ def _parse_simple_key(logger: Logger, key: str) -> _Key: def fatal(): logger.critical( "Invalid config key '%s'; should be a string or an email address " - 'without periods in the username') + 'without periods in the username', key) raise SystemExit(1) if '@' in key: user, domain = _parseaddrparts(key) From 7e2dab95e01822e8bad6c04705af142ed8b9eb49 Mon Sep 17 00:00:00 2001 From: EINDEX Date: Wed, 7 Jan 2026 17:54:31 +1100 Subject: [PATCH 10/11] refactor: update SimpleRouter to support nested sender-receiver configurations - Modified the senders attribute to include receiver keys. - Enhanced the load_from_yaml function to handle both direct and nested configurations. - Updated tests to reflect changes in sender and receiver key handling. Signed-off-by: EINDEX --- src/mailrise/simple_router.py | 41 +++++++++++++++++--- tests/test_config.py | 72 +++++++++++++++++++++++++---------- 2 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/mailrise/simple_router.py b/src/mailrise/simple_router.py index 78003b3..396ab02 100644 --- a/src/mailrise/simple_router.py +++ b/src/mailrise/simple_router.py @@ -102,9 +102,9 @@ class SimpleRouter(Router): # pylint: disable=too-few-public-methods tuple, where key contains username and domain patterns that can be matched by fnmatch and sender is the Sender instance itself. """ - senders: typ.List[typ.Tuple[_Key, _SimpleSender]] + senders: typ.List[typ.Tuple[_Key, _Key, _SimpleSender]] - def __init__(self, senders: typ.List[typ.Tuple[_Key, _SimpleSender]]): + def __init__(self, senders: typ.List[typ.Tuple[_Key, _Key, _SimpleSender]]): super().__init__() self.senders = senders @@ -159,10 +159,39 @@ def load_from_yaml(logger: Logger, configs_node: dict[str, typ.Any]) -> SimpleRo if not isinstance(configs_node, dict): logger.critical('The configs node is not a YAML mapping') raise SystemExit(1) - router = SimpleRouter( - senders=[(_parse_simple_key(logger, key), _parse_simple_key(logger, rev), _load_simple_sender(logger, key, config)) - for key, rev_config in configs_node.items() for rev, config in rev_config.items()] - ) + + senders = [] + for sender_key, rev_config in configs_node.items(): + if not isinstance(rev_config, dict): + logger.critical("YAML config node '%s' is not a mapping", sender_key) + raise SystemExit(1) + + # Check if rev_config contains receiver-level nesting or direct config + # A direct config will have 'urls' or 'mailrise' keys at the top level + is_direct_config = 'urls' in rev_config or any( + k for k in rev_config.keys() + if not isinstance(rev_config[k], dict) or k == 'mailrise' + ) + + if is_direct_config: + # Direct config: sender -> config (no receiver specified) + # Use "*@*" as default receiver + default_recv = "*@*" + senders.append(( + _parse_simple_key(logger, sender_key), + _parse_simple_key(logger, default_recv), + _load_simple_sender(logger, sender_key, rev_config) + )) + else: + # Nested config: sender -> receiver -> config + for recv_key, config in rev_config.items(): + senders.append(( + _parse_simple_key(logger, sender_key), + _parse_simple_key(logger, recv_key), + _load_simple_sender(logger, sender_key, config) + )) + + router = SimpleRouter(senders=senders) if len(router.senders) < 1: logger.critical('No Apprise targets are configured') raise SystemExit(1) diff --git a/tests/test_config.py b/tests/test_config.py index 8481013..f36acbc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -49,15 +49,38 @@ def test_load() -> None: router = mrise.router assert isinstance(router, SimpleRouter) assert len(router.senders) == 1 - key = _Key(user='test') + sender_key = _Key(user='test') + rect_key = _Key(user='*@*') assert mrise.authenticator is None - sender = router.get_sender(key) + sender = router.get_sender(sender_key, rect_key) assert sender is not None notifier = _make_notifier(sender.config_yaml) assert len(notifier) == 1 assert notifier[0].url().startswith('json://localhost/') +def test_sender_receiver_load() -> None: + """Tests a successful load with :fun:`load_config`.""" + file = StringIO(""" + configs: + test: + receiver: + urls: + - json://localhost + """) + mrise = load_config(_logger, file) + router = mrise.router + assert isinstance(router, SimpleRouter) + assert len(router.senders) == 1 + sender_key = _Key(user='test') + rect_key = _Key(user='receiver') + assert mrise.authenticator is None + + sender = router.get_sender(sender_key, rect_key) + assert sender is not None + notifier = _make_notifier(sender.config_yaml) + assert len(notifier) == 1 + assert notifier[0].url().startswith('json://localhost/') def test_multi_load() -> None: """Tests a sucessful load with :fun:`load_config` with multiple configs.""" @@ -76,9 +99,10 @@ def test_multi_load() -> None: assert len(router.senders) == 2 for user in ('test1', 'test2'): - key = _Key(user=user) + sender_key = _Key(user=user) + rect_key = _Key(user='*@*') - sender = router.get_sender(key) + sender = router.get_sender(sender_key, rect_key) assert sender is not None notifier = _make_notifier(sender.config_yaml) assert len(notifier) == 1 @@ -101,9 +125,10 @@ def test_mailrise_options() -> None: router = mrise.router assert isinstance(router, SimpleRouter) assert len(router.senders) == 1 - key = _Key(user='test') + sender_key = _Key(user='test') + rect_key = _Key(user='*@*') - sender = router.get_sender(key) + sender = router.get_sender(sender_key, rect_key) assert sender is not None assert sender.title_template.template == '' assert sender.body_format == NotifyFormat.TEXT @@ -148,8 +173,9 @@ def test_config_keys() -> None: router = mrise.router assert isinstance(router, SimpleRouter) assert len(router.senders) == 1 - key = _Key(user='user', domain='example.com') - assert router.get_sender(key) is not None + sender_key = _Key(user='user', domain='example.com') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is not None def test_fnmatch_config_keys() -> None: @@ -165,10 +191,12 @@ def test_fnmatch_config_keys() -> None: mrise = load_config(_logger, file) router = mrise.router assert isinstance(router, SimpleRouter) - key = _Key(user='user', domain='example.com') - assert router.get_sender(key) is None - key = _Key(user='user', domain='mailrise.xyz') - assert router.get_sender(key) is not None + sender_key = _Key(user='user', domain='example.com') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is None + sender_key = _Key(user='user', domain='mailrise.xyz') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is not None file = StringIO(""" configs: @@ -179,8 +207,9 @@ def test_fnmatch_config_keys() -> None: mrise = load_config(_logger, file) router = mrise.router assert isinstance(router, SimpleRouter) - key = _Key(user='user', domain='example.com') - assert router.get_sender(key) is not None + sender_key = _Key(user='user', domain='example.com') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is not None file = StringIO(""" configs: @@ -191,10 +220,12 @@ def test_fnmatch_config_keys() -> None: mrise = load_config(_logger, file) router = mrise.router assert isinstance(router, SimpleRouter) - key = _Key(user='user', domain='example.com') - assert router.get_sender(key) is None - key = _Key(user='thequickbrownfox', domain='example.com') - assert router.get_sender(key) is not None + sender_key = _Key(user='user', domain='example.com') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is None + sender_key = _Key(user='thequickbrownfox', domain='example.com') + rect_key = _Key(user='*@*') + assert router.get_sender(sender_key, rect_key) is not None def test_authenticator() -> None: @@ -242,8 +273,9 @@ def test_env_var() -> None: router = mrise.router assert isinstance(router, SimpleRouter) assert len(router.senders) == 1 - key = _Key(user='test') - sender = router.get_sender(key) + sender_key = _Key(user='test') + rect_key = _Key(user='*@*') + sender = router.get_sender(sender_key, rect_key) assert sender is not None notifier = _make_notifier(sender.config_yaml) # Missing type annotation for this property as of Dec 2022. From 9db6d75efedffedf60d4e7172993e9c9baa13a21 Mon Sep 17 00:00:00 2001 From: EINDEX Date: Thu, 12 Feb 2026 11:43:48 +1100 Subject: [PATCH 11/11] ci: make the github flow works for everyone on github --- .github/workflows/github-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-packages.yml b/.github/workflows/github-packages.yml index e5bd5c0..5490fee 100644 --- a/.github/workflows/github-packages.yml +++ b/.github/workflows/github-packages.yml @@ -29,4 +29,4 @@ jobs: name: Build and Push uses: ./.github/actions/docker-build with: - images: ghcr.io/YoRyan/mailrise + images: ghcr.io/${{ github.repository }}