Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple cache #106

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion toggl/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@ def save(self, config=None): # type: (utils.Config) -> None

self.validate()

if config.cache_requests:
utils.toggl.cache_clear()

if self.id is not None: # Update
utils.toggl('/{}/{}'.format(self.get_url(), self.id), 'put', self.json(update=True), config=config)
self.__change_dict__ = {} # Reset tracking changes
Expand All @@ -483,7 +486,12 @@ def delete(self, config=None): # type: (utils.Config) -> None
if not self.id:
raise exceptions.TogglException('This instance has not been saved yet!')

utils.toggl('/{}/{}'.format(self.get_url(), self.id), 'delete', config=config or self._config)
config = config or self._config

if config.cache_requests:
utils.toggl.cache_clear()

utils.toggl('/{}/{}'.format(self.get_url(), self.id), 'delete', config=config)
self.id = None # Invalidate the object, so when save() is called after delete a new object is created

def json(self, update=False): # type: (bool) -> str
Expand Down
8 changes: 8 additions & 0 deletions toggl/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def invite(self, *emails): # type: (str) -> None
raise exceptions.TogglValidationException('Supplied email \'{}\' is not valid email!'.format(email))

emails_json = json.dumps({'emails': emails})

if self._config.cache_requests:
utils.toggl.cache_clear()

data = utils.toggl("/workspaces/{}/invite".format(self.id), "post", emails_json, config=self._config)

if 'notifications' in data and data['notifications']:
Expand Down Expand Up @@ -333,6 +337,10 @@ def signup(cls, email, password, timezone=None, created_with='TogglCLI',
'timezone': timezone,
'created_with': created_with
}})

if config.cache_requests:
utils.toggl.cache_clear()

data = utils.toggl("/signups", "post", user_json, config=config)
return cls.deserialize(config=config, **data['data'])

Expand Down
12 changes: 12 additions & 0 deletions toggl/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ class Config(EnvConfigMixin, IniConfigMixin, metaclass=ConfigMeta):
"""
tz = None

"""
Size of the HTTP API cache. Ignored if cache is disabled.
"""
cache_size = 256

"""
Turns on/off the caching of HTTP API calls.
"""
cache_requests = True

ENV_MAPPING = {
'api_token': EnvEntry('TOGGL_API_TOKEN', str),
'user_name': EnvEntry('TOGGL_USERNAME', str),
Expand All @@ -282,6 +292,8 @@ class Config(EnvConfigMixin, IniConfigMixin, metaclass=ConfigMeta):
'time_format': IniEntry('options', str),
'default_wid': IniEntry('options', int),
'retries': IniEntry('options', int),
'cache_size': IniEntry('options', int),
'cache_requests': IniEntry('options', bool),
}

def __init__(self, config_path=sentinel, read_env=True, **kwargs): # type: (str, bool, **typing.Any) -> None
Expand Down
9 changes: 9 additions & 0 deletions toggl/utils/others.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import json
from functools import lru_cache
from pprint import pformat
from time import sleep

Expand Down Expand Up @@ -147,6 +148,9 @@ def _toggl_request(url, method, data, headers, auth):
def toggl(url, method, data=None, headers=None, config=None, address=None):
"""
Makes an HTTP request to toggl.com. Returns the parsed JSON as dict.
Results are cached in an LRU-cache unless disabled through the configuration.
Cache can be cleared by calling `api.others.toggl.cache_clear()`.
The cache will be cleared automatically on any 'put', 'post' or 'delete'.
"""
from ..toggl import TOGGL_URL

Expand Down Expand Up @@ -175,3 +179,8 @@ def toggl(url, method, data=None, headers=None, config=None, address=None):

# If retries failed then 'e' contains the last Exception/Error, lets re-raise it!
raise exception


if Config.factory().cache_requests:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I am afraid we can't do this. Config.factory() will always give you the default ~/.togglrc configuration, which might not be desired for people who build their own Config instance that they pass to the API Wrappers.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we could wrap instead of toggl() the _toggl_request() function and then in toggl() decide based on the passed config if used the cached version or instead non-cached version using the ability to bypass the cache using _toggl_request. __wrapped__() attribute that lru_cache expose. This then can be also used to ignore cache for POST/DELETE/PUT requests.

But still, it leaves us with the problem of caching error response...

# Manual conditional wrapping of the toggl function
toggl = lru_cache(maxsize=Config.factory().cache_size)(toggl)