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

[ImgBot] Optimize images #27

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ script:

after_success:
- coveralls

branches:
only:
- master
Binary file modified docs/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/client_server.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/client_server_tg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/code_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/headers_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/sequence.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/uris_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ def app(tmpdir):
return application.TimeGate(config=dict(
HOST='http://localhost',
BASE_URI='http://www.example.com/',
CACHE_USE=True,
CACHE_FILE=tmpdir.mkdir('cache').strpath,
CACHE_BACKEND='werkzeug.contrib.cache:FileSystemCache',
CACHE_OPTIONS={
'cache_dir': tmpdir.mkdir('cache').strpath,
},
))


Expand Down
41 changes: 39 additions & 2 deletions tests/test_timegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def test_version():

def test_initialization():
"""Test TimeGate initialization."""
from timegate.application import TimeGate
from timegate.application import TimeGate, request
from timegate.examples.simple import ExampleHandler
handler = ExampleHandler()
app = TimeGate(config=dict(HANDLER_MODULE=handler))
assert handler == app.handler
assert len(list(app.url_map.iter_rules())) == 2


def test_application():
Expand All @@ -42,6 +42,43 @@ def test_application():
assert client.get('/').status_code == 404


def test_multi_handler():
"""Test simple request."""
from timegate.application import TimeGate, request
from timegate.examples.simple import ExampleHandler
from werkzeug.test import Client
from werkzeug.wrappers import BaseResponse

base1_uri = 'http://www.example.com/base1/'
base2_uri = 'http://www.example.com/base2/'
app = TimeGate(config=dict(
CACHE_BACKEND='werkzeug.contrib.cache.NullCache',
HANDLER_MODULE=ExampleHandler(base_uri=base1_uri),
BASE_URI=base1_uri,
HANDLERS={
'base2': dict(
HANDLER_MODULE=ExampleHandler(base_uri=base2_uri),
BASE_URI=base2_uri,
)
},
))
client = Client(app, BaseResponse)

assert len(list(app.url_map.iter_rules())) == 4

parameters = [
('', base1_uri), (base1_uri, base1_uri), (base2_uri, base2_uri)
]
for request_base, response_base in parameters:
response = client.get(
'/timegate/{0}resourceA'.format(request_base)
)
assert response.status_code == 302
assert response.headers['Location'] == (
'{0}resourceA_v3'.format(response_base)
)


def test_timemap_response(client):
"""Test timemap responses."""
response = client.get(
Expand Down
119 changes: 76 additions & 43 deletions timegate/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
import json
import logging
import os
import re
from datetime import datetime

from pkg_resources import iter_entry_points

from dateutil.tz import tzutc
from link_header import Link, LinkHeader
from pkg_resources import iter_entry_points
from werkzeug.datastructures import CombinedMultiDict
from werkzeug.exceptions import HTTPException, abort
from werkzeug.http import http_date, parse_date
from werkzeug.local import Local, LocalManager
Expand All @@ -43,8 +46,7 @@
request = local('request')
"""Proxy to request object."""

# logging.getLogger(__name__)
# logging.basicConfig(level=logging.DEBUG)
_RE_HANDLER = re.compile('^((?P<handler_name>[^.]+)\.)?(?P<endpoint>[^.]+)$')


def url_for(*args, **kwargs):
Expand Down Expand Up @@ -74,13 +76,17 @@ def load_handler(name_or_path):
class URIConverter(BaseConverter):
"""URI Converter."""

def __init__(self, url_map, base_uri=None):
def __init__(self, url_map, base_uri=None, default=True):
super(URIConverter, self).__init__(url_map)
assert base_uri or default, 'base_uri or default must be defined'
self.base_uri = base_uri
self.regex = (
r"([^:/?#]+:)?(//[^/?#]*)?"
r"[^?#]*(\?[^#]*)?(#.*)?"
r'([^:/?#]+:)?(//[^/?#]*)?'
r'[^?#]*(\?[^#]*)?(#.*)?'
) if default else (
r'({0})(.*)'.format(base_uri)
)
self.weigth = 100 if default else 400

def to_python(self, value):
"""Return value with base URI prefix."""
Expand All @@ -102,21 +108,55 @@ class TimeGate(object):

def __init__(self, config=None, cache=None):
"""Initialize application with handler."""
self.handlers = {} # registry of handlers
self.rules = [] # list of URL rules
self.config = Config(None)
self.config.from_object(constants)
self.config.update(config or {})
self.cache = None
if cache:
self.cache = cache
elif self.config['CACHE_USE']:
else:
self._build_default_cache()

@cached_property
def handler(self):
handler = load_handler(self.config['HANDLER_MODULE'])
def url_map(self):
"""Build URL map."""
for handler_name, config in self.config.get('HANDLERS', {}).items():
if handler_name is None:
continue # we have already regitered default handler
self.register_handler(
handler_name, CombinedMultiDict([config, self.config])
)
# Default handler at the end in case the weights are same.
self.register_handler(None, CombinedMultiDict([
self.config.get('HANDLERS', {}).get(None, {}), self.config
]))
return Map(self.rules, converters={'uri': URIConverter})

def _build_default_cache(self):
"""Build default cache object."""
self.cache = Cache(
self.config.get('CACHE_BACKEND',
'werkzeug.contrib.cache.NullCache'),
cache_refresh_time=self.config.get('CACHE_REFRESH_TIME', 86400),
**self.config.get('CACHE_OPTIONS', {})
)

def __repr__(self):
"""Representation of this class."""
return '<{0} {1}>'.format(
self.__class__.__name__, ', '.join([
h.__class__.__name__ for h in self.handlers.items()
])
)

def register_handler(self, handler_name, config):
"""Register handler."""
handler = load_handler(config['HANDLER_MODULE'])
HAS_TIMEGATE = hasattr(handler, 'get_memento')
HAS_TIMEMAP = hasattr(handler, 'get_all_mementos')
if self.config['USE_TIMEMAPS'] and (not HAS_TIMEMAP):
if config['USE_TIMEMAPS'] and (not HAS_TIMEMAP):
logging.error(
"Handler has no get_all_mementos() function "
"but is suppose to serve timemaps.")
Expand All @@ -125,48 +165,42 @@ def handler(self):
raise NotImplementedError(
"NotImplementedError: Handler has neither `get_memento` "
"nor `get_all_mementos` method.")
return handler

@cached_property
def url_map(self):
"""Build URL map."""
base_uri = self.config['BASE_URI']
rules = [
Rule('/timegate/<uri(base_uri="{0}"):uri_r>'.format(base_uri),
endpoint='timegate', methods=['GET', 'HEAD']),
Rule('/timemap/<any(json, link):response_type>/'
'<uri(base_uri="{0}"):uri_r>'.format(base_uri),
endpoint='timemap', methods=['GET', 'HEAD']),
]
return Map(rules, converters={'uri': URIConverter})

def _build_default_cache(self):
"""Build default cache object."""
self.cache = Cache(
self.config['CACHE_FILE'],
self.config['CACHE_TOLERANCE'],
self.config['CACHE_EXP'],
self.config['CACHE_MAX_VALUES'],
handler.use_timemaps = (
hasattr(handler, 'get_all_mementos') and config['USE_TIMEMAPS']
)
handler.resource_type = config['RESOURCE_TYPE']

def __repr__(self):
"""Representation of this class."""
return '<{0} {1}>'.format(
self.__class__.__name__, self.handler.__class__.__name__
endpoint_prefix = '{0}.'.format(handler_name) if handler_name else ''
uri_r = '<uri(base_uri="{0}", default={1}):uri_r>'.format(
config['BASE_URI'], str(handler_name is None)
)

self.rules.extend([
Rule('/timegate/{0}'.format(uri_r),
endpoint=endpoint_prefix + 'timegate',
methods=['GET', 'HEAD']),
Rule('/timemap/<any(json, link):response_type>/{0}'.format(uri_r),
endpoint=endpoint_prefix + 'timemap',
methods=['GET', 'HEAD']),
])

self.handlers[handler_name] = handler

def dispatch_request(self, request):
"""Choose correct method."""
request.adapter = adapter = self.url_map.bind_to_environ(
request.environ
)
try:
endpoint, values = adapter.match()
return getattr(self, endpoint)(**values)
parts = _RE_HANDLER.match(endpoint).groupdict()
request.handler = self.handlers[parts['handler_name']]
return getattr(self, parts['endpoint'])(**values)
except HTTPException as e:
return e
finally:
self.adapter = None
request.adapter = request.handler = None

def wsgi_app(self, environ, start_response):
local.request = request = Request(environ)
Expand All @@ -186,7 +220,7 @@ def get_memento(self, uri_r, accept_datetime):
:param accept_datetime: Datetime object with requested time.
:return: The TimeMap if it exists and is valid.
"""
return parsed_request(self.handler.get_memento,
return parsed_request(request.handler.get_memento,
uri_r, accept_datetime)

def get_all_mementos(self, uri_r):
Expand All @@ -201,7 +235,7 @@ def get_all_mementos(self, uri_r):
if self.cache and request.cache_control != 'no-cache':
mementos = self.cache.get_all(uri_r)
if mementos is None:
mementos = parsed_request(self.handler.get_all_mementos, uri_r)
mementos = parsed_request(request.handler.get_all_mementos, uri_r)
if self.cache:
self.cache.set(uri_r, mementos)
return mementos
Expand All @@ -224,16 +258,15 @@ def timegate(self, uri_r):

# Runs the handler's API request for the Memento
mementos = first = last = None
HAS_TIMEMAP = hasattr(self.handler, 'get_all_mementos')
if HAS_TIMEMAP and self.config['USE_TIMEMAPS']:
if request.handler.use_timemaps:
logging.debug('Using multiple-request mode.')
mementos = self.get_all_mementos(uri_r)

if mementos:
first = mementos[0]
last = mementos[-1]
memento = best(mementos, accept_datetime,
self.config['RESOURCE_TYPE'])
request.handler.resource_type)
else:
logging.debug('Using single-request mode.')
memento = self.get_memento(uri_r, accept_datetime)
Expand All @@ -244,7 +277,7 @@ def timegate(self, uri_r):
uri_r,
first,
last,
has_timemap=HAS_TIMEMAP and self.config['USE_TIMEMAPS'],
has_timemap=request.handler.use_timemaps,
)

def timemap(self, uri_r, response_type='link'):
Expand All @@ -258,7 +291,7 @@ def timemap(self, uri_r, response_type='link'):
:param start_response: WSGI callback function.
:return: The body of the HTTP response.
"""
if not self.config['USE_TIMEMAPS']:
if not request.handler.use_timemaps:
abort(403)

mementos = self.get_all_mementos(uri_r)
Expand Down
Loading