Skip to content
Draft
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Generate [RaBe CRIDs](https://github.com/radiorabe/crid-spec) based on several d
poetry add rabe-cridlib

# or on old setup style projects
pip -m install rabe-cridlib
pip install rabe-cridlib
```

## Usage
Expand All @@ -23,7 +23,7 @@ pip -m install rabe-cridlib
>>> # parse an existing crid
>>> crid = cridlib.parse("crid://rabe.ch/v1/klangbecken#t=clock=19930301T131200.00Z")
>>> print(f"version: {crid.version}, show: {crid.show}, start: {crid.start}")
version: v1, show: klangbecken, start: 1993-03-01 13:12:00
version: v1, show: klangbecken, start: 1993-03-01 13:12:00+00:00

>>> # get crid for current show
>>> crid = cridlib.get()
Expand Down
2 changes: 1 addition & 1 deletion cridlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Generate RaBe Content Reference Idenitifier Spcification (CRID) Identifiers.

* [`cridlib.get(timestamp=None, fragment='')`](./get/#cridlib.get.get)
* [`cridlib.parse(value)`](./parse/#gridlib.parse.parse)
* [`cridlib.parse(value)`](./parse/#cridlib.parse.parse)
"""

from .get import get
Expand Down
6 changes: 3 additions & 3 deletions cridlib/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def get(timestamp: datetime | None = None, fragment: str = "") -> CRID:
>>> with patch("cridlib.strategy.past.get_session") as mock_gs:
... mock_gs.return_value.get.return_value = mock_resp
... crid = get(datetime(2020, 3, 1, 0, 0, tzinfo=timezone('Europe/Zurich')))
>>> print(f"version: {crid.version}, start: {crid.start}")
version: v1, start: ...
>>> print(f"version: {crid.version}, start: {crid.start}") # doctest:+ELLIPSIS
version: v1, start: ...-...-... ...+00:00

```

Expand All @@ -49,7 +49,7 @@ def get(timestamp: datetime | None = None, fragment: str = "") -> CRID:
_show = now.get_show()
elif _ts < _now:
_show = past.get_show(past=_ts)
elif _ts > _now: # pragma: no cover
elif _ts > _now:
_show = future.get_show(future=_ts)

if _show:
Expand Down
28 changes: 22 additions & 6 deletions cridlib/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from datetime import datetime
from datetime import datetime, timezone
from pathlib import PurePath
from typing import Self
from urllib.parse import parse_qs
Expand Down Expand Up @@ -102,12 +102,10 @@ def __init__(self: Self, uri: str | None = None) -> None:
# fragments are optional, but if provided we want them to contain t=code
if self.fragment:
try:
# TODO(hairmare): investigate noqa for bug
# https://github.com/radiorabe/python-rabe-cridlib/issues/244
self._start = datetime.strptime( # noqa: DTZ007
self._start = datetime.strptime(
parse_qs(parse_qs(self.fragment)["t"][0])["clock"][0],
"%Y%m%dT%H%M%S.%fZ",
)
).replace(tzinfo=timezone.utc)
except KeyError as ex:
raise CRIDMissingMediaFragmentError(self.fragment, uri) from ex
except ValueError as ex: # pragma: no cover
Expand Down Expand Up @@ -200,7 +198,25 @@ def start(self: Self) -> datetime | None:

Returns
-------
Start time form CRIDs media fragment.
Start time from CRIDs media fragment as a UTC-aware datetime, or
``None`` when no fragment was present. The timezone is always
``datetime.timezone.utc`` so the value can be compared directly
to any other timezone-aware datetime without a ``TypeError``.

Examples
--------
Start time is UTC-aware and safe to compare with aware datetimes:
```python
>>> from datetime import datetime, timezone
>>> crid = CRID("crid://rabe.ch/v1/test#t=clock=19930301T131200.00Z")
>>> crid.start
datetime.datetime(1993, 3, 1, 13, 12, tzinfo=datetime.timezone.utc)
>>> crid.start.tzinfo == timezone.utc
True
>>> crid.start < datetime.now(timezone.utc)
True

```

"""
return self._start
2 changes: 1 addition & 1 deletion cridlib/strategy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Different strategies for getting CRIDs from different sources.

* [`cridlib.strategy.past`](./past/)
* [`cridlib.strategy.present`](./present/)
* [`cridlib.strategy.now`](./now/)
* [`cridlib.strategy.future`](./future/)
"""
2 changes: 1 addition & 1 deletion cridlib/strategy/future.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)


def get_show(future: datetime) -> str: # pragma: no cover
def get_show(future: datetime) -> str:
"""Return the slug for a show from LibreTime if it is in the next 7 days.

Only returns a show for the next seven days because everything futher than
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Configureation for pytest."""
"""Configuration for pytest."""

# pylint: disable=line-too-long

Expand Down
25 changes: 25 additions & 0 deletions tests/strategy/test_future.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for the future show strategy."""

from datetime import datetime, timezone

from freezegun import freeze_time

import cridlib.strategy.future


def test_get_show(libretime_mock): # noqa: ARG001
"""Test that the correct future show is returned for a given timestamp."""
with freeze_time("1993-03-01 00:00:00 UTC"):
show = cridlib.strategy.future.get_show(
future=datetime(1993, 3, 1, 11, 15, 0, tzinfo=timezone.utc),
)
assert show == "info"


def test_get_show_no_match(libretime_mock): # noqa: ARG001
"""Test that an empty string is returned when no show matches the timestamp."""
with freeze_time("1993-03-01 00:00:00 UTC"):
show = cridlib.strategy.future.get_show(
future=datetime(1993, 3, 8, 13, 12, 0, tzinfo=timezone.utc),
)
assert show == ""
9 changes: 6 additions & 3 deletions tests/test_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ def test_get_now(klangbecken_mock): # noqa: ARG001

def test_get_past(archiv_mock): # noqa: ARG001
"""Test meth:`get` for past shows."""
ts = datetime(1993, 3, 1, 13, 12, 0, tzinfo=timezone.utc)
with freeze_time("1993-03-02 00:00:00 UTC"):
crid = cridlib.get(
timestamp=datetime(1993, 3, 1, 13, 12, 00, tzinfo=timezone.utc),
)
crid = cridlib.get(timestamp=ts)
assert crid.version == "v1"
assert crid.show == "test"
assert str(crid) == "crid://rabe.ch/v1/test#t=clock=19930301T131200.00Z"
# start is UTC-aware; it can be compared directly to the original timestamp
assert crid.start is not None
assert crid.start.tzinfo is timezone.utc
assert crid.start == ts

# show with additional local args in fragments
with freeze_time("1993-03-02 00:00:00 UTC"):
Expand Down
21 changes: 18 additions & 3 deletions tests/test_lib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Test high level cridlib API."""

from datetime import datetime
from datetime import datetime, timezone

import pytest
from freezegun import freeze_time
Expand All @@ -16,7 +16,7 @@
{
"version": "v1",
"show": "test",
"start": datetime(1993, 3, 1, 13, 12),
"start": datetime(1993, 3, 1, 13, 12, tzinfo=timezone.utc),
},
),
(
Expand All @@ -32,7 +32,7 @@
{
"version": "v1",
"show": None,
"start": datetime(1993, 3, 1, 13, 12),
"start": datetime(1993, 3, 1, 13, 12, tzinfo=timezone.utc),
},
),
],
Expand All @@ -46,6 +46,21 @@ def test_crid_roundtrip(crid_str, expected):
assert crid.start == expected["start"]


def test_start_is_utc_aware():
"""Test that start is a UTC-aware datetime.

The ``Z`` in the clock fragment encodes UTC. Before the tzinfo fix
``crid.start`` was naive, so comparing it to any timezone-aware
datetime would raise a ``TypeError``. Now it carries
``timezone.utc`` and comparisons work as expected.
"""
crid = cridlib.lib.CRID("crid://rabe.ch/v1/test#t=clock=19930301T131200.00Z")
assert crid.start is not None
assert crid.start.tzinfo is timezone.utc
# Direct comparison with another UTC-aware datetime must not raise TypeError
assert crid.start < datetime.now(timezone.utc)


def test_crid_scheme_mismatch():
with pytest.raises(cridlib.lib.CRIDSchemeMismatchError):
cridlib.lib.CRID("https://rabe.ch/v1/test")
Expand Down