Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.12
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.13"

- name: Install required packages
run: pip install pre-commit

- name: Run pre-commit hooks
run: pre-commit run --all-files


tests:
name: Python ${{ matrix.python-version }}, django ${{ matrix.django-version }}
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', ]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', ]
django-version: ['42', '51', '52', ]

exclude:
Expand All @@ -46,8 +45,10 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install tox
run: pip install tox
run: uv pip install --system tox tox-uv
- name: Run Tests
env:
TOXENV: django${{ matrix.django-version }}
Expand All @@ -56,7 +57,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: coverage-data-${{ matrix.python-version }}-${{ matrix.django-version }}
path: '${{ github.workspace }}/.coverage.*'
path: '${{ github.workspace }}/.coverage'
include-hidden-files: true
if-no-files-found: error

Expand All @@ -69,21 +70,22 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.13'

- name: Install dependencies
run: python -m pip install --upgrade coverage[toml]

- name: Download data
uses: actions/download-artifact@v4
with:
path: ${{ github.workspace }}
path: ${{ github.workspace }}/coverage-reports
pattern: coverage-data-*
merge-multiple: true
merge-multiple: false

- name: Combine coverage and fail if it's <100.0%
run: |
python -m coverage combine
python -m coverage combine coverage-reports/*/.coverage
python -m coverage xml
python -m coverage html --skip-covered --skip-empty
python -m coverage report --fail-under=100.0
echo "## Coverage summary" >> $GITHUB_STEP_SUMMARY
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Requirements.txt is managed by pip-tools
# Deprecated dependency files
requirements.txt

# sphinx build folder
Expand All @@ -156,3 +156,6 @@ _build

# Test databases
*.sqlite

# AI
.claude/
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11
rev: v0.14.0
hooks:
# Run the Ruff formatter.
- id: ruff-format
Expand All @@ -12,27 +12,27 @@ repos:
args: [--fix, --exit-non-zero-on-fix]

- repo: https://github.com/adamchainz/blacken-docs
rev: 1.19.1
rev: 1.20.0
hooks:
- id: blacken-docs
additional_dependencies:
- black==25.1.0
- black==25.9.0
files: '(?:README\.md|\.ambient-package-update\/templates\/snippets\/.*\.tpl|docs\/.*\.(?:md|rst))'

- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [ --py39-plus ]
args: [ --py310-plus ]

- repo: https://github.com/adamchainz/django-upgrade
rev: 1.25.0
rev: 1.29.0
hooks:
- id: django-upgrade
args: [--target-version, "4.2"]

- repo: https://github.com/adamchainz/djade-pre-commit
rev: 1.4.0
rev: 1.6.0
hooks:
- id: djade
args: [--target-version, "4.2"]
Expand All @@ -43,7 +43,7 @@ repos:
)$

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-ast
- id: check-builtin-literals
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version: 2
build:
os: ubuntu-24.04
tools:
python: "3.12"
python: "3.13"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

**2.7.4** (2025-10-09)
* Maintenance updates via ambient-package-update

**2.7.3** (2025-06-24)
* Convert `msg.send()` result to boolean in `._send_and_log_email()`

Expand Down
10 changes: 4 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
## Setup package for development

- Create a Python virtualenv and activate it
- Install "pip-tools" with `pip install -U pip-tools`
- Compile the requirements with `pip-compile --extra dev, -o requirements.txt pyproject.toml --resolver=backtracking`
- Sync the dependencies with your virtualenv with `pip-sync`
- Install "uv" with `pip install -U uv`
- Sync the requirements with `uv sync --frozen --group dev`

## Add functionality

Expand Down Expand Up @@ -58,20 +57,19 @@ Example: run all hooks of pre-push stage
- To build the documentation, run: `sphinx-build docs/ docs/_build/html/`.
- Open `docs/_build/html/index.html` to see the documentation.


### Translation files

If you have added custom text, make sure to wrap it in `_()` where `_` is
gettext_lazy (`from django.utils.translation import gettext_lazy as _`).

How to create translation file:

* Navigate to `django-pony-express`
* Navigate to `django_pony_express`
* `python manage.py makemessages -l de`
* Have a look at the new/changed files within `django_pony_express/locale`

How to compile translation files:

* Navigate to `django-pony-express`
* Navigate to `django_pony_express`
* `python manage.py compilemessages`
* Have a look at the new/changed files within `django_pony_express/locale`
62 changes: 37 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ suite.
Similar to class-based view in Django core, this package provides a neat, DRY and testable (!) way to handle your
emails in Django.

* [PyPI](https://pypi.org/project/django-pony-express/)
* [GitHub](https://github.com/ambient-innovation/django-pony-express)
* [Full documentation](https://django-pony-express.readthedocs.io/en/latest/index.html)
* Creator & Maintainer: [Ambient Digital](https://ambient.digital/)
[PyPI](https://pypi.org/project/django-pony-express/) | [GitHub](https://github.com/ambient-innovation/django-pony-express) | [Full documentation](https://django-pony-express.readthedocs.io/en/latest/index.html)

Creator & Maintainer: [Ambient Digital](https://ambient.digital/)

## Features

Expand Down Expand Up @@ -43,48 +41,62 @@ Ingenious, right?

`pip install django-pony-express`

or via pipenv:
or via uv:

`pipenv install django-pony-express`
`uv add django-pony-express`

- Add module to `INSTALLED_APPS` within the main django `settings.py`:

```python
INSTALLED_APPS = (
# ...
"django_pony_express",
)
```


```python
INSTALLED_APPS = (
# ...
"django_pony_express",
)
```

### Publish to ReadTheDocs.io

- Fetch the latest changes in GitHub mirror and push them
- Trigger new build at ReadTheDocs.io (follow instructions in admin panel at RTD) if the GitHub webhook is not yet set
up.

### Publish to PyPi
### Preparation and building

This package uses [uv](https://github.com/astral-sh/uv) for dependency management and building.

- Update documentation about new/changed functionality

- Update the `Changelog`
- Update the `CHANGES.md`

- Increment version in main `__init__.py`

- Create pull request / merge to master
- Create pull request / merge to "master"

- This project uses the flit package to publish to PyPI. Thus, publishing should be as easy as running:
```
flit publish
- This project uses uv to publish to PyPI. This will create distribution files in the `dist/` directory.

```bash
uv build
```

To publish to TestPyPI use the following to ensure that you have set up your .pypirc as
shown [here](https://flit.readthedocs.io/en/latest/upload.html#using-pypirc) and use the following command:
### Publishing to PyPI

```
flit publish --repository testpypi
```
To publish to the production PyPI:

```bash
uv publish
```

To publish to TestPyPI first (recommended for testing):

```bash
uv publish --publish-url https://test.pypi.org/legacy/
```

You can then test the installation from TestPyPI:

```bash
uv pip install --index-url https://test.pypi.org/simple/ ambient-package-update
```

### Maintenance

Expand Down
2 changes: 1 addition & 1 deletion django_pony_express/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Class-based emails including a test suite for Django"""

__version__ = "2.7.3"
__version__ = "2.7.4"
11 changes: 5 additions & 6 deletions django_pony_express/services/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import re
from typing import Optional, Union

from bs4 import BeautifulSoup
from django.conf import settings
Expand All @@ -25,7 +24,7 @@ class BaseEmailServiceFactory:
service_class = None
recipient_email_list = []

def __init__(self, recipient_email_list: Union[list, tuple, QuerySet] = None, **kwargs) -> None:
def __init__(self, recipient_email_list: list | tuple | QuerySet = None, **kwargs) -> None:
"""
Initialisation takes optionally a list of recipients. Doesn't have to be a list of strings because
fetching the actual email from a complex data structure can be done in the method `get_email_from_recipient()`
Expand Down Expand Up @@ -130,9 +129,9 @@ class BaseEmailService:

def __init__(
self,
recipient_email_list: Optional[Union[list, tuple, str]] = None,
context_data: Optional[dict] = None,
attachment_list: Optional[list] = None,
recipient_email_list: list | tuple | str | None = None,
context_data: dict | None = None,
attachment_list: list | None = None,
connection: BaseEmailBackend = None,
**kwargs,
) -> None:
Expand Down Expand Up @@ -199,7 +198,7 @@ def get_reply_to_emails(self) -> list:
"""
return [self.REPLY_TO_ADDRESS] if isinstance(self.REPLY_TO_ADDRESS, str) else self.REPLY_TO_ADDRESS

def get_translation(self) -> Union[str, None]:
def get_translation(self) -> str | None:
"""
Tries to fetch the current translation from the django settings.
"""
Expand Down
Loading