From c37ce8fd7d7539ea21af32f9542ee973ac080fe0 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Thu, 9 Nov 2023 19:25:31 +0100 Subject: [PATCH] Rating implementation A fixed field called rating is implemented, accepting values from 1 to 10, or None if not set. --- tests/test_api.py | 2 +- tests/test_event.py | 29 ++++++++++++++++++++++++++++- tscat/base.py | 19 ++++++++++++++----- tscat/import_export.py | 4 ++++ tscat/orm_sqlalchemy/__init__.py | 3 +++ tscat/orm_sqlalchemy/orm.py | 4 +++- 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index b3583ae..4c36198 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -256,7 +256,7 @@ def test_entities_fix_keys_and_values_can_be_retrieved(self): e = create_event(dt.datetime.now(), dt.datetime.now() + dt.timedelta(days=1), "Patrick", other_attr="asd", other_attr2=123) keys = list(sorted(e.fixed_attributes().keys())) - self.assertListEqual(sorted(['author', 'products', 'start', 'stop', 'tags', 'uuid']), keys) + self.assertListEqual(sorted(['author', 'products', 'start', 'stop', 'tags', 'uuid', 'rating']), keys) keys = list(sorted(e.variable_attributes().keys())) self.assertListEqual(sorted(['other_attr', 'other_attr2']), keys) diff --git a/tests/test_event.py b/tests/test_event.py index d98c072..476b64d 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -53,7 +53,7 @@ def test_constructor_various_combinations_all_ok(self, start, stop, author, uuid tags = re.escape(str(tags)) products = re.escape(str(products)) r = r'^Event\(start=.*, stop=.*, author=' + author + r', uuid=[0-9a-f-]{36}, tags=' + tags \ - + r', products=' + products + r'\) attributes\(' + attr_repr + r'\)$' + + r', products=' + products + r', rating=None\) attributes\(' + attr_repr + r'\)$' self.assertRegex(f'{e}', r) @@ -102,3 +102,30 @@ def test_constructor_with_dynamic_attribute_manual_access(self): self.assertEqual(e.field_str, "string-test") self.assertEqual(e.field_bool, True) self.assertEqual(e.field_dt, dt_val) + + def test_event_rating(self): + t1, t2 = dt.datetime.now(), dt.datetime.now() + dt.timedelta(days=1) + + e = create_event(t1, t2, "Patrick") + self.assertEqual(e.rating, None) + + e.rating = 1 + self.assertEqual(e.rating, 1) + + e.rating = 5 + self.assertEqual(e.rating, 5) + + e.rating = None + self.assertEqual(e.rating, None) + + with self.assertRaises(ValueError): + e.rating = -1 + + with self.assertRaises(ValueError): + e.rating = 0 + + with self.assertRaises(ValueError): + e.rating = 1.5 + + with self.assertRaises(ValueError): + e.rating = 11 diff --git a/tscat/base.py b/tscat/base.py index 4941518..1eaf828 100644 --- a/tscat/base.py +++ b/tscat/base.py @@ -23,7 +23,7 @@ def backend() -> orm_sqlalchemy.Backend: def _listify(v) -> Union[List, Tuple]: - if type(v) in [list, tuple]: + if isinstance(v, (list, tuple)): return v else: return [v] @@ -144,13 +144,14 @@ def is_removed(self) -> bool: return self._removed class _Event(_BackendBasedEntity): - _fixed_keys = ['start', 'stop', 'author', 'uuid', 'tags', 'products'] + _fixed_keys = ['start', 'stop', 'author', 'uuid', 'tags', 'products', 'rating'] def __init__(self, start: dt.datetime, stop: dt.datetime, author: str, uuid: Optional[str] = None, tags: Iterable[str] = [], products: Iterable[str] = [], + rating: Optional[int] = None, _insert: bool = True, **kwargs): self._in_ctor = True @@ -161,6 +162,7 @@ def __init__(self, start: dt.datetime, stop: dt.datetime, self.author = author self.tags = list(tags) self.products = list(products) + self.rating = rating if not uuid: self.uuid = str(uuid4()) @@ -178,6 +180,7 @@ def __init__(self, start: dt.datetime, stop: dt.datetime, 'uuid': self.uuid, 'tags': self.tags, 'products': self.products, + 'rating': self.rating, 'attributes': kwargs, }) @@ -193,10 +196,16 @@ def __setattr__(self, key, value): if value < self.start: raise ValueError("stop date has to be after start date") elif key in ['tags', 'products']: - if any(type(v) != str for v in value): + if any(not isinstance(v, str) for v in value): raise ValueError("a tag has to be a string") if any(',' in v for v in value): raise ValueError("a string-list value shall not contain a comma") + elif key == 'rating': + if value is not None: + if not isinstance(value, int): + raise ValueError("rating has to be an integer value") + if value < 1 or value > 10: + raise ValueError("rating has to be between 1 and 10") super(_Event, self).__setattr__(key, value) @@ -253,7 +262,7 @@ def __setattr__(self, key, value): if not value: raise ValueError('Catalogue name cannot be emtpy.') elif key == 'tags': - if any(type(v) != str for v in value): + if any(not isinstance(v, str) for v in value): raise ValueError("a tag has to be a string") if any(',' in v for v in value): raise ValueError("a string-list value shall not contain a comma") @@ -351,7 +360,7 @@ def get_events(base: Union[Predicate, _Catalogue, None] = None, events = [] for ev in backend().get_events(base_dict): - e = _Event(ev['start'], ev['stop'], ev['author'], ev['uuid'], ev['tags'], ev['products'], + e = _Event(ev['start'], ev['stop'], ev['author'], ev['uuid'], ev['tags'], ev['products'], ev['rating'], **ev['attributes'], _insert=False) e._removed = removed_items e._backend_entity = ev['entity'] diff --git a/tscat/import_export.py b/tscat/import_export.py index 1f35e27..0467a1c 100644 --- a/tscat/import_export.py +++ b/tscat/import_export.py @@ -196,6 +196,9 @@ def __init__(self, attrs: Dict[str, str], tscat_name: Optional[str] = None) -> N __VOTableTSCatFieldSpecialDateTime({'name': "Start Time", 'ID': "TimeIntervalStart", 'ucd': "time.start"}, 'start'), __VOTableTSCatFieldSpecialDateTime({'name': "Stop Time", 'ID': "TimeIntervalStop", 'ucd': "time.end"}, 'stop'), __VOTableTSCatFieldSpecialDateTime({}), + __VOTableTSCatField(int, {'datatype': 'long'}, + lambda x: 0 if x is None else x, + lambda x: None if x == 0 else x, 'rating'), __VOTableTSCatField(int, {'datatype': 'long'}, int, int), __VOTableTSCatField(float, {'datatype': 'double'}, float, float), __VOTableTSCatField(bool, {'datatype': 'boolean'}, bool, bool), @@ -246,6 +249,7 @@ def export_votable(catalogues: Union[List[_Catalogue], _Catalogue]) -> VOTableFi ('uuid', __vo_table_field_from(str)), ('tags', __vo_table_field_from(list)), ('products', __vo_table_field_from(list)), + ('rating', __vo_table_field_from('rating')), ] events = get_events(catalogue) diff --git a/tscat/orm_sqlalchemy/__init__.py b/tscat/orm_sqlalchemy/__init__.py index 5fd193c..ef26aa4 100644 --- a/tscat/orm_sqlalchemy/__init__.py +++ b/tscat/orm_sqlalchemy/__init__.py @@ -173,6 +173,7 @@ def add_event(self, event: Dict) -> orm.Event: event['uuid'], event['tags'], event['products'], + event['rating'], event['attributes']) def add_events_to_catalogue(self, catalogue: orm.Catalogue, events: List[orm.Event]) -> None: @@ -248,6 +249,7 @@ def get_events(self, base: Dict = {}) -> List[Dict]: "uuid": e.uuid, "tags": e.tags, "products": e.products, + "rating": e.rating, "attributes": e.attributes, "entity": e} events.append(event) @@ -264,6 +266,7 @@ def get_events_by_uuid_list(self, uuids: List[str]) -> Dict[str, Dict]: "uuid": e.uuid, "tags": e.tags, "products": e.products, + "rating": e.rating, "attributes": e.attributes, "entity": e} diff --git a/tscat/orm_sqlalchemy/orm.py b/tscat/orm_sqlalchemy/orm.py index 7780cf6..5a76e46 100644 --- a/tscat/orm_sqlalchemy/orm.py +++ b/tscat/orm_sqlalchemy/orm.py @@ -34,18 +34,20 @@ class Event(Base): tags: List[str] = Column(ScalarListType(str), default=[], info={"type": (list, "string_list")}) products: List[str] = Column(ScalarListType(str), default=[], info={"type": (list, "string_list")}) + rating: int = Column(Integer, default=None, nullable=True) removed: bool = Column(Boolean, default=False, nullable=False) attributes: Dict[str, Any] = Column(MutableDict.as_mutable(JSON)) - def __init__(self, start, stop, author, uuid, tags, products, attributes): + def __init__(self, start, stop, author, uuid, tags, products, rating, attributes): self.start = start self.stop = stop self.author = author self.uuid = uuid self.tags = tags self.products = products + self.rating = rating self.attributes = attributes def __repr__(self): # pragma: no cover