Skip to content

2.0.0

Latest

Choose a tag to compare

@liiight liiight released this 10 Oct 14:08
· 4 commits to main since this release
0daf424

Provider schemas are now pydantic (v2) models instead of JSON Schema dicts
validated with jsonschema. The public library API (notify(), get_notifier(), Provider.notify(),
Response, exceptions, environment variables and the logging handler) is unchanged. See the migration guide
for details and for how to port custom providers.

Breaking changes

  • Dropped support for Python 3.8 and 3.9. Python 3.10+ is required. Python 3.10 to 3.15 are supported and tested.
  • Replaced the jsonschema dependency with pydantic>=2.7,<3.
  • Custom providers declare their arguments with a schema_model (a notifiers.models.SchemaModel subclass)
    instead of the _required and _schema dicts. Providers that still define _required / _schema can't be
    instantiated. See porting custom providers.
  • Removed notifiers.utils.schema (one_or_more, list_to_commas and the format_checker with its is_*
    format checks). list_to_commas moved to notifiers.utils.helpers, the format checks are replaced by the types in
    notifiers.models.
  • Removed SchemaResource.validator and SchemaResource._validate_schema(). SchemaError is no longer raised.
  • SchemaResource._validate_data() returns the validated data instead of None.
  • Removed the Gitter provider: the Gitter API (api.gitter.im) no longer exists, Gitter moved to Matrix.
  • Removed the PopcornNotify provider: the service is down.
  • Removed the importlib-metadata dependency: entry point providers are loaded with the standard library
    importlib.metadata.

Changes

  • Argument values are converted to their declared type when possible, e.g. port="587" becomes 587. This makes
    environment variables usable for integer and boolean arguments.
  • Arguments with an alternative spelling (from / from_, type / type_) can also be set via environment
    variables using either spelling.
  • Validation error messages for anything other than missing or unknown arguments and provider specific rules use pydantic's
    wording, e.g. 'priority': Input should be less than or equal to 2.
  • Added BadArguments.errors: a list of all validation errors (loc, msg and type for each).
  • provider.schema returns pydantic's model_json_schema(by_alias=True) of the schema model, and
    provider.arguments its properties. The layout follows pydantic (description and title per argument,
    optional arguments as anyOf with null, nested models under $defs).
  • provider.required lists the required arguments only. Conditional requirements (anyOf / oneOf /
    dependencies) are enforced on validation but no longer listed.
  • provider.defaults is derived from the defaults declared on the schema model.
  • Schema model fields are snake_case. Arguments named differently by the remote API keep that name as an alias, so both
    spellings are accepted (e.g. Join deviceId / device_id, smsnumber / sms_number; DingTalk msgtype /
    msg_type, atMobiles / at_mobiles).
  • Added notifiers.models: the SchemaModel base class, OneOrMore / one_or_more and field types
    (Email, Url, Hostname, Port, Timestamp, ISO8601Datetime, RFC2822Datetime, DateString,
    AsciiStr, FilePath, E164).
  • Importing notifiers (or any non email provider) no longer performs a hostname lookup via socket.getfqdn(), which
    could block for seconds depending on the system resolver
    (#483). The default email from address
    (notifiers@<hostname>) is computed only when sending an email without a from address, via
    notifiers.providers.email.default_from(). Replaces the DEFAULT_FROM constant.

CLI

  • notify and resource command options are generated from the provider's schema model. Every option and flag that
    existed before 2.0 keeps its name and type. Additions:

    • Arguments named in camelCase by the remote API also accept a --kebab-case spelling, e.g. Join --deviceId or
      --device-id, --smsnumber or --sms-number.
    • List arguments can be passed several times: Notify --tags, Statuspage --component-ids.
    • Options for arguments without a description before 2.0 have help text, e.g. Telegram --chat-id.
  • Removed the duplicate --type- option of Pushbullet and Zulip, use --type.

  • Boolean flags that aren't passed don't send a value, so the provider's default applies, like in the library. Before 2.0
    they were sent as False: e.g. notifiers gmail notify connected without TLS and without logging in unless
    --tls and --login were passed.

  • Provider command groups are built on demand, only for the invoked provider, which makes CLI startup faster.
    notifiers_cli.core.provider_group_factory() still builds all of them up front, but isn't needed to run the CLI.

  • Provider documentation lists each provider's arguments in a table generated from its schema model.

Docker

  • The image is published to the GitHub Container Registry as ghcr.io/liiight/notifiers, for linux/amd64 and
    linux/arm64, by a GitHub Actions workflow: release tags publish <version>, <major>.<minor> and latest,
    main publishes main. Pull requests build and smoke test the image.
  • The image runs: it failed to start with unable to find user user, and the notifiers command wasn't on PATH.
  • Rebuilt as a two stage build: installs the locked dependencies with uv, then copies only the virtual environment into a
    python:3.14-alpine image running as a notifiers user. The image is about 25 MB instead of about 100 MB, without
    uv, the source tree or development files. docker run without arguments shows the CLI help.

Fixes

  • DingTalk: the schema could never validate and sending failed. Rewritten with nested models. Send a text message with
    message='...' or any message type with msg_data.
  • get_notifier() failed for providers registered only via the notifiers entry point.
  • Email / Gmail / iCloud: a single attachment path (attachments='/path/to/file') failed with
    Is a directory: '/'.
  • Email / Gmail / iCloud: when the SMTP connection was closed (e.g. the server replied 421 after an idle timeout, or
    dropped the connection), every following email failed with please run connect() first. The cached connection is
    checked with NOOP before reuse and reopened when needed
    (#478, by @broper2).
  • Documentation: the custom provider guide used APIs that don't exist (notifiers.utils.schema.one_of, _notify).

Development

  • Tests: added tests/test_schema_types.py (replaces tests/test_json_schema.py),
    tests/providers/test_generic_provider_tests.py (checks every provider and resource, including snake_case field
    names) and tests/providers/test_email_offline.py (email, Gmail and iCloud end to end against a fake SMTP server).
  • CI runs the offline test suite (-m "not online") on every Python version (fail-fast: false), plus a ruff lint and
    format check. Tests that need network access are marked online.
  • Python 3.14 and 3.15 are in the CI test matrix and the package classifiers. The online tests, the docs build (CI and
    Read the Docs) and the Docker image use Python 3.14.
  • CI runs the online tests (real provider APIs, with the repository secrets) in a separate required online job, on
    push, on manual dispatch and weekly. --run-skipped-online (or NOTIFIERS_RUN_SKIPPED_ONLINE=1, a manual dispatch
    input) also runs online tests marked as skipped, to check whether an unavailable test account works again.
  • Online tests of providers whose test account is unavailable are skipped with the reason: Pushover
    (#488), Statuspage
    (#489), Gmail sending
    (#490), iCloud, DingTalk, VictorOps, Mailgun, Join and Notify.
  • Pushbullet online tests run again.
  • ruff 0.16.10 in pre-commit and in the dev dependency group.
  • Offline tests run without NOTIFIERS_* credentials in the environment, so they behave the same locally and in CI.
  • Tests: added tests/test_cli_options.py (CLI option generation, email / Gmail / iCloud via the CLI against a fake SMTP
    server, on demand provider groups).
  • Docs: added the provider-arguments Sphinx directive (source/_ext/provider_arguments.py).
  • Docs: added Releasing, the release steps for maintainers, including the Homebrew formula update for 2.0.0.
  • Homebrew: the formula is kept in packaging/homebrew/notifiers.rb. A CI workflow installs this repository's code with
    it on macOS and Linux (brew install --build-from-source, brew test, brew audit --strict), on pull requests,
    on main and weekly, and checks that its resources match the runtime dependencies.
  • Tests: entry point provider discovery is tested with a fake installed plugin. The magic_mock_provider fixture no
    longer leaks its patched MockProvider into other tests.
  • Docs: the Sphinx dependencies are in a docs dependency group, the Read the Docs build installs it with uv, and CI
    builds the docs with warnings as errors, like Read the Docs. Removed the unused sphinx-autodoc-annotation extension,
    the missing static and template paths, and the alabaster sidebar settings. Updated the intersphinx URLs. The PDF is
    built with xelatex.
  • The statuspage incident cleanup runs for online tests only, and tolerates API errors.
  • Coverage: CI measures the notifiers and notifiers_cli packages only and uploads coverage.xml with
    codecov/codecov-action@v5, one flag per Python version. A failed upload fails the job instead of passing silently
    (#452). Removed the deprecated codecov package from the dev
    dependencies.
  • Coverage: added codecov.yml (#53). Codecov reports once all six
    Python versions uploaded, the project check allows a 1% drop, the patch check is informational, and the pull request
    comment is posted only when coverage changes.

Full documentation: https://notifiers.readthedocs.io/en/stable/