Skip to content

Commit

Permalink
Rename SwaggerClientFactory to SwaggerClientCache, to be more inline …
Browse files Browse the repository at this point in the history
…with it's function.
  • Loading branch information
dnephin committed Dec 28, 2014
1 parent 5f846fc commit 46a68a4
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 84 deletions.
72 changes: 26 additions & 46 deletions swaggerpy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,75 +28,55 @@


class CacheEntry(object):
"""A wrapper to client which stores the last updated timestamp and the
ttl in seconds when the client expires
"""An entry in the cache. Each item has it's own ttl.
:param swagger_client: Core SwaggerClient instance
:type swagger_client: :class:`SwaggerClient`
:param item: the item to cache
:param ttl: time-to-live in seconds after which the client expires
:type ttl: int
"""

def __init__(self, swagger_client, ttl, timestamp=None):
self.swagger_client = swagger_client
def __init__(self, item, ttl, timestamp=None):
self.item = item
self.ttl = ttl
self.timestamp = timestamp or time.time()

def is_stale(self, timestamp=None):
"""Checks if the instance has become stale
:return: true/false whether client is now stale
:return: True if the cache item is stale, False otherwise
"""
current_time = timestamp or time.time()
return self.timestamp + self.ttl < current_time


class SwaggerClientFactory(object):
"""Factory to store swagger clients and refetch the api-docs if the client
class SwaggerClientCache(object):
"""Cache to store swagger clients and refetch the api-docs if the client
becomes stale
"""

def __init__(self):
self.cache = dict()

# TODO: cache resource listing instead of client
# TODO: cache needs to use all of args/kwargs
def __call__(self, api_docs_url, *args, **kwargs):
"""
:param api_docs_url: url for swagger api docs used to build the client
:type api_docs_url: str
:param timeout: (optional) Timeout after which api-docs is stale
:return: :class:`CacheEntry`
"""
# Construct cache key out of api_docs_url
if isinstance(api_docs_url, (str, unicode)):
key = api_docs_url
else:
key = json.dumps(api_docs_url)

if (key not in self.cache or self.cache[key].is_stale()):
self.cache[key] = self.build_cached_client(
api_docs_url, *args, **kwargs
)
def __contains__(self, key):
return key in self.cache and not self.cache[key].is_stale()

return self.cache[key]

def build_cached_client(self, api_docs_url, *args, **kwargs):
"""Builds a fresh SwaggerClient and stores it in a namedtuple which
contains its created timestamp and timeout in seconds
"""
def __call__(self, *args, **kwargs):
# timeout is backwards compatible with 0.7
ttl = kwargs.pop('ttl', kwargs.pop('timeout', SWAGGER_SPEC_CACHE_TTL))
key = repr(args) + repr(kwargs)

if isinstance(api_docs_url, (str, unicode)):
client = SwaggerClient.from_url(api_docs_url, *args, **kwargs)
else:
client = SwaggerClient.from_resource_listing(
api_docs_url, *args, **kwargs)
if key not in self:
self.cache[key] = CacheEntry(
self.build_client(*args, **kwargs), ttl)

return self.cache[key].item

return CacheEntry(client, ttl)
def build_client(self, api_docs, *args, **kwargs):
if isinstance(api_docs, basestring):
return SwaggerClient.from_url(api_docs, *args, **kwargs)
return SwaggerClient.from_resource_listing(api_docs, *args, **kwargs)


factory = None
cache = None


def get_client(*args, **kwargs):
Expand All @@ -105,7 +85,7 @@ def get_client(*args, **kwargs):
.. note::
This factory method uses a global which maintains the state of swagger
client. Use :class:`SwaggerClientFactory` if you want more control.
client. Use :class:`SwaggerClientCache` if you want more control.
To change the freshness timeout, simply pass an argument: ttl=<seconds>
Expand All @@ -122,12 +102,12 @@ def get_client(*args, **kwargs):
:param ttl: (optional) Timeout in secs. after which api-docs is stale
:return: :class:`SwaggerClient`
"""
global factory
global cache

if factory is None:
factory = SwaggerClientFactory()
if cache is None:
cache = SwaggerClientCache()

return factory(*args, **kwargs).swagger_client
return cache(*args, **kwargs)


class Operation(object):
Expand Down
68 changes: 30 additions & 38 deletions tests/client_test.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# Copyright (c) 2013, Digium, Inc.
#

"""Swagger client tests.
"""

import datetime
from swaggerpy.compat import json
import unittest

import httpretty
Expand All @@ -19,7 +9,7 @@
from swaggerpy import client
from swaggerpy.client import (
SwaggerClient,
SwaggerClientFactory,
SwaggerClientCache,
validate_and_add_params_to_request,
)

Expand All @@ -45,33 +35,35 @@ def test_unrequired_param_not_added_to_request_when_none(self):
mock_add_param.assert_called_once_with(param, False, mock_request)


class SwaggerClientFactoryTest(unittest.TestCase):
class SwaggerClientCacheTest(unittest.TestCase):
"""Test the proxy wrapper of SwaggerClient
"""

def setUp(self):
client.factory = None
client.cache = None

tearDown = setUp

def test_is_stale_returns_true_after_ttl(self):
with patch('swaggerpy.client.SwaggerClient'):
with patch('swaggerpy.client.time.time', side_effect=[1]):
client.get_client('test', ttl=10)
self.assertTrue(client.factory.cache['test'].is_stale(12))
self.assertTrue(client.cache.cache["('test',){}"].is_stale(12))

def test_is_stale_returns_false_before_ttl(self):
with patch('swaggerpy.client.SwaggerClient'):
with patch('swaggerpy.client.time.time', side_effect=[1]):
client.get_client('test', ttl=10)
self.assertFalse(client.factory.cache['test'].is_stale(11))
self.assertFalse(client.cache.cache["('test',){}"].is_stale(11))

def test_build_cached_client_with_proper_values(self):
def test_build_cached_item_with_proper_values(self):
with patch('swaggerpy.client.SwaggerClient.from_url') as mock:
mock.return_value = 'foo'
with patch('swaggerpy.client.time.time',
side_effect=[1, 1]):
client_object = SwaggerClientFactory().build_cached_client(
'test', ttl=3)
self.assertEqual('foo', client_object.swagger_client)
cache = SwaggerClientCache()
client_object = client.CacheEntry(cache.build_client('test'), 3)
self.assertEqual('foo', client_object.item)
self.assertEqual(3, client_object.ttl)
self.assertEqual(1, client_object.timestamp)

Expand All @@ -83,15 +75,15 @@ def test_builds_client_if_not_present_in_cache(self):

def test_builds_client_if_present_in_cache_but_stale(self):
with patch('swaggerpy.client.time.time', side_effect=[2, 3]):
client.factory = client.SwaggerClientFactory()
client.factory.cache['foo'] = client.CacheEntry('bar', 0, 1)
client.cache = client.SwaggerClientCache()
client.cache.cache['foo'] = client.CacheEntry('bar', 0, 1)
with patch('swaggerpy.client.SwaggerClient.from_url') as mock:
client.get_client('foo')
mock.assert_called_once_with('foo')

def test_uses_the_cache_if_present_and_fresh(self):
client.factory = client.SwaggerClientFactory()
client.factory.cache['foo'] = client.CacheEntry('bar', 2, 1)
client.cache = client.SwaggerClientCache()
client.cache.cache['foo'] = client.CacheEntry('bar', 2, 1)
with patch('swaggerpy.client.SwaggerClient') as mock:
with patch('swaggerpy.client.time.time', side_effect=[2]):
client.get_client('foo')
Expand All @@ -101,24 +93,32 @@ def test_uses_the_cache_if_present_and_fresh(self):
class GetClientMethodTest(unittest.TestCase):

def setUp(self):
client.factory = None
client.cache = None

tearDown = setUp

def test_get_client_gets_atleast_one_param(self):
self.assertRaises(TypeError, client.get_client)

def test_get_client_instantiates_new_factory_if_not_set(self):
with patch.object(SwaggerClientFactory, '__call__') as mock_method:
with patch.object(SwaggerClientCache, '__call__') as mock_method:
mock_method.client.return_value = None
client.get_client()
self.assertTrue(client.factory is not None)
self.assertTrue(client.cache is not None)

def test_get_client_uses_instantiated_factory_second_time(self):
with patch.object(SwaggerClientFactory, '__call__') as mock_method:
with patch.object(SwaggerClientCache, '__call__') as mock_method:
mock_method.client.return_value = None
client.factory = SwaggerClientFactory()
prev_factory = client.factory
client.cache = SwaggerClientCache()
prev_factory = client.cache
client.get_client()
self.assertTrue(prev_factory is client.factory)
self.assertTrue(prev_factory is client.cache)

def test_cache_of_a_json_dict(self):
client.get_client({'swaggerVersion': '1.2', 'apis': []})
self.assertTrue(
repr(({'swaggerVersion': '1.2', 'apis': []},)) + "{}" in
client.cache.cache)


class ClientTest(unittest.TestCase):
Expand All @@ -127,14 +127,6 @@ def test_get_client_allows_json_dict(self):
client_stub = client.get_client(self.resource_listing)
self.assertTrue(isinstance(client_stub, client.SwaggerClient))

# TODO: what is this testing? clear() is necessary because other tests
# can run first and pollute the cache. should fix
def test_serialization_of_json_dict(self):
client.factory.cache.clear()
client.get_client({'swaggerVersion': '1.2', 'apis': []})
self.assertTrue({'swaggerVersion': '1.2', 'apis': []} in
map(json.loads, client.factory.cache.keys()))

@httpretty.activate
def test_bad_operation(self):
try:
Expand Down

0 comments on commit 46a68a4

Please sign in to comment.