Skip to content

Commit

Permalink
Merge branch 'release/0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Nov 6, 2015
2 parents 2f3e5e6 + 8653a07 commit 4ba84a6
Show file tree
Hide file tree
Showing 48 changed files with 4,531 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ nosetests.xml
.DS_Store
.idea/*

test.py
test_*.py
/test.py
/test_*.py
config.py

Makefile
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ python:
- "2.7"
- "3.4"
install:
- pip install -r requirements.txt
#- pip install -r requirements.txt
- pip install -r tests-requirements.txt
script: py.test

services:
- memcached
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@

### 0.1

(November 6th, 2015)

- Initial release
2 changes: 2 additions & 0 deletions cachy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-

from .cache_manager import CacheManager
from .repository import Repository
298 changes: 298 additions & 0 deletions cachy/cache_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
# -*- coding: utf-8 -*-

import threading
import types
from functools import wraps
from .contracts.factory import Factory
from .contracts.store import Store

from .stores import (
DictStore,
FileStore,
RedisStore,
MemcachedStore
)

from .repository import Repository
from .serializers import (
Serializer,
JsonSerializer,
MsgPackSerializer,
PickleSerializer
)


class CacheManager(Factory, threading.local):
"""
A CacheManager is a pool of cache stores.
"""

_serializers = {
'json': JsonSerializer(),
'msgpack': MsgPackSerializer(),
'pickle': PickleSerializer()
}

def __init__(self, config):
super(CacheManager, self).__init__()

self._config = config
self._stores = {}
self._custom_creators = {}
self._serializer = self._resolve_serializer(config.get('serializer', 'pickle'))

def store(self, name=None):
"""
Get a cache store instance by name.
:param name: The cache store name
:type name: str
:rtype: Repository
"""
if name is None:
name = self.get_default_driver()

self._stores[name] = self._get(name)

return self._stores[name]

def driver(self, name=None):
"""
Get a cache store instance by name.
:param name: The cache store name
:type name: str
:rtype: Repository
"""
return self.store(name)

def _get(self, name):
"""
Attempt to get the store from the local cache.
:param name: The store name
:type name: str
:rtype: Repository
"""
return self._stores.get(name, self._resolve(name))

def _resolve(self, name):
"""
Resolve the given store
:param name: The store to resolve
:type name: str
:rtype: Repository
"""
config = self._get_config(name)

if not config:
raise RuntimeError('Cache store [%s] is not defined.' % name)

if config['driver'] in self._custom_creators:
repository = self._call_custom_creator(config)
else:
repository = getattr(self, '_create_%s_driver' % config['driver'])(config)

if 'serializer' in config:
serializer = self._resolve_serializer(config['serializer'])
else:
serializer = self._serializer

repository.get_store().set_serializer(serializer)

return repository

def _call_custom_creator(self, config):
"""
Call a custom driver creator.
:param config: The driver configuration
:type config: dict
:rtype: Repository
"""
creator = self._custom_creators[config['driver']](config)

if isinstance(creator, Store):
creator = self.repository(creator)

if not isinstance(creator, Repository):
raise RuntimeError('Custom creator should return a Repository instance.')

return creator

def _create_dict_driver(self, config):
"""
Create an instance of the dict cache driver.
:param config: The driver configuration
:type config: dict
:rtype: Repository
"""
return self.repository(DictStore())

def _create_file_driver(self, config):
"""
Create an instance of the file cache driver.
:param config: The driver configuration
:type config: dict
:rtype: Repository
"""
return self.repository(FileStore(config['path']))

def _create_redis_driver(self, config):
"""
Create an instance of the redis cache driver.
:param config: The driver configuration
:type config: dict
:return: Repository
"""
return self.repository(RedisStore(**config))

def _create_memcached_driver(self, config):
"""
Create an instance of the redis cache driver.
:param config: The driver configuration
:type config: dict
:return: Repository
"""
return self.repository(MemcachedStore(**config))

def repository(self, store):
"""
Create a new cache repository with the given implementation.
:param store: The cache store implementation instance
:type store: Store
:rtype: Repository
"""
repository = Repository(store)

return repository

def _get_prefix(self, config):
"""
Get the cache prefix.
:param config: The configuration
:type config: dict
:rtype: str
"""
return config.get('prefix', '')

def _get_config(self, name):
"""
Get the cache connection configuration.
:param name: The cache name
:type name: str
:rtype: dict
"""
return self._config['stores'].get(name)

def get_default_driver(self):
"""
Get the default cache driver name.
:rtype: str
:raises: RuntimeError
"""
if 'default' in self._config:
return self._config['default']

if len(self._config['stores']) == 1:
return list(self._config['stores'].keys())[0]

raise RuntimeError('Missing "default" cache in configuration.')

def set_default_driver(self, name):
"""
Set the default cache driver name.
:param name: The default cache driver name
:type name: str
"""
self._config['default'] = name

def extend(self, driver, store):
"""
Register a custom driver creator.
:param driver: The driver
:type driver: name
:param store: The store class
:type store: Store or callable
:rtype: self
"""
self._custom_creators[driver] = store

return self

def _resolve_serializer(self, serializer):
"""
Resolve the given serializer.
:param serializer: The serializer to resolve
:type serializer: str or Serializer
:rtype: Serializer
"""
if isinstance(serializer, Serializer):
return serializer

if serializer in self._serializers:
return self._serializers[serializer]

raise RuntimeError('Unsupported serializer')

def register_serializer(self, name, serializer):
"""
Register a new serializer.
:param name: The name of the serializer
:type name: str
:param serializer: The serializer
:type serializer: Serializer
"""
self._serializers[name] = serializer

def __getattr__(self, item):
try:
return object.__getattribute__(self, item)
except AttributeError:
return getattr(self.store(), item)

def __call__(self, store=None, *args, **kwargs):
if isinstance(store, (types.FunctionType, types.MethodType)):
fn = store

if len(args) > 0:
store = args[0]
args = args[1:] if len(args) > 1 else []
else:
store = None

args = (fn,) + args

return self.store(store)(*args, **kwargs)
else:
return self.store(store)(*args, **kwargs)
2 changes: 2 additions & 0 deletions cachy/contracts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

18 changes: 18 additions & 0 deletions cachy/contracts/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-


class Factory(object):
"""
Represent a cahce factory.
"""

def store(self, name=None):
"""
Get a cache store instance by name.
:param name: The cache store name
:type name: str
:rtype: mixed
"""
raise NotImplementedError()

0 comments on commit 4ba84a6

Please sign in to comment.