diff --git a/docs/_static/jquery-3.6.0.js b/docs/_static/jquery-3.6.0.js index fc6c299..382e622 100644 --- a/docs/_static/jquery-3.6.0.js +++ b/docs/_static/jquery-3.6.0.js @@ -4395,7 +4395,7 @@ var dataUser = new Data(); // 2. Improve the module's maintainability by reducing the storage // paths to a single mechanism. // 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 4. _Never_ expose "private" data to user code (TODO: Drop _internal_data_dict, _removeData) // 5. Avoid exposing implementation details on user objects (eg. expando properties) // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 @@ -4463,7 +4463,7 @@ jQuery.extend( { dataUser.remove( elem, name ); }, - // TODO: Now that all calls to _data and _removeData have been replaced + // TODO: Now that all calls to _internal_data_dict and _removeData have been replaced // with direct calls to dataPriv methods, these can be deprecated. _data: function( elem, name, data ) { return dataPriv.access( elem, name, data ); diff --git a/media/pyteamsnap_logo.svg b/media/pyteamsnap_logo.svg index fc63ec4..dc20efd 100644 --- a/media/pyteamsnap_logo.svg +++ b/media/pyteamsnap_logo.svg @@ -1,6 +1,6 @@ - + @@ -18,10 +18,13 @@ - - + + + + + - + diff --git a/pyteamsnap/models/base.py b/pyteamsnap/models/base.py index f8b549d..182276f 100644 --- a/pyteamsnap/models/base.py +++ b/pyteamsnap/models/base.py @@ -4,6 +4,7 @@ from apiclient import ( APIClient, ) import typing as T +import copy # Import "preview" of Self typing # https://stackoverflow.com/a/70932112 @@ -23,25 +24,31 @@ class BaseApiObject: :param data: Data to instantiate instance, defaults to empty dict. """ self.client = client - self._data = data + for k, v in data.items(): + if k == 'type': continue + if k == 'id': continue + setattr(self, k, v) + self._internal_data_dict = copy.deepcopy(data) self.rel = self.__class__.rel """rel: Relationship between a linked resource and the current document""" def __repr__(self): - return f'TeamSnap<{self.__class__.__name__}:{self.id}> "{self.__str__()}"' + return f'TeamSnap<{self.__class__.__name__}:{self.id}> "{self.data.get("name")}"' def __getitem__(self, key): - return self._data.__getitem__(key) + return self._internal_data_dict.__getitem__(key) + # return getattr(self, key) def __setitem__(self, key, newvalue): - return self._data.__setitem__(key, newvalue) + return self._internal_data_dict.__setitem__(key, newvalue) + # return setattr(self, key, newvalue) def __iter__(self): - return iter(self._data.items()) + return iter(self._internal_data_dict.items()) @property def id(self) -> int: - return self._data["id"] + return self._internal_data_dict["id"] @property def data(self) -> T.Dict[str, T.Union[str, list]]: @@ -49,7 +56,7 @@ class BaseApiObject: :return: dict: dict with keys: """ - return self._data + return self._internal_data_dict @classmethod def search(cls, client: APIClient, **kwargs): @@ -62,11 +69,11 @@ class BaseApiObject: @classmethod def get(cls, client: APIClient, id: T.Union[int, str]) -> Self: r = client.get(f"{client.link(cls.rel)}/{id}") - return cls(client, cls.rel, client.parse_response(r)[0]) + return cls(client, client.parse_response(r)[0]) @classmethod def new(cls, client: Self) -> Self: - return cls(client, cls.rel) + return cls(client) def post(self) -> Self: data = { @@ -75,7 +82,7 @@ class BaseApiObject: } } r = self.client.post_item(self.rel, data=data) - self._data = r + self._internal_data_dict = r return self def put(self) -> Self: @@ -86,7 +93,7 @@ class BaseApiObject: } id = self.data.get("id") r = self.client.put_item(self.rel, id=id, data=data) - self._data = r + self._internal_data_dict = r return self def delete(self): diff --git a/pyteamsnap/models/event.py b/pyteamsnap/models/event.py index f3cfcc3..76e0c07 100644 --- a/pyteamsnap/models/event.py +++ b/pyteamsnap/models/event.py @@ -11,6 +11,51 @@ class Event(BaseApiObject): type = "event" version = "3.866.0" + __slots__ = ( + # "type", + "additional_location_details", + "browser_time_zone", + "division_location_id", + "doesnt_count_towards_record", + "duration_in_minutes", + "game_type_code", + "icon_color", + "is_canceled", + "is_game", + "is_overtime", + "is_shootout", + "is_tbd", + "label", + "location_id", + "minutes_to_arrive_early", + "name", + "notes", + "notify_opponent", + "notify_opponent_contacts_email", + "notify_opponent_contacts_name", + "notify_opponent_notes", + "notify_team", + "notify_team_as_member_id", + "opponent_id", + "points_for_opponent", + "points_for_team", + "repeating_include", + "repeating_type_code", + "repeating_until", + "results", + "results_url", + "shootout_points_for_opponent", + "shootout_points_for_team", + "start_date", + "team_id", + "time_zone", + "tracks_availability", + "uniform", + ) + + def __str__(self): + return f'{self["formatted_title"]}' + @property def data(self): """Data dictionary for object diff --git a/pyteamsnap/models/eventlineupentry.py b/pyteamsnap/models/eventlineupentry.py index 4feb12f..c151507 100644 --- a/pyteamsnap/models/eventlineupentry.py +++ b/pyteamsnap/models/eventlineupentry.py @@ -23,5 +23,5 @@ class EventLineupEntry(BaseApiObject): # this is a workaround r = client.get(f"{client.link(cls.rel)}/search", params=kwargs) results = client.parse_response(r) - [cls(client, rel=cls.rel, data=r) for r in results] - return [cls(client, rel=cls.rel, data=r) for r in results] + [cls(client, data=r) for r in results] + return [cls(client, data=r) for r in results] diff --git a/requirements_dev.txt b/requirements_dev.txt index c77eee6..df9053f 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,3 +5,4 @@ black==22.10 vcrpy==4.2 jinja2==3.1 python-dotenv==0.21 +bump==1.32 diff --git a/tests/client.py b/tests/client.py new file mode 100644 index 0000000..7a344bb --- /dev/null +++ b/tests/client.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +"""Tests for `pyteamsnap` package.""" + + +import unittest +from unittest import TestCase + +from pyteamsnap import client +from os import getenv +from pyteamsnap.models.base import BaseApiObject +import vcr +TEAMSNAP_TOKEN = getenv('TEAMSNAP_TOKEN') +TEAMSNAP_TEAM = getenv('TEAMSNAP_TEAM') +TEAMSNAP_EVENT = getenv('TEAMSNAP_EVENT') + +vcr_options = { + 'decode_compressed_response': True, + 'cassette_library_dir':'tests/fixtures/cassettes', + 'filter_headers':['authorization'] +} + +class ClientTestCase(TestCase): + """Tests for `pyteamsnap` package.""" + + @classmethod + def setUpClass(cls) -> None: + """Set up test fixtures, if any.""" + cls.TEAMSNAP_TOKEN = getenv('TEAMSNAP_TOKEN') + cls.TEAMSNAP_TEAM = int(getenv('TEAMSNAP_TEAM')) + cls.TEAMSNAP_EVENT = getenv('TEAMSNAP_EVENT') + with vcr.use_cassette("client.yml", **vcr_options): + cls.client = client.TeamSnap(token=TEAMSNAP_TOKEN) + cls.cassette = vcr.use_cassette(f"{cls.__name__}.yml", **vcr_options) + super().setUpClass() + + + def test_bulkload(self): + from pyteamsnap.models import Event, EventLineup, EventLineupEntry, AvailabilitySummary, Member + with self.cassette: + bulk_load = self.client.bulk_load( + team_id=self.TEAMSNAP_TEAM, + event__id=self.TEAMSNAP_EVENT, + types=[ + Event, + EventLineup, + EventLineupEntry, + AvailabilitySummary, + Member + ]) + + self.assertIsInstance(bulk_load, list) + return bulk_load + +