[](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [pydantic](https://togithub.com/pydantic/pydantic) ([changelog](https://docs.pydantic.dev/latest/changelog/)) | `>=2.5.0,<2.7` -> `>=2.5.0,<2.8` | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes <details> <summary>pydantic/pydantic (pydantic)</summary> ### [`v2.7.0`](https://togithub.com/pydantic/pydantic/blob/HEAD/HISTORY.md#v270-2024-04-11) [Compare Source](https://togithub.com/pydantic/pydantic/compare/v2.6.4...v2.7.0) [GitHub release](https://togithub.com/pydantic/pydantic/releases/tag/v2.7.0) The code released in v2.7.0 is practically identical to that of v2.7.0b1. ##### What's Changed ##### Packaging - Reorganize `pyproject.toml` sections by [@​Viicos](https://togithub.com/Viicos) in [#​8899](https://togithub.com/pydantic/pydantic/pull/8899) - Bump `pydantic-core` to `v2.18.1` by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9211](https://togithub.com/pydantic/pydantic/pull/9211) - Adopt `jiter` `v0.2.0` by [@​samuelcolvin](https://togithub.com/samuelcolvin) in [pydantic/pydantic-core#1250](https://togithub.com/pydantic/pydantic-core/pull/1250) ##### New Features - Extract attribute docstrings from `FieldInfo.description` by [@​Viicos](https://togithub.com/Viicos) in [#​6563](https://togithub.com/pydantic/pydantic/pull/6563) - Add a `with_config` decorator to comply with typing spec by [@​Viicos](https://togithub.com/Viicos) in [#​8611](https://togithub.com/pydantic/pydantic/pull/8611) - Allow an optional separator splitting the value and unit of the result of `ByteSize.human_readable` by [@​jks15satoshi](https://togithub.com/jks15satoshi) in [#​8706](https://togithub.com/pydantic/pydantic/pull/8706) - Add generic `Secret` base type by [@​conradogarciaberrotaran](https://togithub.com/conradogarciaberrotaran) in [#​8519](https://togithub.com/pydantic/pydantic/pull/8519) - Make use of `Sphinx` inventories for cross references in docs by [@​Viicos](https://togithub.com/Viicos) in [#​8682](https://togithub.com/pydantic/pydantic/pull/8682) - Add environment variable to disable plugins by [@​geospackle](https://togithub.com/geospackle) in [#​8767](https://togithub.com/pydantic/pydantic/pull/8767) - Add support for `deprecated` fields by [@​Viicos](https://togithub.com/Viicos) in [#​8237](https://togithub.com/pydantic/pydantic/pull/8237) - Allow `field_serializer('*')` by [@​ornariece](https://togithub.com/ornariece) in [#​9001](https://togithub.com/pydantic/pydantic/pull/9001) - Handle a case when `model_config` is defined as a model property by [@​alexeyt101](https://togithub.com/alexeyt101) in [#​9004](https://togithub.com/pydantic/pydantic/pull/9004) - Update `create_model()` to support `typing.Annotated` as input by [@​wannieman98](https://togithub.com/wannieman98) in [#​8947](https://togithub.com/pydantic/pydantic/pull/8947) - Add `ClickhouseDsn` support by [@​solidguy7](https://togithub.com/solidguy7) in [#​9062](https://togithub.com/pydantic/pydantic/pull/9062) - Add support for `re.Pattern[str]` to `pattern` field by [@​jag-k](https://togithub.com/jag-k) in [#​9053](https://togithub.com/pydantic/pydantic/pull/9053) - Support for `serialize_as_any` runtime setting by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8830](https://togithub.com/pydantic/pydantic/pull/8830) - Add support for `typing.Self` by [@​Youssefares](https://togithub.com/Youssefares) in [#​9023](https://togithub.com/pydantic/pydantic/pull/9023) - Ability to pass `context` to serialization by [@​ornariece](https://togithub.com/ornariece) in [#​8965](https://togithub.com/pydantic/pydantic/pull/8965) - Add feedback widget to docs with flarelytics integration by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9129](https://togithub.com/pydantic/pydantic/pull/9129) - Support for parsing partial JSON strings in Python by [@​samuelcolvin](https://togithub.com/samuelcolvin) in [pydantic/jiter#66](https://togithub.com/pydantic/jiter/pull/66) **Finalized in v2.7.0, rather than v2.7.0b1:** - Add support for field level number to str coercion option by [@​NeevCohen](https://togithub.com/NeevCohen) in [#​9137](https://togithub.com/pydantic/pydantic/pull/9137) - Update `warnings` parameter for serialization utilities to allow raising a warning by [@​Lance-Drane](https://togithub.com/Lance-Drane) in [#​9166](https://togithub.com/pydantic/pydantic/pull/9166) ##### Changes - Correct docs, logic for `model_construct` behavior with `extra` by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8807](https://togithub.com/pydantic/pydantic/pull/8807) - Improve error message for improper `RootModel` subclasses by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8857](https://togithub.com/pydantic/pydantic/pull/8857) - Use `PEP570` syntax by [@​Viicos](https://togithub.com/Viicos) in [#​8940](https://togithub.com/pydantic/pydantic/pull/8940) - Add `enum` and `type` to the JSON schema for single item literals by [@​dmontagu](https://togithub.com/dmontagu) in [#​8944](https://togithub.com/pydantic/pydantic/pull/8944) - Deprecate `update_json_schema` internal function by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9125](https://togithub.com/pydantic/pydantic/pull/9125) - Serialize duration to hour minute second, instead of just seconds by [@​kakilangit](https://togithub.com/kakilangit) in [pydantic/speedate#50](https://togithub.com/pydantic/speedate/pull/50) - Trimming str before parsing to int and float by [@​hungtsetse](https://togithub.com/hungtsetse) in [pydantic/pydantic-core#1203](https://togithub.com/pydantic/pydantic-core/pull/1203) ##### Performance - `enum` validator improvements by [@​samuelcolvin](https://togithub.com/samuelcolvin) in [#​9045](https://togithub.com/pydantic/pydantic/pull/9045) - Move `enum` validation and serialization to Rust by [@​samuelcolvin](https://togithub.com/samuelcolvin) in [#​9064](https://togithub.com/pydantic/pydantic/pull/9064) - Improve schema generation for nested dataclasses by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9114](https://togithub.com/pydantic/pydantic/pull/9114) - Fast path for ASCII python string creation in JSON by [@​samuelcolvin](https://togithub.com/samuelcolvin) in in [pydantic/jiter#72](https://togithub.com/pydantic/jiter/pull/72) - SIMD integer and string JSON parsing on `aarch64`(**Note:** SIMD on x86 will be implemented in a future release) by [@​samuelcolvin](https://togithub.com/samuelcolvin) in in [pydantic/jiter#65](https://togithub.com/pydantic/jiter/pull/65) - Support JSON `Cow<str>` from `jiter` by [@​davidhewitt](https://togithub.com/davidhewitt) in [pydantic/pydantic-core#1231](https://togithub.com/pydantic/pydantic-core/pull/1231) - MAJOR performance improvement: update to PyO3 0.21 final by [@​davidhewitt](https://togithub.com/davidhewitt) in [pydantic/pydantic-core#1248](https://togithub.com/pydantic/pydantic-core/pull/1248) - cache Python strings by [@​samuelcolvin](https://togithub.com/samuelcolvin) in [pydantic/pydantic-core#1240](https://togithub.com/pydantic/pydantic-core/pull/1240) ##### Fixes - Fix strict parsing for some `Sequence`s by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8614](https://togithub.com/pydantic/pydantic/pull/8614) - Add a check on the existence of `__qualname__` by [@​anci3ntr0ck](https://togithub.com/anci3ntr0ck) in [#​8642](https://togithub.com/pydantic/pydantic/pull/8642) - Handle `__pydantic_extra__` annotation being a string or inherited by [@​alexmojaki](https://togithub.com/alexmojaki) in [#​8659](https://togithub.com/pydantic/pydantic/pull/8659) - Fix json validation for `NameEmail` by [@​Holi0317](https://togithub.com/Holi0317) in [#​8650](https://togithub.com/pydantic/pydantic/pull/8650) - Fix type-safety of attribute access in `BaseModel` by [@​bluenote10](https://togithub.com/bluenote10) in [#​8651](https://togithub.com/pydantic/pydantic/pull/8651) - Fix bug with `mypy` plugin and `no_strict_optional = True` by [@​dmontagu](https://togithub.com/dmontagu) in [#​8666](https://togithub.com/pydantic/pydantic/pull/8666) - Fix `ByteSize` error `type` change by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8681](https://togithub.com/pydantic/pydantic/pull/8681) - Fix inheriting annotations in dataclasses by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8679](https://togithub.com/pydantic/pydantic/pull/8679) - Fix regression in core schema generation for indirect definition references by [@​dmontagu](https://togithub.com/dmontagu) in [#​8702](https://togithub.com/pydantic/pydantic/pull/8702) - Fix unsupported types bug with plain validator by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8710](https://togithub.com/pydantic/pydantic/pull/8710) - Reverting problematic fix from 2.6 release, fixing schema building bug by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8718](https://togithub.com/pydantic/pydantic/pull/8718) - fixes `__pydantic_config__` ignored for TypeDict by [@​13sin](https://togithub.com/13sin) in [#​8734](https://togithub.com/pydantic/pydantic/pull/8734) - Fix test failures with `pytest v8.0.0` due to `pytest.warns()` starting to work inside `pytest.raises()` by [@​mgorny](https://togithub.com/mgorny) in [#​8678](https://togithub.com/pydantic/pydantic/pull/8678) - Use `is_valid_field` from 1.x for `mypy` plugin by [@​DanielNoord](https://togithub.com/DanielNoord) in [#​8738](https://togithub.com/pydantic/pydantic/pull/8738) - Better-support `mypy` strict equality flag by [@​dmontagu](https://togithub.com/dmontagu) in [#​8799](https://togithub.com/pydantic/pydantic/pull/8799) - model_json_schema export with Annotated types misses 'required' parameters by [@​LouisGobert](https://togithub.com/LouisGobert) in [#​8793](https://togithub.com/pydantic/pydantic/pull/8793) - Fix default inclusion in `FieldInfo.__repr_args__` by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8801](https://togithub.com/pydantic/pydantic/pull/8801) - Fix resolution of forward refs in dataclass base classes that are not present in the subclass module namespace by [@​matsjoyce-refeyn](https://togithub.com/matsjoyce-refeyn) in [#​8751](https://togithub.com/pydantic/pydantic/pull/8751) - Fix `BaseModel` type annotations to be resolvable by `typing.get_type_hints` by [@​devmonkey22](https://togithub.com/devmonkey22) in [#​7680](https://togithub.com/pydantic/pydantic/pull/7680) - Fix: allow empty string aliases with `AliasGenerator` by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8810](https://togithub.com/pydantic/pydantic/pull/8810) - Fix test along with `date` -> `datetime` timezone assumption fix by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8823](https://togithub.com/pydantic/pydantic/pull/8823) - Fix deprecation warning with usage of `ast.Str` by [@​Viicos](https://togithub.com/Viicos) in [#​8837](https://togithub.com/pydantic/pydantic/pull/8837) - Add missing `deprecated` decorators by [@​Viicos](https://togithub.com/Viicos) in [#​8877](https://togithub.com/pydantic/pydantic/pull/8877) - Fix serialization of `NameEmail` if name includes an email address by [@​NeevCohen](https://togithub.com/NeevCohen) in [#​8860](https://togithub.com/pydantic/pydantic/pull/8860) - Add information about class in error message of schema generation by [@​Czaki](https://togithub.com/Czaki) in [#​8917](https://togithub.com/pydantic/pydantic/pull/8917) - Make `TypeAdapter`'s typing compatible with special forms by [@​adriangb](https://togithub.com/adriangb) in [#​8923](https://togithub.com/pydantic/pydantic/pull/8923) - Fix issue with config behavior being baked into the ref schema for `enum`s by [@​dmontagu](https://togithub.com/dmontagu) in [#​8920](https://togithub.com/pydantic/pydantic/pull/8920) - More helpful error re wrong `model_json_schema` usage by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8928](https://togithub.com/pydantic/pydantic/pull/8928) - Fix nested discriminated union schema gen, pt 2 by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8932](https://togithub.com/pydantic/pydantic/pull/8932) - Fix schema build for nested dataclasses / TypedDicts with discriminators by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8950](https://togithub.com/pydantic/pydantic/pull/8950) - Remove unnecessary logic for definitions schema gen with discriminated unions by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​8951](https://togithub.com/pydantic/pydantic/pull/8951) - Fix handling of optionals in `mypy` plugin by [@​dmontagu](https://togithub.com/dmontagu) in [#​9008](https://togithub.com/pydantic/pydantic/pull/9008) - Fix `PlainSerializer` usage with std type constructor by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9031](https://togithub.com/pydantic/pydantic/pull/9031) - Remove unnecessary warning for config in plugin by [@​dmontagu](https://togithub.com/dmontagu) in [#​9039](https://togithub.com/pydantic/pydantic/pull/9039) - Fix default value serializing by [@​NeevCohen](https://togithub.com/NeevCohen) in [#​9066](https://togithub.com/pydantic/pydantic/pull/9066) - Fix extra fields check in `Model.__getattr__()` by [@​NeevCohen](https://togithub.com/NeevCohen) in [#​9082](https://togithub.com/pydantic/pydantic/pull/9082) - Fix `ClassVar` forward ref inherited from parent class by [@​alexmojaki](https://togithub.com/alexmojaki) in [#​9097](https://togithub.com/pydantic/pydantic/pull/9097) - fix sequence like validator with strict `True` by [@​andresliszt](https://togithub.com/andresliszt) in [#​8977](https://togithub.com/pydantic/pydantic/pull/8977) - Improve warning message when a field name shadows a field in a parent model by [@​chan-vince](https://togithub.com/chan-vince) in [#​9105](https://togithub.com/pydantic/pydantic/pull/9105) - Do not warn about shadowed fields if they are not redefined in a child class by [@​chan-vince](https://togithub.com/chan-vince) in [#​9111](https://togithub.com/pydantic/pydantic/pull/9111) - Fix discriminated union bug with unsubstituted type var by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9124](https://togithub.com/pydantic/pydantic/pull/9124) - Support serialization of `deque` when passed to `Sequence[blah blah blah]` by [@​sydney-runkle](https://togithub.com/sydney-runkle) in [#​9128](https://togithub.com/pydantic/pydantic/pull/9128) - Init private attributes from super-types in `model_post_init` by [@​Viicos](https://togithub.com/Viicos) in [#​9134](https://togithub.com/pydantic/pydantic/pull/9134) - fix `model_construct` with `validation_alias` by [@​ornariece](https://togithub.com/ornariece) in [#​9144](https://togithub.com/pydantic/pydantic/pull/9144) - Ensure json-schema generator handles `Literal` `null` types by [@​bruno-f-cruz](https://togithub.com/bruno-f-cruz) in [#​9135](https://togithub.com/pydantic/pydantic/pull/9135) - **Fixed in v2.7.0**: Fix allow extra generic by [@​dmontagu](https://togithub.com/dmontagu) in [#​9193](https://togithub.com/pydantic/pydantic/pull/9193) ##### New Contributors - [@​hungtsetse](https://togithub.com/hungtsetse) made their first contribution in [#​8546](https://togithub.com/pydantic/pydantic/pull/8546) - [@​StrawHatDrag0n](https://togithub.com/StrawHatDrag0n) made their first contribution in [#​8583](https://togithub.com/pydantic/pydantic/pull/8583) - [@​anci3ntr0ck](https://togithub.com/anci3ntr0ck) made their first contribution in [#​8642](https://togithub.com/pydantic/pydantic/pull/8642) - [@​Holi0317](https://togithub.com/Holi0317) made their first contribution in [#​8650](https://togithub.com/pydantic/pydantic/pull/8650) - [@​bluenote10](https://togithub.com/bluenote10) made their first contribution in [#​8651](https://togithub.com/pydantic/pydantic/pull/8651) - [@​ADSteele916](https://togithub.com/ADSteele916) made their first contribution in [#​8703](https://togithub.com/pydantic/pydantic/pull/8703) - [@​musicinmybrain](https://togithub.com/musicinmybrain) made their first contribution in [#​8731](https://togithub.com/pydantic/pydantic/pull/8731) - [@​jks15satoshi](https://togithub.com/jks15satoshi) made their first contribution in [#​8706](https://togithub.com/pydantic/pydantic/pull/8706) - [@​13sin](https://togithub.com/13sin) made their first contribution in [#​8734](https://togithub.com/pydantic/pydantic/pull/8734) - [@​DanielNoord](https://togithub.com/DanielNoord) made their first contribution in [#​8738](https://togithub.com/pydantic/pydantic/pull/8738) - [@​conradogarciaberrotaran](https://togithub.com/conradogarciaberrotaran) made their first contribution in [#​8519](https://togithub.com/pydantic/pydantic/pull/8519) - [@​chris-griffin](https://togithub.com/chris-griffin) made their first contribution in [#​8775](https://togithub.com/pydantic/pydantic/pull/8775) - [@​LouisGobert](https://togithub.com/LouisGobert) made their first contribution in [#​8793](https://togithub.com/pydantic/pydantic/pull/8793) - [@​matsjoyce-refeyn](https://togithub.com/matsjoyce-refeyn) made their first contribution in [#​8751](https://togithub.com/pydantic/pydantic/pull/8751) - [@​devmonkey22](https://togithub.com/devmonkey22) made their first contribution in [#​7680](https://togithub.com/pydantic/pydantic/pull/7680) - [@​adamency](https://togithub.com/adamency) made their first contribution in [#​8847](https://togithub.com/pydantic/pydantic/pull/8847) - [@​MamfTheKramf](https://togithub.com/MamfTheKramf) made their first contribution in [#​8851](https://togithub.com/pydantic/pydantic/pull/8851) - [@​ornariece](https://togithub.com/ornariece) made their first contribution in [#​9001](https://togithub.com/pydantic/pydantic/pull/9001) - [@​alexeyt101](https://togithub.com/alexeyt101) made their first contribution in [#​9004](https://togithub.com/pydantic/pydantic/pull/9004) - [@​wannieman98](https://togithub.com/wannieman98) made their first contribution in [#​8947](https://togithub.com/pydantic/pydantic/pull/8947) - [@​solidguy7](https://togithub.com/solidguy7) made their first contribution in [#​9062](https://togithub.com/pydantic/pydantic/pull/9062) - [@​kloczek](https://togithub.com/kloczek) made their first contribution in [#​9047](https://togithub.com/pydantic/pydantic/pull/9047) - [@​jag-k](https://togithub.com/jag-k) made their first contribution in [#​9053](https://togithub.com/pydantic/pydantic/pull/9053) - [@​priya-gitTest](https://togithub.com/priya-gitTest) made their first contribution in [#​9088](https://togithub.com/pydantic/pydantic/pull/9088) - [@​Youssefares](https://togithub.com/Youssefares) made their first contribution in [#​9023](https://togithub.com/pydantic/pydantic/pull/9023) - [@​chan-vince](https://togithub.com/chan-vince) made their first contribution in [#​9105](https://togithub.com/pydantic/pydantic/pull/9105) - [@​bruno-f-cruz](https://togithub.com/bruno-f-cruz) made their first contribution in [#​9135](https://togithub.com/pydantic/pydantic/pull/9135) - [@​Lance-Drane](https://togithub.com/Lance-Drane) made their first contribution in [#​9166](https://togithub.com/pydantic/pydantic/pull/9166) </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 becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/libretime/libretime). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4yNjkuMiIsInVwZGF0ZWRJblZlciI6IjM3LjI2OS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: jo <ljonas@riseup.net>
581 lines
18 KiB
Python
581 lines
18 KiB
Python
from os import environ
|
|
from pathlib import Path
|
|
from typing import List, Union
|
|
from unittest import mock
|
|
|
|
from pydantic import BaseModel, Field
|
|
from pytest import mark, raises
|
|
from typing_extensions import Annotated
|
|
|
|
from libretime_shared.config import (
|
|
AnyHttpUrlStr,
|
|
BaseConfig,
|
|
DatabaseConfig,
|
|
IcecastOutput,
|
|
RabbitMQConfig,
|
|
ShoutcastOutput,
|
|
)
|
|
|
|
AnyOutput = Annotated[
|
|
Union[IcecastOutput, ShoutcastOutput],
|
|
Field(discriminator="kind"),
|
|
]
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class FixtureConfig(BaseConfig):
|
|
public_url: AnyHttpUrlStr
|
|
api_key: str
|
|
allowed_hosts: List[str] = []
|
|
database: DatabaseConfig
|
|
rabbitmq: RabbitMQConfig = RabbitMQConfig()
|
|
outputs: List[AnyOutput]
|
|
|
|
|
|
FIXTURE_CONFIG_JSON_SCHEMA = {
|
|
"$defs": {
|
|
"AudioAAC": {
|
|
"properties": {
|
|
"channels": {
|
|
"allOf": [{"$ref": "#/$defs/AudioChannels"}],
|
|
"default": "stereo",
|
|
},
|
|
"bitrate": {"title": "Bitrate", "type": "integer"},
|
|
"format": {
|
|
"const": "aac",
|
|
"default": "aac",
|
|
"enum": ["aac"],
|
|
"title": "Format",
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": ["bitrate"],
|
|
"title": "AudioAAC",
|
|
"type": "object",
|
|
},
|
|
"AudioChannels": {
|
|
"enum": ["stereo", "mono"],
|
|
"title": "AudioChannels",
|
|
"type": "string",
|
|
},
|
|
"AudioMP3": {
|
|
"properties": {
|
|
"channels": {
|
|
"allOf": [{"$ref": "#/$defs/AudioChannels"}],
|
|
"default": "stereo",
|
|
},
|
|
"bitrate": {"title": "Bitrate", "type": "integer"},
|
|
"format": {
|
|
"const": "mp3",
|
|
"default": "mp3",
|
|
"enum": ["mp3"],
|
|
"title": "Format",
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": ["bitrate"],
|
|
"title": "AudioMP3",
|
|
"type": "object",
|
|
},
|
|
"AudioOGG": {
|
|
"properties": {
|
|
"channels": {
|
|
"allOf": [{"$ref": "#/$defs/AudioChannels"}],
|
|
"default": "stereo",
|
|
},
|
|
"bitrate": {"title": "Bitrate", "type": "integer"},
|
|
"format": {
|
|
"const": "ogg",
|
|
"default": "ogg",
|
|
"enum": ["ogg"],
|
|
"title": "Format",
|
|
"type": "string",
|
|
},
|
|
"enable_metadata": {
|
|
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
|
"default": False,
|
|
"title": "Enable Metadata",
|
|
},
|
|
},
|
|
"required": ["bitrate"],
|
|
"title": "AudioOGG",
|
|
"type": "object",
|
|
},
|
|
"AudioOpus": {
|
|
"properties": {
|
|
"channels": {
|
|
"allOf": [{"$ref": "#/$defs/AudioChannels"}],
|
|
"default": "stereo",
|
|
},
|
|
"bitrate": {"title": "Bitrate", "type": "integer"},
|
|
"format": {
|
|
"const": "opus",
|
|
"default": "opus",
|
|
"enum": ["opus"],
|
|
"title": "Format",
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": ["bitrate"],
|
|
"title": "AudioOpus",
|
|
"type": "object",
|
|
},
|
|
"DatabaseConfig": {
|
|
"properties": {
|
|
"host": {
|
|
"default": "localhost",
|
|
"title": "Host",
|
|
"type": "string",
|
|
},
|
|
"port": {"default": 5432, "title": "Port", "type": "integer"},
|
|
"name": {
|
|
"default": "libretime",
|
|
"title": "Name",
|
|
"type": "string",
|
|
},
|
|
"user": {
|
|
"default": "libretime",
|
|
"title": "User",
|
|
"type": "string",
|
|
},
|
|
"password": {
|
|
"default": "libretime",
|
|
"title": "Password",
|
|
"type": "string",
|
|
},
|
|
},
|
|
"title": "DatabaseConfig",
|
|
"type": "object",
|
|
},
|
|
"IcecastOutput": {
|
|
"properties": {
|
|
"kind": {
|
|
"const": "icecast",
|
|
"default": "icecast",
|
|
"enum": ["icecast"],
|
|
"title": "Kind",
|
|
"type": "string",
|
|
},
|
|
"enabled": {
|
|
"default": False,
|
|
"title": "Enabled",
|
|
"type": "boolean",
|
|
},
|
|
"public_url": {
|
|
"anyOf": [
|
|
{"format": "uri", "type": "string"},
|
|
{"type": "null"},
|
|
],
|
|
"default": None,
|
|
"title": "Public Url",
|
|
},
|
|
"host": {
|
|
"default": "localhost",
|
|
"title": "Host",
|
|
"type": "string",
|
|
},
|
|
"port": {"default": 8000, "title": "Port", "type": "integer"},
|
|
"mount": {"title": "Mount", "type": "string"},
|
|
"source_user": {
|
|
"default": "source",
|
|
"title": "Source User",
|
|
"type": "string",
|
|
},
|
|
"source_password": {
|
|
"title": "Source Password",
|
|
"type": "string",
|
|
},
|
|
"admin_user": {
|
|
"default": "admin",
|
|
"title": "Admin User",
|
|
"type": "string",
|
|
},
|
|
"admin_password": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Admin Password",
|
|
},
|
|
"audio": {
|
|
"discriminator": {
|
|
"mapping": {
|
|
"aac": "#/$defs/AudioAAC",
|
|
"mp3": "#/$defs/AudioMP3",
|
|
"ogg": "#/$defs/AudioOGG",
|
|
"opus": "#/$defs/AudioOpus",
|
|
},
|
|
"propertyName": "format",
|
|
},
|
|
"oneOf": [
|
|
{"$ref": "#/$defs/AudioAAC"},
|
|
{"$ref": "#/$defs/AudioMP3"},
|
|
{"$ref": "#/$defs/AudioOGG"},
|
|
{"$ref": "#/$defs/AudioOpus"},
|
|
],
|
|
"title": "Audio",
|
|
},
|
|
"name": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Name",
|
|
},
|
|
"description": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Description",
|
|
},
|
|
"website": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Website",
|
|
},
|
|
"genre": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Genre",
|
|
},
|
|
"mobile": {
|
|
"default": False,
|
|
"title": "Mobile",
|
|
"type": "boolean",
|
|
},
|
|
},
|
|
"required": ["mount", "source_password", "audio"],
|
|
"title": "IcecastOutput",
|
|
"type": "object",
|
|
},
|
|
"RabbitMQConfig": {
|
|
"properties": {
|
|
"host": {
|
|
"default": "localhost",
|
|
"title": "Host",
|
|
"type": "string",
|
|
},
|
|
"port": {"default": 5672, "title": "Port", "type": "integer"},
|
|
"user": {
|
|
"default": "libretime",
|
|
"title": "User",
|
|
"type": "string",
|
|
},
|
|
"password": {
|
|
"default": "libretime",
|
|
"title": "Password",
|
|
"type": "string",
|
|
},
|
|
"vhost": {
|
|
"default": "/libretime",
|
|
"title": "Vhost",
|
|
"type": "string",
|
|
},
|
|
},
|
|
"title": "RabbitMQConfig",
|
|
"type": "object",
|
|
},
|
|
"ShoutcastOutput": {
|
|
"properties": {
|
|
"kind": {
|
|
"const": "shoutcast",
|
|
"default": "shoutcast",
|
|
"enum": ["shoutcast"],
|
|
"title": "Kind",
|
|
"type": "string",
|
|
},
|
|
"enabled": {
|
|
"default": False,
|
|
"title": "Enabled",
|
|
"type": "boolean",
|
|
},
|
|
"public_url": {
|
|
"anyOf": [
|
|
{"format": "uri", "type": "string"},
|
|
{"type": "null"},
|
|
],
|
|
"default": None,
|
|
"title": "Public Url",
|
|
},
|
|
"host": {
|
|
"default": "localhost",
|
|
"title": "Host",
|
|
"type": "string",
|
|
},
|
|
"port": {"default": 8000, "title": "Port", "type": "integer"},
|
|
"source_user": {
|
|
"default": "source",
|
|
"title": "Source User",
|
|
"type": "string",
|
|
},
|
|
"source_password": {
|
|
"title": "Source Password",
|
|
"type": "string",
|
|
},
|
|
"admin_user": {
|
|
"default": "admin",
|
|
"title": "Admin User",
|
|
"type": "string",
|
|
},
|
|
"admin_password": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Admin Password",
|
|
},
|
|
"audio": {
|
|
"discriminator": {
|
|
"mapping": {
|
|
"aac": "#/$defs/AudioAAC",
|
|
"mp3": "#/$defs/AudioMP3",
|
|
},
|
|
"propertyName": "format",
|
|
},
|
|
"oneOf": [
|
|
{"$ref": "#/$defs/AudioAAC"},
|
|
{"$ref": "#/$defs/AudioMP3"},
|
|
],
|
|
"title": "Audio",
|
|
},
|
|
"name": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Name",
|
|
},
|
|
"website": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Website",
|
|
},
|
|
"genre": {
|
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
|
"default": None,
|
|
"title": "Genre",
|
|
},
|
|
"mobile": {
|
|
"default": False,
|
|
"title": "Mobile",
|
|
"type": "boolean",
|
|
},
|
|
},
|
|
"required": ["source_password", "audio"],
|
|
"title": "ShoutcastOutput",
|
|
"type": "object",
|
|
},
|
|
},
|
|
"properties": {
|
|
"public_url": {"format": "uri", "title": "Public Url", "type": "string"},
|
|
"api_key": {"title": "Api Key", "type": "string"},
|
|
"allowed_hosts": {
|
|
"default": [],
|
|
"items": {"type": "string"},
|
|
"title": "Allowed Hosts",
|
|
"type": "array",
|
|
},
|
|
"database": {"$ref": "#/$defs/DatabaseConfig"},
|
|
"rabbitmq": {
|
|
"allOf": [{"$ref": "#/$defs/RabbitMQConfig"}],
|
|
"default": {
|
|
"host": "localhost",
|
|
"port": 5672,
|
|
"user": "libretime",
|
|
"password": "libretime",
|
|
"vhost": "/libretime",
|
|
},
|
|
},
|
|
"outputs": {
|
|
"items": {
|
|
"discriminator": {
|
|
"mapping": {
|
|
"icecast": "#/$defs/IcecastOutput",
|
|
"shoutcast": "#/$defs/ShoutcastOutput",
|
|
},
|
|
"propertyName": "kind",
|
|
},
|
|
"oneOf": [
|
|
{"$ref": "#/$defs/IcecastOutput"},
|
|
{"$ref": "#/$defs/ShoutcastOutput"},
|
|
],
|
|
},
|
|
"title": "Outputs",
|
|
"type": "array",
|
|
},
|
|
},
|
|
"required": ["public_url", "api_key", "database", "outputs"],
|
|
"title": "FixtureConfig",
|
|
"type": "object",
|
|
}
|
|
|
|
|
|
FIXTURE_CONFIG_RAW = """
|
|
public_url: http://libretime.example.org/
|
|
api_key: "f3bf04fc"
|
|
allowed_hosts:
|
|
- example.com
|
|
- sub.example.com
|
|
|
|
# Comment !
|
|
database:
|
|
host: "localhost"
|
|
port: 5432
|
|
|
|
ignored: "ignored"
|
|
|
|
outputs:
|
|
- enabled: true
|
|
kind: icecast
|
|
host: localhost
|
|
port: 8000
|
|
mount: main.ogg
|
|
source_password: hackme
|
|
audio:
|
|
format: ogg
|
|
bitrate: 256
|
|
"""
|
|
|
|
|
|
def test_base_config(tmp_path: Path):
|
|
config_filepath = tmp_path / "config.yml"
|
|
config_filepath.write_text(FIXTURE_CONFIG_RAW)
|
|
|
|
with mock.patch.dict(
|
|
environ,
|
|
{
|
|
"LIBRETIME_API": "invalid",
|
|
"LIBRETIME_DATABASE_PORT": "8888",
|
|
"LIBRETIME_DATABASE": "invalid",
|
|
"LIBRETIME_RABBITMQ": "invalid",
|
|
"LIBRETIME_RABBITMQ_HOST": "changed",
|
|
"LIBRETIME_OUTPUTS_0_ENABLED": "false",
|
|
"LIBRETIME_OUTPUTS_0_HOST": "changed",
|
|
"WRONGPREFIX_API_KEY": "invalid",
|
|
},
|
|
):
|
|
config = FixtureConfig(config_filepath)
|
|
|
|
assert config.model_json_schema() == FIXTURE_CONFIG_JSON_SCHEMA
|
|
|
|
assert config.public_url == "http://libretime.example.org"
|
|
assert config.api_key == "f3bf04fc"
|
|
assert config.allowed_hosts == ["example.com", "sub.example.com"]
|
|
assert config.database.host == "localhost"
|
|
assert config.database.port == 8888
|
|
assert config.rabbitmq.host == "changed"
|
|
assert config.rabbitmq.port == 5672
|
|
assert config.outputs[0].enabled is False
|
|
assert config.outputs[0].kind == "icecast"
|
|
assert config.outputs[0].host == "changed"
|
|
assert config.outputs[0].audio.format == "ogg"
|
|
|
|
# Optional model: loading default values (rabbitmq)
|
|
with mock.patch.dict(environ, {}):
|
|
config = FixtureConfig(config_filepath)
|
|
assert config.allowed_hosts == ["example.com", "sub.example.com"]
|
|
assert config.rabbitmq.host == "localhost"
|
|
assert config.rabbitmq.port == 5672
|
|
|
|
# Optional model: overriding using environment (rabbitmq)
|
|
with mock.patch.dict(
|
|
environ,
|
|
{
|
|
"LIBRETIME_RABBITMQ_HOST": "changed",
|
|
"LIBRETIME_ALLOWED_HOSTS": "example.com, changed.example.com",
|
|
},
|
|
):
|
|
config = FixtureConfig(config_filepath)
|
|
assert config.allowed_hosts == ["example.com", "changed.example.com"]
|
|
assert config.rabbitmq.host == "changed"
|
|
assert config.rabbitmq.port == 5672
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class RequiredModel(BaseModel):
|
|
api_key: str
|
|
with_default: str = "original"
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
class FixtureWithRequiredSubmodelConfig(BaseConfig):
|
|
required: RequiredModel
|
|
|
|
|
|
FIXTURE_WITH_REQUIRED_SUBMODEL_CONFIG_RAW = """
|
|
required:
|
|
api_key: "test_key"
|
|
"""
|
|
|
|
|
|
def test_base_config_required_submodel(tmp_path: Path):
|
|
config_filepath = tmp_path / "config.yml"
|
|
config_filepath.write_text(FIXTURE_WITH_REQUIRED_SUBMODEL_CONFIG_RAW)
|
|
|
|
# With config file
|
|
with mock.patch.dict(environ, {}):
|
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
|
assert config.required.api_key == "test_key"
|
|
assert config.required.with_default == "original"
|
|
|
|
# With env variables
|
|
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "test_key"}):
|
|
config = FixtureWithRequiredSubmodelConfig(None)
|
|
assert config.required.api_key == "test_key"
|
|
assert config.required.with_default == "original"
|
|
|
|
# With env variables override
|
|
with mock.patch.dict(environ, {"LIBRETIME_REQUIRED_API_KEY": "changed"}):
|
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
|
assert config.required.api_key == "changed"
|
|
assert config.required.with_default == "original"
|
|
|
|
# With env variables default override
|
|
with mock.patch.dict(
|
|
environ,
|
|
{
|
|
"LIBRETIME_REQUIRED_API_KEY": "changed",
|
|
"LIBRETIME_REQUIRED_WITH_DEFAULT": "changed",
|
|
},
|
|
):
|
|
config = FixtureWithRequiredSubmodelConfig(config_filepath)
|
|
assert config.required.api_key == "changed"
|
|
assert config.required.with_default == "changed"
|
|
|
|
# Raise validation error
|
|
with mock.patch.dict(environ, {}):
|
|
with raises(SystemExit):
|
|
FixtureWithRequiredSubmodelConfig(None)
|
|
|
|
|
|
def test_base_config_from_init() -> None:
|
|
class FromInitFixtureConfig(BaseConfig):
|
|
found: str
|
|
override: str
|
|
|
|
with mock.patch.dict(environ, {"LIBRETIME_OVERRIDE": "changed"}):
|
|
config = FromInitFixtureConfig(
|
|
found="changed",
|
|
override="invalid",
|
|
)
|
|
|
|
assert config.found == "changed"
|
|
assert config.override == "changed"
|
|
|
|
|
|
FIXTURE_CONFIG_RAW_MISSING = """
|
|
database:
|
|
host: "localhost"
|
|
"""
|
|
|
|
FIXTURE_CONFIG_RAW_INVALID = """
|
|
database
|
|
host: "localhost"
|
|
"""
|
|
|
|
|
|
@mark.parametrize(
|
|
"raw,exception",
|
|
[
|
|
(FIXTURE_CONFIG_RAW_INVALID, SystemExit),
|
|
(FIXTURE_CONFIG_RAW_MISSING, SystemExit),
|
|
],
|
|
)
|
|
def test_load_config_error(tmp_path: Path, raw, exception):
|
|
config_filepath = tmp_path / "config.yml"
|
|
config_filepath.write_text(raw)
|
|
|
|
with raises(exception):
|
|
with mock.patch.dict(environ, {}):
|
|
FixtureConfig(config_filepath)
|