Compare commits

...

6 Commits

Author SHA1 Message Date
b777d4339c chore(deps): update dependency urllib3 to v2.5.0
All checks were successful
Validate PR Title / validate (pull_request) Successful in 8s
CI / ci (pull_request) Successful in 1m6s
2025-06-22 21:33:38 +00:00
73696a9647 chore(deps): update dependency certifi to v2025.6.15 (#132)
All checks were successful
Snyk / security (push) Successful in 53s
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [certifi](https://github.com/certifi/python-certifi) | `==2025.4.26` -> `==2025.6.15` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/certifi/2025.6.15?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/certifi/2025.4.26/2025.6.15?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>certifi/python-certifi (certifi)</summary>

### [`v2025.6.15`](https://github.com/certifi/python-certifi/compare/2025.04.26...2025.06.15)

[Compare Source](https://github.com/certifi/python-certifi/compare/2025.04.26...2025.06.15)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC41Mi4wIiwidXBkYXRlZEluVmVyIjoiNDEuMS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: luke/epage#132
Co-authored-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
Co-committed-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
2025-06-22 23:32:41 +02:00
862da875bd chore(deps): update dependency requests to v2.32.4 (#131)
All checks were successful
Snyk / security (push) Successful in 49s
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [requests](https://requests.readthedocs.io) ([source](https://github.com/psf/requests), [changelog](https://github.com/psf/requests/blob/master/HISTORY.md)) | `==2.32.3` -> `==2.32.4` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/requests/2.32.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/requests/2.32.3/2.32.4?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>psf/requests (requests)</summary>

### [`v2.32.4`](https://github.com/psf/requests/blob/HEAD/HISTORY.md#2324-2025-06-10)

[Compare Source](https://github.com/psf/requests/compare/v2.32.3...v2.32.4)

**Security**

- CVE-2024-47081 Fixed an issue where a maliciously crafted URL and trusted
  environment will retrieve credentials for the wrong hostname/machine from a
  netrc file.

**Improvements**

- Numerous documentation improvements

**Deprecations**

- Added support for pypy 3.11 for Linux and macOS.
- Dropped support for pypy 3.9 following its end of support.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC44IiwidXBkYXRlZEluVmVyIjoiNDEuMS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: luke/epage#131
Co-authored-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
Co-committed-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
2025-06-22 23:31:13 +02:00
b4dc8cf707 chore(deps): update dependency click to v8.2.1 (#129)
All checks were successful
Snyk / security (push) Successful in 51s
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [click](https://github.com/pallets/click) ([changelog](https://click.palletsprojects.com/page/changes/)) | `==8.1.8` -> `==8.2.1` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/click/8.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/click/8.1.8/8.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>pallets/click (click)</summary>

### [`v8.2.1`](https://github.com/pallets/click/blob/HEAD/CHANGES.rst#Version-821)

[Compare Source](https://github.com/pallets/click/compare/8.2.0...8.2.1)

Released 2025-05-20

- Fix flag value handling for flag options with a provided type. :issue:`2894`
  :issue:`2897` :pr:`2930`
- Fix shell completion for nested groups. :issue:`2906` :pr:`2907`
- Flush `sys.stderr` at the end of `CliRunner.invoke`. :issue:`2682`
- Fix EOF handling for stdin input in CliRunner. :issue:`2787`

### [`v8.2.0`](https://github.com/pallets/click/blob/HEAD/CHANGES.rst#Version-820)

[Compare Source](https://github.com/pallets/click/compare/8.1.8...8.2.0)

Released 2025-05-10

- Drop support for Python 3.7, 3.8, and 3.9. :pr:`2588` :pr:`2893`

- Use modern packaging metadata with `pyproject.toml` instead of `setup.cfg`.
  :pr:`2438`

- Use `flit_core` instead of `setuptools` as build backend. :pr:`2543`

- Deprecate the `__version__` attribute. Use feature detection, or
  `importlib.metadata.version("click")`, instead. :issue:`2598`

- `BaseCommand` is deprecated. `Command` is the base class for all
  commands. :issue:`2589`

- `MultiCommand` is deprecated. `Group` is the base class for all group
  commands. :issue:`2590`

- The current parser and related classes and methods, are deprecated.
  :issue:`2205`

  - `OptionParser` and the `parser` module, which is a modified copy of
    `optparse` in the standard library.
  - `Context.protected_args` is unneeded. `Context.args` contains any
    remaining arguments while parsing.
  - `Parameter.add_to_parser` (on both `Argument` and `Option`) is
    unneeded. Parsing works directly without building a separate parser.
  - `split_arg_string` is moved from `parser` to `shell_completion`.

- Enable deferred evaluation of annotations with
  `from __future__ import annotations`. :pr:`2270`

- When generating a command's name from a decorated function's name, the
  suffixes `_command`, `_cmd`, `_group`, and `_grp` are removed.
  :issue:`2322`

- Show the `types.ParamType.name` for `types.Choice` options within
  `--help` message if `show_choices=False` is specified.
  :issue:`2356`

- Do not display default values in prompts when `Option.show_default` is
  `False`. :pr:`2509`

- Add `get_help_extra` method on `Option` to fetch the generated extra
  items used in `get_help_record` to render help text. :issue:`2516`
  :pr:`2517`

- Keep stdout and stderr streams independent in `CliRunner`. Always
  collect stderr output and never raise an exception. Add a new
  output stream to simulate what the user sees in its terminal. Removes
  the `mix_stderr` parameter in `CliRunner`. :issue:`2522` :pr:`2523`

- `Option.show_envvar` now also shows environment variable in error messages.
  :issue:`2695` :pr:`2696`

- `Context.close` will be called on exit. This results in all
  `Context.call_on_close` callbacks and context managers added via
  `Context.with_resource` to be closed on exit as well. :pr:`2680`

- Add `ProgressBar(hidden: bool)` to allow hiding the progressbar. :issue:`2609`

- A `UserWarning` will be shown when multiple parameters attempt to use the
  same name. :issue:`2396`

- When using `Option.envvar` with `Option.flag_value`, the `flag_value`
  will always be used instead of the value of the environment variable.
  :issue:`2746` :pr:`2788`

- Add `Choice.get_invalid_choice_message` method for customizing the
  invalid choice message. :issue:`2621` :pr:`2622`

- If help is shown because `no_args_is_help` is enabled (defaults to `True`
  for groups, `False` for commands), the exit code is 2 instead of 0.
  :issue:`1489` :pr:`1489`

- Contexts created during shell completion are closed properly, fixing
  a `ResourceWarning` when using `click.File`. :issue:`2644` :pr:`2800`
  :pr:`2767`

- `click.edit(filename)` now supports passing an iterable of filenames in
  case the editor supports editing multiple files at once. Its return type
  is now also typed: `AnyStr` if `text` is passed, otherwise `None`.
  :issue:`2067` :pr:`2068`

- Specialized typing of `progressbar(length=...)` as `ProgressBar[int]`.
  :pr:`2630`

- Improve `echo_via_pager` behaviour in face of errors.
  :issue:`2674`

  - Terminate the pager in case a generator passed to `echo_via_pager`
    raises an exception.
  - Ensure to always close the pipe to the pager process and wait for it
    to terminate.
  - `echo_via_pager` will not ignore `KeyboardInterrupt` anymore. This
    allows the user to search for future output of the generator when
    using less and then aborting the program using ctrl-c.

- `deprecated: bool | str` can now be used on options and arguments. This
  previously was only available for `Command`. The message can now also be
  customised by using a `str` instead of a `bool`. :issue:`2263` :pr:`2271`

  - `Command.deprecated` formatting in `--help` changed from
    `(Deprecated) help` to `help (DEPRECATED)`.
  - Parameters cannot be required nor prompted or an error is raised.
  - A warning will be printed when something deprecated is used.

- Add a `catch_exceptions` parameter to `CliRunner`. If
  `catch_exceptions` is not passed to `CliRunner.invoke`, the value
  from `CliRunner` is used. :issue:`2817` :pr:`2818`

- `Option.flag_value` will no longer have a default value set based on
  `Option.default` if `Option.is_flag` is `False`. This results in
  `Option.default` not needing to implement `__bool__`. :pr:`2829`

- Incorrect `click.edit` typing has been corrected. :pr:`2804`

- `Choice` is now generic and supports any iterable value.
  This allows you to use enums and other non-`str` values. :pr:`2796`
  :issue:`605`

- Fix setup of help option's defaults when using a custom class on its
  decorator. Removes `HelpOption`. :issue:`2832` :pr:`2840`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC44LjIiLCJ1cGRhdGVkSW5WZXIiOiI0MS4xLjQiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbInR5cGUvZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: luke/epage#129
Co-authored-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
Co-committed-by: Renovate [BOT] <renovate-bot@git.tainton.uk>
2025-06-22 23:28:21 +02:00
8e8cb5c33e Formatting
All checks were successful
Snyk / security (push) Successful in 50s
2025-06-22 22:25:48 +01:00
4d60b89b02 Add Dockerfile
All checks were successful
Snyk / security (push) Successful in 50s
2025-06-22 22:22:40 +01:00
7 changed files with 57 additions and 34 deletions

View File

@ -1,13 +1,13 @@
certifi==2025.4.26 certifi==2025.6.15
charset-normalizer==3.4.2 charset-normalizer==3.4.2
click==8.1.8 click==8.2.1
Flask==3.1.0 Flask==3.1.0
Flask-WTF==1.2.2 Flask-WTF==1.2.2
idna==3.10 idna==3.10
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.6
MarkupSafe==3.0.2 MarkupSafe==3.0.2
requests==2.32.3 requests==2.32.4
urllib3==2.4.0 urllib3==2.5.0
Werkzeug==3.1.3 Werkzeug==3.1.3
WTForms==3.2.1 WTForms==3.2.1

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM python:3.13-slim
LABEL maintainer="Luke Tainton <luke@tainton.uk>"
USER root
ENV PYTHONPATH="/run:/usr/local/lib/python3.11/lib-dynload:/usr/local/lib/python3.11/site-packages:/usr/local/lib/python3.11"
ENV UV_PROJECT_ENVIRONMENT="/usr/local/"
WORKDIR /run
RUN mkdir -p /.local && \
chmod -R 777 /.local && \
pip install -U pip uv==0.5.14
COPY pyproject.toml /run/pyproject.toml
COPY uv.lock /run/uv.lock
# needed for PDM build
COPY README.md /run/README.md
RUN uv sync --frozen
ENTRYPOINT ["python3", "-B", "-m", "app.main"]
ARG version="dev"
ENV APP_VERSION=$version
COPY app /run/app

View File

@ -4,24 +4,26 @@
from flask import Flask, render_template, request from flask import Flask, render_template, request
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
from app.send_page import send_page
from app.send_page import send_page
app = Flask(__name__) app = Flask(__name__)
csrf = CSRFProtect(app) csrf = CSRFProtect(app)
@app.route("/", methods=['GET'])
@app.route("/", methods=["GET"])
def index(): def index():
"""Returns index template.""" """Returns index template."""
return render_template('index.html', status='') return render_template("index.html", status="")
@app.route("/", methods=['POST'])
@app.route("/", methods=["POST"])
def send(): def send():
"""POST function""" """POST function"""
result = send_page( result = send_page(
name=request.form.get('name'), name=request.form.get("name"),
email=request.form.get('email'), email=request.form.get("email"),
message=request.form.get('message') message=request.form.get("message"),
) )
status = 'success' if result[0] else 'fail' status = "success" if result[0] else "fail"
return render_template('index.html', status=status) return render_template("index.html", status=status)

View File

@ -3,33 +3,31 @@
"""Send messages via the Pushover API.""" """Send messages via the Pushover API."""
import os import os
import requests import requests
def send_page(name: str, email: str, message: str) -> tuple: def send_page(name: str, email: str, message: str) -> tuple:
"""POST to the Pushover API.""" """POST to the Pushover API."""
api_url = "https://api.pushover.net/1/messages.json" api_url = "https://api.pushover.net/1/messages.json"
api_token = os.getenv('PUSHOVER_API_TOKEN') api_token = os.getenv("PUSHOVER_API_TOKEN")
user_key = os.getenv('PUSHOVER_USER_KEY') user_key = os.getenv("PUSHOVER_USER_KEY")
full_msg = f"Name: {name}\nEmail: {email}\n\nMessage: {message}" full_msg = f"Name: {name}\nEmail: {email}\n\nMessage: {message}"
payload = { payload = {
'token': api_token, "token": api_token,
'user': user_key, "user": user_key,
'title': f"ePage from {name}", "title": f"ePage from {name}",
'message': full_msg, "message": full_msg,
'priority': "1", "priority": "1",
'sound': 'cosmic' "sound": "cosmic",
} }
req = requests.post( req = requests.post(
api_url, api_url, json=payload, headers={"Content-Type": "application/json"}, timeout=5
json=payload,
headers={'Content-Type': 'application/json'},
timeout=5
) )
if req.status_code == 200 and req.json().get('status') == 1: if req.status_code == 200 and req.json().get("status") == 1:
return (True, None) return (True, None)
return (False, req.json()) return (False, req.json())

View File

@ -3,6 +3,7 @@
"""PyTest unit tests.""" """PyTest unit tests."""
import os import os
import pytest import pytest
from app.app import app, csrf from app.app import app, csrf

View File

@ -3,10 +3,10 @@
"""Tests for app/app.py""" """Tests for app/app.py"""
from tests import client # pragma: no cover from tests import client # pragma: no cover
def test_index(client) -> None: def test_index(client) -> None:
"""Ensure that the index page is loaded correctly.""" """Ensure that the index page is loaded correctly."""
req = client.get('/') req = client.get("/")
assert req.status_code == 200 and "ePage" in req.text assert req.status_code == 200 and "ePage" in req.text

View File

@ -7,9 +7,5 @@ from app.send_page import send_page
def test_send_page_no_env() -> None: def test_send_page_no_env() -> None:
"""Ensure the API returns an error if no API key specified.""" """Ensure the API returns an error if no API key specified."""
result = send_page( result = send_page(name="Unit Test", email="none@none.com", message="Unit Test")
name='Unit Test', assert not result[0] and result[1].get("token") == "invalid"
email='none@none.com',
message='Unit Test'
)
assert not result[0] and result[1].get('token') == 'invalid'