"""
These are planet data providers.
ATM there is only 60 planets in the pool so this is not a problem to keep
them in memory. When circumstances change, and the pool of planets would
increase significantly, we can implement a more efficient data provider that
would build index an index only once store it only update missing planets
when they are requested.
"""
import operator
from abc import ABC, abstractmethod
from dataclasses import dataclass
from threading import RLock
import backoff
import cachetools
import petl as etl
import requests
from cached_property import cached_property
from swdata.adapters import results_generator
from swdata.settings import SWAPI_URL
[docs]class PlanetProvider(ABC):
[docs] @abstractmethod
def get_name(self, url: str):
...
[docs]class LazyPlanetProvider(PlanetProvider):
"""
Lazy and slow planet provider usable only when the number queries planets
is small in comparison to the total number of available planets.
"""
CACHE_TIME = 60 * 60
CACHED_PLANETS = 100
def __init__(self) -> None:
self._cache = cachetools.TTLCache(
maxsize=self.CACHED_PLANETS,
ttl=self.CACHE_TIME,
)
self._rlock = RLock()
[docs] @cachetools.cachedmethod(
cache=operator.attrgetter('_cache'),
lock=operator.attrgetter('_rlock'),
)
def get_name(self, url):
response = self._get(url)
name = response.json()['name']
return name
@backoff.on_exception(backoff.expo, Exception, max_tries=3)
def _get(self, url):
response = requests.get(url)
response.raise_for_status()
return response
[docs]class EagerPlanetProvider(PlanetProvider):
"""
Eager provider loading all planets into memory upfront.
"""
[docs] def get_name(self, url):
return self.planets[url]
@cached_property
def planets(self):
data = results_generator(f'{SWAPI_URL}planets/')
return etl.fromdicts(data, header=('url', 'name')).skip(1).dict()