diff --git a/.flake8 b/.flake8 index 9705b0356a1934a128a512b20e40ec680472b87c..f5883816b62a531fb55b5fd3db72470b6ab57e49 100644 --- a/.flake8 +++ b/.flake8 @@ -2,5 +2,5 @@ max-line-length = 99 max-doc-length = 99 docstring-convention=google -exclude = python, tests/*, __init__.py, src/aura_engine/client/* +exclude = python, tests/*, __init__.py, src/aura_engine_api/*, src/aura_steering_api/*, src/aura_tank_api/* ignore = E121,E123,E126,E203,E226,E24,E704,W503,N802,D105,D107,D200,D202,D212,D417 diff --git a/.openapi-client-tank.yml b/.openapi-client-tank.yml new file mode 100644 index 0000000000000000000000000000000000000000..56571b0cf2baaa9648f13a681763ed47841b2579 --- /dev/null +++ b/.openapi-client-tank.yml @@ -0,0 +1,2 @@ +project_name_override: .build +package_name_override: aura_tank_api \ No newline at end of file diff --git a/Makefile b/Makefile index b780cccd4dace5d521ac8d6566d4f19413c52b9d..857dace859d1d270b279c512b47fd6060bd1ced6 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ help:: @echo "$(APP_NAME) targets:" @echo " init.app - init application environment" @echo " init.dev - init development environment" + @echo " build.models - build models for API requests/responses" @echo " lint - verify code style" @echo " spell - check spelling of text" @echo " format - apply automatic formatting" @@ -59,6 +60,14 @@ init.dev:: pyproject.toml cp -n config/sample.engine.ini config/engine.ini mkdir -p .cache +build.models:: + rm -rf .build + poetry run openapi-python-client generate --path schemas/openapi-tank.json --config .openapi-client-tank.yml + cp -r .build/aura_tank_api/models src/aura_tank_api + cp .build/aura_tank_api/py.typed src/aura_tank_api + cp .build/aura_tank_api/types.py src/aura_tank_api + + lint:: poetry run python3 -m flake8 . diff --git a/pyproject.toml b/pyproject.toml index 16fe726500760dbe37804d21508a628d9c47d6ad..62c5958659f586f99f412ab9601dec05a2e885b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ coverage = {extras = ["toml"], version = "^7.2.5"} [tool.coverage.run] source = ["src"] -omit = [] +omit = ["types.py"] [tool.coverage.report] # TODO Increase after we have more test cases fail_under = 25 diff --git a/schemas/openapi-tank.json b/schemas/openapi-tank.json new file mode 100644 index 0000000000000000000000000000000000000000..6673c07b6932f80f054d285059cf064ebc22dfab --- /dev/null +++ b/schemas/openapi-tank.json @@ -0,0 +1,144 @@ +{ + "openapi" : "3.0.1", + "info" : { + "contact" : { + "name" : "aura.radio", + "url" : "https://aura.radio" + }, + "description" : "Import & Playlist Daemon", + "license" : { + "name" : "AGPLv3", + "url" : "https://www.gnu.org/licenses/agpl-3.0" + }, + "title" : "AURA Tank API", + "version" : "1.0" + }, + "servers" : [ { + "url" : "/" + } ], + "paths" : { + }, + "components" : { + "schemas" : { + "File" : { + "properties" : { + "created" : { + "type" : "string" + }, + "duration" : { + "type" : "integer" + }, + "id" : { + "type" : "integer" + }, + "metadata" : { + "$ref" : "#/components/schemas/FileMetadata" + }, + "show" : { + "type" : "string" + }, + "size" : { + "type" : "integer" + }, + "source" : { + "$ref" : "#/components/schemas/FileSource" + }, + "updated" : { + "type" : "string" + } + }, + "type" : "object" + }, + "FileMetadata" : { + "properties" : { + "album" : { + "type" : "string" + }, + "artist" : { + "description" : "actually a full-text index would be nice here...", + "type" : "string" + }, + "isrc" : { + "type" : "string" + }, + "organization" : { + "type" : "string" + }, + "title" : { + "type" : "string" + } + }, + "type" : "object" + }, + "FileSource" : { + "properties" : { + "hash" : { + "type" : "string" + }, + "import" : { + "$ref" : "#/components/schemas/Import" + }, + "uri" : { + "type" : "string" + } + }, + "type" : "object" + }, + "Import" : { + "properties" : { + "error" : { + "type" : "string" + }, + "state" : { + "type" : "string" + } + }, + "type" : "object" + }, + "Playlist" : { + "properties" : { + "created" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "entries" : { + "items" : { + "$ref" : "#/components/schemas/PlaylistEntry" + }, + "type" : "array" + }, + "id" : { + "type" : "integer" + }, + "playout-mode" : { + "type" : "string" + }, + "show" : { + "type" : "string" + }, + "updated" : { + "type" : "string" + } + }, + "type" : "object" + }, + "PlaylistEntry" : { + "properties" : { + "duration" : { + "type" : "integer" + }, + "file" : { + "$ref" : "#/components/schemas/File" + }, + "uri" : { + "type" : "string" + } + }, + "type" : "object" + } + }, + "x-original-swagger-version" : "2.0" + } +} \ No newline at end of file diff --git a/src/aura_tank_api/__init__.py b/src/aura_tank_api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/aura_tank_api/models/__init__.py b/src/aura_tank_api/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d29566926351ce07f8880624e467dd73e05121fd --- /dev/null +++ b/src/aura_tank_api/models/__init__.py @@ -0,0 +1,17 @@ +""" Contains all the data models used in inputs/outputs """ + +from .file import File +from .file_metadata import FileMetadata +from .file_source import FileSource +from .import_ import Import +from .playlist import Playlist +from .playlist_entry import PlaylistEntry + +__all__ = ( + "File", + "FileMetadata", + "FileSource", + "Import", + "Playlist", + "PlaylistEntry", +) diff --git a/src/aura_tank_api/models/file.py b/src/aura_tank_api/models/file.py new file mode 100644 index 0000000000000000000000000000000000000000..b408d50d2110fd027d0c1edd8337887a3b60579c --- /dev/null +++ b/src/aura_tank_api/models/file.py @@ -0,0 +1,137 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.file_metadata import FileMetadata + from ..models.file_source import FileSource + + +T = TypeVar("T", bound="File") + + +@attr.s(auto_attribs=True) +class File: + """ + Attributes: + created (Union[Unset, str]): + duration (Union[Unset, int]): + id (Union[Unset, int]): + metadata (Union[Unset, FileMetadata]): + show (Union[Unset, str]): + size (Union[Unset, int]): + source (Union[Unset, FileSource]): + updated (Union[Unset, str]): + """ + + created: Union[Unset, str] = UNSET + duration: Union[Unset, int] = UNSET + id: Union[Unset, int] = UNSET + metadata: Union[Unset, "FileMetadata"] = UNSET + show: Union[Unset, str] = UNSET + size: Union[Unset, int] = UNSET + source: Union[Unset, "FileSource"] = UNSET + updated: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + created = self.created + duration = self.duration + id = self.id + metadata: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.metadata, Unset): + metadata = self.metadata.to_dict() + + show = self.show + size = self.size + source: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.source, Unset): + source = self.source.to_dict() + + updated = self.updated + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if created is not UNSET: + field_dict["created"] = created + if duration is not UNSET: + field_dict["duration"] = duration + if id is not UNSET: + field_dict["id"] = id + if metadata is not UNSET: + field_dict["metadata"] = metadata + if show is not UNSET: + field_dict["show"] = show + if size is not UNSET: + field_dict["size"] = size + if source is not UNSET: + field_dict["source"] = source + if updated is not UNSET: + field_dict["updated"] = updated + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + from ..models.file_metadata import FileMetadata + from ..models.file_source import FileSource + + d = src_dict.copy() + created = d.pop("created", UNSET) + + duration = d.pop("duration", UNSET) + + id = d.pop("id", UNSET) + + _metadata = d.pop("metadata", UNSET) + metadata: Union[Unset, FileMetadata] + if isinstance(_metadata, Unset): + metadata = UNSET + else: + metadata = FileMetadata.from_dict(_metadata) + + show = d.pop("show", UNSET) + + size = d.pop("size", UNSET) + + _source = d.pop("source", UNSET) + source: Union[Unset, FileSource] + if isinstance(_source, Unset): + source = UNSET + else: + source = FileSource.from_dict(_source) + + updated = d.pop("updated", UNSET) + + file = cls( + created=created, + duration=duration, + id=id, + metadata=metadata, + show=show, + size=size, + source=source, + updated=updated, + ) + + file.additional_properties = d + return file + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/models/file_metadata.py b/src/aura_tank_api/models/file_metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd06f46e60ea3795c7162ec4082dabb716406e6 --- /dev/null +++ b/src/aura_tank_api/models/file_metadata.py @@ -0,0 +1,89 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="FileMetadata") + + +@attr.s(auto_attribs=True) +class FileMetadata: + """ + Attributes: + album (Union[Unset, str]): + artist (Union[Unset, str]): actually a full-text index would be nice here... + isrc (Union[Unset, str]): + organization (Union[Unset, str]): + title (Union[Unset, str]): + """ + + album: Union[Unset, str] = UNSET + artist: Union[Unset, str] = UNSET + isrc: Union[Unset, str] = UNSET + organization: Union[Unset, str] = UNSET + title: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + album = self.album + artist = self.artist + isrc = self.isrc + organization = self.organization + title = self.title + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if album is not UNSET: + field_dict["album"] = album + if artist is not UNSET: + field_dict["artist"] = artist + if isrc is not UNSET: + field_dict["isrc"] = isrc + if organization is not UNSET: + field_dict["organization"] = organization + if title is not UNSET: + field_dict["title"] = title + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + album = d.pop("album", UNSET) + + artist = d.pop("artist", UNSET) + + isrc = d.pop("isrc", UNSET) + + organization = d.pop("organization", UNSET) + + title = d.pop("title", UNSET) + + file_metadata = cls( + album=album, + artist=artist, + isrc=isrc, + organization=organization, + title=title, + ) + + file_metadata.additional_properties = d + return file_metadata + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/models/file_source.py b/src/aura_tank_api/models/file_source.py new file mode 100644 index 0000000000000000000000000000000000000000..97032b69b93018a1eaacae2d3a6a17370420cb33 --- /dev/null +++ b/src/aura_tank_api/models/file_source.py @@ -0,0 +1,87 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.import_ import Import + + +T = TypeVar("T", bound="FileSource") + + +@attr.s(auto_attribs=True) +class FileSource: + """ + Attributes: + hash_ (Union[Unset, str]): + import_ (Union[Unset, Import]): + uri (Union[Unset, str]): + """ + + hash_: Union[Unset, str] = UNSET + import_: Union[Unset, "Import"] = UNSET + uri: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + hash_ = self.hash_ + import_: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.import_, Unset): + import_ = self.import_.to_dict() + + uri = self.uri + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if hash_ is not UNSET: + field_dict["hash"] = hash_ + if import_ is not UNSET: + field_dict["import"] = import_ + if uri is not UNSET: + field_dict["uri"] = uri + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + from ..models.import_ import Import + + d = src_dict.copy() + hash_ = d.pop("hash", UNSET) + + _import_ = d.pop("import", UNSET) + import_: Union[Unset, Import] + if isinstance(_import_, Unset): + import_ = UNSET + else: + import_ = Import.from_dict(_import_) + + uri = d.pop("uri", UNSET) + + file_source = cls( + hash_=hash_, + import_=import_, + uri=uri, + ) + + file_source.additional_properties = d + return file_source + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/models/import_.py b/src/aura_tank_api/models/import_.py new file mode 100644 index 0000000000000000000000000000000000000000..7490f941558e2528ff244ee43566f94be3b06370 --- /dev/null +++ b/src/aura_tank_api/models/import_.py @@ -0,0 +1,65 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="Import") + + +@attr.s(auto_attribs=True) +class Import: + """ + Attributes: + error (Union[Unset, str]): + state (Union[Unset, str]): + """ + + error: Union[Unset, str] = UNSET + state: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + error = self.error + state = self.state + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if error is not UNSET: + field_dict["error"] = error + if state is not UNSET: + field_dict["state"] = state + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + error = d.pop("error", UNSET) + + state = d.pop("state", UNSET) + + import_ = cls( + error=error, + state=state, + ) + + import_.additional_properties = d + return import_ + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/models/playlist.py b/src/aura_tank_api/models/playlist.py new file mode 100644 index 0000000000000000000000000000000000000000..426ed62b51e51f5b19fb25576027478d8de65cbf --- /dev/null +++ b/src/aura_tank_api/models/playlist.py @@ -0,0 +1,123 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.playlist_entry import PlaylistEntry + + +T = TypeVar("T", bound="Playlist") + + +@attr.s(auto_attribs=True) +class Playlist: + """ + Attributes: + created (Union[Unset, str]): + description (Union[Unset, str]): + entries (Union[Unset, List['PlaylistEntry']]): + id (Union[Unset, int]): + playout_mode (Union[Unset, str]): + show (Union[Unset, str]): + updated (Union[Unset, str]): + """ + + created: Union[Unset, str] = UNSET + description: Union[Unset, str] = UNSET + entries: Union[Unset, List["PlaylistEntry"]] = UNSET + id: Union[Unset, int] = UNSET + playout_mode: Union[Unset, str] = UNSET + show: Union[Unset, str] = UNSET + updated: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + created = self.created + description = self.description + entries: Union[Unset, List[Dict[str, Any]]] = UNSET + if not isinstance(self.entries, Unset): + entries = [] + for entries_item_data in self.entries: + entries_item = entries_item_data.to_dict() + + entries.append(entries_item) + + id = self.id + playout_mode = self.playout_mode + show = self.show + updated = self.updated + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if created is not UNSET: + field_dict["created"] = created + if description is not UNSET: + field_dict["description"] = description + if entries is not UNSET: + field_dict["entries"] = entries + if id is not UNSET: + field_dict["id"] = id + if playout_mode is not UNSET: + field_dict["playout-mode"] = playout_mode + if show is not UNSET: + field_dict["show"] = show + if updated is not UNSET: + field_dict["updated"] = updated + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + from ..models.playlist_entry import PlaylistEntry + + d = src_dict.copy() + created = d.pop("created", UNSET) + + description = d.pop("description", UNSET) + + entries = [] + _entries = d.pop("entries", UNSET) + for entries_item_data in _entries or []: + entries_item = PlaylistEntry.from_dict(entries_item_data) + + entries.append(entries_item) + + id = d.pop("id", UNSET) + + playout_mode = d.pop("playout-mode", UNSET) + + show = d.pop("show", UNSET) + + updated = d.pop("updated", UNSET) + + playlist = cls( + created=created, + description=description, + entries=entries, + id=id, + playout_mode=playout_mode, + show=show, + updated=updated, + ) + + playlist.additional_properties = d + return playlist + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/models/playlist_entry.py b/src/aura_tank_api/models/playlist_entry.py new file mode 100644 index 0000000000000000000000000000000000000000..c575c632048a06c3101227b0592c05f3e72fb34d --- /dev/null +++ b/src/aura_tank_api/models/playlist_entry.py @@ -0,0 +1,87 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.file import File + + +T = TypeVar("T", bound="PlaylistEntry") + + +@attr.s(auto_attribs=True) +class PlaylistEntry: + """ + Attributes: + duration (Union[Unset, int]): + file (Union[Unset, File]): + uri (Union[Unset, str]): + """ + + duration: Union[Unset, int] = UNSET + file: Union[Unset, "File"] = UNSET + uri: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + duration = self.duration + file: Union[Unset, Dict[str, Any]] = UNSET + if not isinstance(self.file, Unset): + file = self.file.to_dict() + + uri = self.uri + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if duration is not UNSET: + field_dict["duration"] = duration + if file is not UNSET: + field_dict["file"] = file + if uri is not UNSET: + field_dict["uri"] = uri + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + from ..models.file import File + + d = src_dict.copy() + duration = d.pop("duration", UNSET) + + _file = d.pop("file", UNSET) + file: Union[Unset, File] + if isinstance(_file, Unset): + file = UNSET + else: + file = File.from_dict(_file) + + uri = d.pop("uri", UNSET) + + playlist_entry = cls( + duration=duration, + file=file, + uri=uri, + ) + + playlist_entry.additional_properties = d + return playlist_entry + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/aura_tank_api/py.typed b/src/aura_tank_api/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..1aad32711f3d86135c552ae38665f4dcc73f3ce2 --- /dev/null +++ b/src/aura_tank_api/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 \ No newline at end of file diff --git a/src/aura_tank_api/types.py b/src/aura_tank_api/types.py new file mode 100644 index 0000000000000000000000000000000000000000..599eeb9f5eef454afb0f9db35e08ef57de14c3a7 --- /dev/null +++ b/src/aura_tank_api/types.py @@ -0,0 +1,44 @@ +""" Contains some shared types for properties """ +from http import HTTPStatus +from typing import BinaryIO, Generic, Literal, MutableMapping, Optional, Tuple, TypeVar + +import attr + + +class Unset: + def __bool__(self) -> Literal[False]: + return False + + +UNSET: Unset = Unset() + +FileJsonType = Tuple[Optional[str], BinaryIO, Optional[str]] + + +@attr.s(auto_attribs=True) +class File: + """Contains information for file uploads""" + + payload: BinaryIO + file_name: Optional[str] = None + mime_type: Optional[str] = None + + def to_tuple(self) -> FileJsonType: + """Return a tuple representation that httpx will accept for multipart/form-data""" + return self.file_name, self.payload, self.mime_type + + +T = TypeVar("T") + + +@attr.s(auto_attribs=True) +class Response(Generic[T]): + """A response from an endpoint""" + + status_code: HTTPStatus + content: bytes + headers: MutableMapping[str, str] + parsed: Optional[T] + + +__all__ = ["File", "Response", "FileJsonType"]