From 28b89c179b15ca1424a34aa6fe86cc045dc2b80c Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Tue, 30 Aug 2022 12:04:42 +0200 Subject: [server] make VersionedItemInfo data structure a common API again --- src/hydrilla/item_infos.py | 66 ++++++++++++++++++++++++++ src/hydrilla/server/malcontent.py | 99 ++++++++++----------------------------- src/hydrilla/server/serve.py | 4 +- 3 files changed, 92 insertions(+), 77 deletions(-) diff --git a/src/hydrilla/item_infos.py b/src/hydrilla/item_infos.py index 9a87b40..0e10871 100644 --- a/src/hydrilla/item_infos.py +++ b/src/hydrilla/item_infos.py @@ -484,3 +484,69 @@ class CategorizedItemInfo(Categorizable, t.Generic[CategorizedType, CategoryKeyT def is_empty(self) -> bool: """....""" return len(self.items) == 0 + + +VersionedType = t.TypeVar('VersionedType', ResourceInfo, MappingInfo) + +class VersionedItemInfo( + CategorizedItemInfo[VersionedType, versions.VerTuple], + t.Generic[VersionedType] +): + """Stores data of multiple versions of given resource/mapping.""" + SelfType = t.TypeVar('SelfType', bound='VersionedItemInfo[VersionedType]') + + def register(self: 'SelfType', item_info: VersionedType) -> 'SelfType': + """ + Make item info queryable by version. Perform sanity checks for uuid. + """ + return self._update(item_info.version, lambda old_info: item_info) + + def unregister(self: 'SelfType', version: versions.VerTuple) -> 'SelfType': + """....""" + return self._update(version, lambda old_info: None) + + @property + def newest_version(self) -> versions.VerTuple: + """....""" + assert not self.is_empty() + + return max(self.items.keys()) + + @property + def newest_info(self) -> VersionedType: + """Find and return info of the newest version of item.""" + return self.items[self.newest_version] + + def get_by_ver(self, ver: t.Sequence[int]) -> t.Optional[VersionedType]: + """ + Find and return info of the specified version of the item (or None if + absent). + """ + return self.items.get(versions.normalize(ver)) + + def get_all(self) -> t.Iterable[VersionedType]: + """Generate item info for all its versions, from oldest to newest.""" + return [self.items[version] for version in sorted(self.items.keys())] + +VersionedResourceInfo = VersionedItemInfo[ResourceInfo] +VersionedMappingInfo = VersionedItemInfo[MappingInfo] + + +VersionedItemInfoMap = Map[str, VersionedItemInfo] +VersionedResourceInfoMap = Map[str, VersionedResourceInfo] +VersionedMappingInfoMap = Map[str, VersionedMappingInfo] + +def register_in_map( + map: Map[str, VersionedItemInfo[VersionedType]], + info: VersionedType +) -> Map[str, VersionedItemInfo[VersionedType]]: + versioned_info = map.get(info.identifier, VersionedItemInfo()) + + return map.set(info.identifier, versioned_info.register(info)) + +def all_map_infos( + map: Map[str, VersionedItemInfo[VersionedType]] +) -> t.Iterator[VersionedType]: + for versioned_info in map.values(): + for item_info in versioned_info.get_all(): + yield item_info diff --git a/src/hydrilla/server/malcontent.py b/src/hydrilla/server/malcontent.py index af925a0..9557161 100644 --- a/src/hydrilla/server/malcontent.py +++ b/src/hydrilla/server/malcontent.py @@ -33,63 +33,22 @@ import typing as t from pathlib import Path +from immutables import Map + from ..translations import smart_gettext as _ from ..exceptions import HaketiloException from .. import versions from .. import item_infos from .. import pattern_tree -VersionedType = t.TypeVar( - 'VersionedType', - item_infos.ResourceInfo, - item_infos.MappingInfo -) - -class VersionedItemInfo( - item_infos.CategorizedItemInfo[VersionedType, versions.VerTuple], - t.Generic[VersionedType] -): - """Stores data of multiple versions of given resource/mapping.""" - SelfType = t.TypeVar('SelfType', bound='VersionedItemInfo[VersionedType]') - - def register(self: 'SelfType', item_info: VersionedType) -> 'SelfType': - """ - Make item info queryable by version. Perform sanity checks for uuid. - """ - return self._update(item_info.version, lambda old_info: item_info) - - def unregister(self: 'SelfType', version: versions.VerTuple) -> 'SelfType': - """....""" - return self._update(version, lambda old_info: None) - - @property - def newest_version(self) -> versions.VerTuple: - """....""" - assert not self.is_empty() - - return max(self.items.keys()) - - @property - def newest_info(self) -> VersionedType: - """Find and return info of the newest version of item.""" - return self.items[self.newest_version] - - def get_by_ver(self, ver: t.Sequence[int]) -> t.Optional[VersionedType]: - """ - Find and return info of the specified version of the item (or None if - absent). - """ - return self.items.get(versions.normalize(ver)) - - def get_all(self) -> t.Iterable[VersionedType]: - """Generate item info for all its versions, from oldest to newest.""" - return [self.items[version] for version in sorted(self.items.keys())] - MappingTree = pattern_tree.PatternTree[item_infos.MappingInfo] -ResourceInfos = dict[str, VersionedItemInfo[item_infos.ResourceInfo]] -MappingInfos = dict[str, VersionedItemInfo[item_infos.MappingInfo]] +# VersionedType = t.TypeVar( +# 'VersionedType', +# item_infos.ResourceInfo, +# item_infos.MappingInfo +# ) class Malcontent: """ @@ -109,8 +68,8 @@ class Malcontent: self.werror: bool = werror self.verify_files: bool = verify_files - self.resource_infos: ResourceInfos = {} - self.mapping_infos: MappingInfos = {} + self.resource_infos: item_infos.VersionedResourceInfoMap = Map() + self.mapping_infos: item_infos.VersionedMappingInfoMap = Map() self.mapping_tree: MappingTree = MappingTree() @@ -161,15 +120,6 @@ class Malcontent: else: logging.error(msg) - @staticmethod - def _register_info( - infos: dict[str, VersionedItemInfo[VersionedType]], - identifier: str, - item_info: VersionedType - ) -> None: - versioned_info = infos.get(identifier, VersionedItemInfo()) - infos[identifier] = versioned_info.register(item_info) - def _load_item(self, type: item_infos.ItemType, ver_file: Path) \ -> None: """ @@ -196,16 +146,15 @@ class Malcontent: self._check_package_files(item_info) if isinstance(item_info, item_infos.ResourceInfo): - self._register_info(self.resource_infos, identifier, item_info) + self.resource_infos = item_infos.register_in_map( + map = self.resource_infos, + info = item_info + ) else: - self._register_info(self.mapping_infos, identifier, item_info) - - @staticmethod - def _all_infos(infos: dict[str, VersionedItemInfo[VersionedType]]) \ - -> t.Iterator[VersionedType]: - for versioned_info in infos.values(): - for item_info in versioned_info.get_all(): - yield item_info + self.mapping_infos = item_infos.register_in_map( + map = self.mapping_infos, + info = item_info + ) def _report_missing(self) -> None: """ @@ -221,7 +170,7 @@ class Malcontent: ver=versions.version_string(info.version)) logging.error(msg) - for resource_info in self._all_infos(self.resource_infos): + for resource_info in item_infos.all_map_infos(self.resource_infos): for dep_specifier in resource_info.dependencies: identifier = dep_specifier.identifier if identifier not in self.resource_infos: @@ -236,7 +185,7 @@ class Malcontent: ver=versions.version_string(info.version)) logging.error(msg) - for mapping_info in self._all_infos(self.mapping_infos): + for mapping_info in item_infos.all_map_infos(self.mapping_infos): for resource_specifier in mapping_info.payloads.values(): identifier = resource_specifier.identifier if identifier not in self.resource_infos: @@ -252,8 +201,8 @@ class Malcontent: logging.error(msg) infos: t.Iterable[item_infos.AnyInfo] = ( - *self._all_infos(self.mapping_infos), - *self._all_infos(self.resource_infos) + *item_infos.all_map_infos(self.mapping_infos), + *item_infos.all_map_infos(self.resource_infos) ) for item_info in infos: for mapping_specifier in item_info.required_mappings: @@ -266,7 +215,7 @@ class Malcontent: Initialize structures needed to serve queries. Called once after all data gets loaded. """ - for info in self._all_infos(self.mapping_infos): + for info in item_infos.all_map_infos(self.mapping_infos): for pattern in info.payloads: try: self.mapping_tree = \ @@ -299,7 +248,7 @@ class Malcontent: return list(collected.values()) def get_all_resources(self) -> t.Sequence[item_infos.ResourceInfo]: - return tuple(self._all_infos(self.resource_infos)) + return tuple(item_infos.all_map_infos(self.resource_infos)) def get_all_mappings(self) -> t.Sequence[item_infos.MappingInfo]: - return tuple(self._all_infos(self.mapping_infos)) + return tuple(item_infos.all_map_infos(self.mapping_infos)) diff --git a/src/hydrilla/server/serve.py b/src/hydrilla/server/serve.py index 823437a..dc85f85 100644 --- a/src/hydrilla/server/serve.py +++ b/src/hydrilla/server/serve.py @@ -124,7 +124,7 @@ def get_resource_or_mapping(item_type: str, identifier: str) \ identifier = match.group(1) - infos: t.Mapping[str, malcontent.VersionedItemInfo] + infos: t.Mapping[str, item_infos.VersionedItemInfo] if item_type == 'resource': infos = get_malcontent().resource_infos else: @@ -135,7 +135,7 @@ def get_resource_or_mapping(item_type: str, identifier: str) \ if versioned_info is None: flask.abort(404) - info = versioned_info.newest_info() + info = versioned_info.newest_info # no need for send_from_directory(); path is safe, constructed by us info_path = f'{info.identifier}/{versions.version_string(info.version)}' -- cgit v1.2.3