diff --git a/CHANGELOG.md b/CHANGELOG.md index 2793051..5ab549d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,29 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Tags are now limited to 255 characters in length, and should match the regex `\A[\w][\w\-]+[\w]\z` (importantly, they can't contain commas). [PR #23](https://github.com/riverqueue/riverqueue-python/pull/23). + ## [0.3.0] - 2024-07-04 ### Added -- Implement `insert_many` and `insert_many_tx`. [PR #22](https://github.com/riverqueue/river/pull/22). +- Implement `insert_many` and `insert_many_tx`. [PR #22](https://github.com/riverqueue/riverqueue-python/pull/22). ## [0.2.0] - 2024-07-04 ### Changed -- Rename `Args` to `JobArgs` and add `JobArgsWithInsertOpts` protocol. [PR #20](https://github.com/riverqueue/river/pull/20). +- Rename `Args` to `JobArgs` and add `JobArgsWithInsertOpts` protocol. [PR #20](https://github.com/riverqueue/riverqueue-python/pull/20). ## [0.1.2] - 2024-07-04 ### Changed -- Add usage instructions README, add job state constants, and change return value of `insert_many()` and `insert_many_tx()` to an integer instead of a list of jobs. [PR #19](https://github.com/riverqueue/river/pull/19). +- Add usage instructions README, add job state constants, and change return value of `insert_many()` and `insert_many_tx()` to an integer instead of a list of jobs. [PR #19](https://github.com/riverqueue/riverqueue-python/pull/19). ## [0.1.1] - 2024-07-04 ### Fixed -- Fix `pyproject.toml` description and add various URLs like to homepage, docs, and GitHub repositories. [PR #18](https://github.com/riverqueue/river/pull/18). +- Fix `pyproject.toml` description and add various URLs like to homepage, docs, and GitHub repositories. [PR #18](https://github.com/riverqueue/riverqueue-python/pull/18). ## [0.1.0] - 2024-07-04 diff --git a/src/riverqueue/client.py b/src/riverqueue/client.py index f2d8fe5..0caeee0 100644 --- a/src/riverqueue/client.py +++ b/src/riverqueue/client.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from datetime import datetime, timezone, timedelta +import re from typing import ( Any, Awaitable, @@ -320,7 +321,7 @@ def _make_insert_params( queue=insert_opts.queue or args_insert_opts.queue or QUEUE_DEFAULT, scheduled_at=scheduled_at and scheduled_at.astimezone(timezone.utc), state="scheduled" if scheduled_at else "available", - tags=insert_opts.tags or args_insert_opts.tags or [], + tags=_validate_tags(insert_opts.tags or args_insert_opts.tags or []), ) return insert_params, unique_opts @@ -348,3 +349,14 @@ def _truncate_time(time, interval_seconds) -> datetime: def _uint64_to_int64(uint64): # Packs a uint64 then unpacks to int64 to fit within Postgres bigint return (uint64 + (1 << 63)) % (1 << 64) - (1 << 63) + + +tag_re = re.compile("\A[\w][\w\-]+[\w]\Z") + + +def _validate_tags(tags: list[str]) -> list[str]: + for tag in tags: + assert ( + len(tag) <= 255 and tag_re.match(tag) + ), f"tags should be less than 255 characters in length and match regex {tag_re.pattern}" + return tags diff --git a/tests/client_test.py b/tests/client_test.py index 852d6d5..00cf52e 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -259,6 +259,26 @@ def to_json() -> None: assert "args should return non-nil from `to_json`" == str(ex.value) +def test_tag_validation(client): + client.insert( + SimpleArgs(), insert_opts=InsertOpts(tags=["foo", "bar", "baz", "foo-bar-baz"]) + ) + + with pytest.raises(AssertionError) as ex: + client.insert(SimpleArgs(), insert_opts=InsertOpts(tags=["commas,bad"])) + assert ( + "tags should be less than 255 characters in length and match regex \A[\w][\w\-]+[\w]\Z" + == str(ex.value) + ) + + with pytest.raises(AssertionError) as ex: + client.insert(SimpleArgs(), insert_opts=InsertOpts(tags=["a" * 256])) + assert ( + "tags should be less than 255 characters in length and match regex \A[\w][\w\-]+[\w]\Z" + == str(ex.value) + ) + + def test_check_advisory_lock_prefix_bounds(): Client(mock_driver, advisory_lock_prefix=123)