diff --git a/bin/glance b/bin/glance index 7e070ae7e3..915a26b2bb 100755 --- a/bin/glance +++ b/bin/glance @@ -40,8 +40,8 @@ if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')): gettext.install('glance', unicode=1) -from glance import client as glance_client from glance import version +from glance import client as glance_client from glance.common import exception from glance.common import utils diff --git a/etc/glance-api.conf b/etc/glance-api.conf index 1caec78de5..78ca42150b 100644 --- a/etc/glance-api.conf +++ b/etc/glance-api.conf @@ -214,7 +214,7 @@ pipeline = versionnegotiation context apiv1app # pipeline = versionnegotiation authtoken auth-context cachemanage apiv1app [app:apiv1app] -paste.app_factory = glance.api.v1:app_factory +paste.app_factory = glance.api.v1.router:app_factory [filter:versionnegotiation] paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory diff --git a/glance/api/__init__.py b/glance/api/__init__.py index a95f34e9ee..d65c689a83 100644 --- a/glance/api/__init__.py +++ b/glance/api/__init__.py @@ -14,50 +14,3 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import logging -import webob.exc - -from glance import registry -from glance.common import exception - - -logger = logging.getLogger('glance.api') - - -class BaseController(object): - def get_image_meta_or_404(self, request, image_id): - """ - Grabs the image metadata for an image with a supplied - identifier or raises an HTTPNotFound (404) response - - :param request: The WSGI/Webob Request object - :param image_id: The opaque image identifier - - :raises HTTPNotFound if image does not exist - """ - context = request.context - try: - return registry.get_image_metadata(context, image_id) - except exception.NotFound: - msg = _("Image with identifier %s not found") % image_id - logger.debug(msg) - raise webob.exc.HTTPNotFound( - msg, request=request, content_type='text/plain') - except exception.NotAuthorized: - msg = _("Unauthorized image access") - logger.debug(msg) - raise webob.exc.HTTPForbidden(msg, request=request, - content_type='text/plain') - - def get_active_image_meta_or_404(self, request, image_id): - """ - Same as get_image_meta_or_404 except that it will raise a 404 if the - image isn't 'active'. - """ - image = self.get_image_meta_or_404(request, image_id) - if image['status'] != 'active': - msg = _("Image %s is not active") % image_id - logger.debug(msg) - raise webob.exc.HTTPNotFound( - msg, request=request, content_type='text/plain') - return image diff --git a/glance/api/cached_images.py b/glance/api/cached_images.py index f866701090..fc643a1bd3 100644 --- a/glance/api/cached_images.py +++ b/glance/api/cached_images.py @@ -25,14 +25,14 @@ import webob.exc from glance.common import exception from glance.common import wsgi -from glance import api +from glance.api.v1 import controller from glance import image_cache from glance import registry logger = logging.getLogger(__name__) -class Controller(api.BaseController): +class Controller(controller.BaseController): """ A controller for managing cached images. """ diff --git a/glance/api/v1/__init__.py b/glance/api/v1/__init__.py index 7d0b28635a..456c37d443 100644 --- a/glance/api/v1/__init__.py +++ b/glance/api/v1/__init__.py @@ -15,51 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. -import logging +SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format', + 'min_ram', 'min_disk', 'size_min', 'size_max', + 'is_public', 'changes-since'] -import routes - -from glance.api.v1 import images -from glance.api.v1 import members -from glance.common import wsgi - -logger = logging.getLogger('glance.api.v1') - - -class API(wsgi.Router): - - """WSGI router for Glance v1 API requests.""" - - def __init__(self, options): - self.options = options - mapper = routes.Mapper() - - images_resource = images.create_resource(options) - - mapper.resource("image", "images", controller=images_resource, - collection={'detail': 'GET'}) - mapper.connect("/", controller=images_resource, action="index") - mapper.connect("/images/{id}", controller=images_resource, - action="meta", conditions=dict(method=["HEAD"])) - - members_resource = members.create_resource(options) - - mapper.resource("member", "members", controller=members_resource, - parent_resource=dict(member_name='image', - collection_name='images')) - mapper.connect("/shared-images/{id}", - controller=members_resource, - action="index_shared_images") - mapper.connect("/images/{image_id}/members", - controller=members_resource, - action="update_all", - conditions=dict(method=["PUT"])) - - super(API, self).__init__(mapper) - - -def app_factory(global_conf, **local_conf): - """paste.deploy app factory for creating Glance API server apps""" - conf = global_conf.copy() - conf.update(local_conf) - return API(conf) +SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir') diff --git a/glance/api/v1/controller.py b/glance/api/v1/controller.py new file mode 100644 index 0000000000..2e2b3e900c --- /dev/null +++ b/glance/api/v1/controller.py @@ -0,0 +1,64 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +import webob.exc + +from glance import registry +from glance.common import exception + +logger = logging.getLogger(__name__) + + +class BaseController(object): + def get_image_meta_or_404(self, request, image_id): + """ + Grabs the image metadata for an image with a supplied + identifier or raises an HTTPNotFound (404) response + + :param request: The WSGI/Webob Request object + :param image_id: The opaque image identifier + + :raises HTTPNotFound if image does not exist + """ + context = request.context + try: + return registry.get_image_metadata(context, image_id) + except exception.NotFound: + msg = _("Image with identifier %s not found") % image_id + logger.debug(msg) + raise webob.exc.HTTPNotFound( + msg, request=request, content_type='text/plain') + except exception.NotAuthorized: + msg = _("Unauthorized image access") + logger.debug(msg) + raise webob.exc.HTTPForbidden(msg, request=request, + content_type='text/plain') + + def get_active_image_meta_or_404(self, request, image_id): + """ + Same as get_image_meta_or_404 except that it will raise a 404 if the + image isn't 'active'. + """ + image = self.get_image_meta_or_404(request, image_id) + if image['status'] != 'active': + msg = _("Image %s is not active") % image_id + logger.debug(msg) + raise webob.exc.HTTPNotFound( + msg, request=request, content_type='text/plain') + return image diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py index 8ff9488ef4..42ab7d9eb4 100644 --- a/glance/api/v1/images.py +++ b/glance/api/v1/images.py @@ -31,11 +31,13 @@ from webob.exc import (HTTPNotFound, HTTPNoContent, HTTPUnauthorized) -from glance import api +import glance.api.v1 +from glance.api.v1 import controller from glance import image_cache from glance.common import exception from glance.common import notifier from glance.common import wsgi +from glance.common import utils import glance.store import glance.store.filesystem import glance.store.http @@ -50,16 +52,12 @@ from glance.store import (get_from_backend, from glance import registry -logger = logging.getLogger('glance.api.v1.images') - -SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format', - 'min_ram', 'min_disk', 'size_min', 'size_max', - 'is_public', 'changes-since'] - -SUPPORTED_PARAMS = ('limit', 'marker', 'sort_key', 'sort_dir') +logger = logging.getLogger(__name__) +SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS +SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS -class Controller(api.BaseController): +class Controller(controller.BaseController): """ WSGI controller for images resource in Glance v1 API @@ -596,7 +594,7 @@ class ImageDeserializer(wsgi.JSONRequestDeserializer): def _deserialize(self, request): result = {} - result['image_meta'] = wsgi.get_image_meta_from_headers(request) + result['image_meta'] = utils.get_image_meta_from_headers(request) data = request.body_file if self.has_body(request) else None result['image_data'] = data return result @@ -630,7 +628,7 @@ class ImageSerializer(wsgi.JSONResponseSerializer): :param response: The Webob Response object :param image_meta: Mapping of image metadata """ - headers = wsgi.image_meta_to_http_headers(image_meta) + headers = utils.image_meta_to_http_headers(image_meta) for k, v in headers.items(): response.headers[k] = v diff --git a/glance/api/v1/router.py b/glance/api/v1/router.py new file mode 100644 index 0000000000..65130c4b4d --- /dev/null +++ b/glance/api/v1/router.py @@ -0,0 +1,65 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +import routes + +from glance.api.v1 import images +from glance.api.v1 import members +from glance.common import wsgi + +logger = logging.getLogger(__name__) + + +class API(wsgi.Router): + + """WSGI router for Glance v1 API requests.""" + + def __init__(self, options): + self.options = options + mapper = routes.Mapper() + + images_resource = images.create_resource(options) + + mapper.resource("image", "images", controller=images_resource, + collection={'detail': 'GET'}) + mapper.connect("/", controller=images_resource, action="index") + mapper.connect("/images/{id}", controller=images_resource, + action="meta", conditions=dict(method=["HEAD"])) + + members_resource = members.create_resource(options) + + mapper.resource("member", "members", controller=members_resource, + parent_resource=dict(member_name='image', + collection_name='images')) + mapper.connect("/shared-images/{id}", + controller=members_resource, + action="index_shared_images") + mapper.connect("/images/{image_id}/members", + controller=members_resource, + action="update_all", + conditions=dict(method=["PUT"])) + + super(API, self).__init__(mapper) + + +def app_factory(global_conf, **local_conf): + """paste.deploy app factory for creating Glance API server apps""" + conf = global_conf.copy() + conf.update(local_conf) + return API(conf) diff --git a/glance/client.py b/glance/client.py index 41e9c8b895..61a93e76a2 100644 --- a/glance/client.py +++ b/glance/client.py @@ -21,14 +21,17 @@ Client classes for callers of a Glance system import errno import json +import logging import os -from glance.api.v1 import images as v1_images +import glance.api.v1 from glance.common import client as base_client from glance.common import exception -from glance.common import wsgi +from glance.common import utils -#TODO(jaypipes) Allow a logger param for client classes +logger = logging.getLogger(__name__) +SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS +SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS class V1Client(base_client.BaseClient): @@ -49,7 +52,7 @@ class V1Client(base_client.BaseClient): :param sort_key: results will be ordered by this image attribute :param sort_dir: direction in which to to order results (asc, desc) """ - params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS) + params = self._extract_params(kwargs, SUPPORTED_PARAMS) res = self.do_request("GET", "/images", params=params) data = json.loads(res.read())['images'] return data @@ -65,7 +68,7 @@ class V1Client(base_client.BaseClient): :param sort_key: results will be ordered by this image attribute :param sort_dir: direction in which to to order results (asc, desc) """ - params = self._extract_params(kwargs, v1_images.SUPPORTED_PARAMS) + params = self._extract_params(kwargs, SUPPORTED_PARAMS) res = self.do_request("GET", "/images/detail", params=params) data = json.loads(res.read())['images'] return data @@ -82,7 +85,7 @@ class V1Client(base_client.BaseClient): """ res = self.do_request("GET", "/images/%s" % image_id) - image = wsgi.get_image_meta_from_headers(res) + image = utils.get_image_meta_from_headers(res) return image, base_client.ImageBodyIterator(res) def get_image_meta(self, image_id): @@ -93,7 +96,7 @@ class V1Client(base_client.BaseClient): """ res = self.do_request("HEAD", "/images/%s" % image_id) - image = wsgi.get_image_meta_from_headers(res) + image = utils.get_image_meta_from_headers(res) return image def _get_image_size(self, image_data): @@ -136,7 +139,7 @@ class V1Client(base_client.BaseClient): :retval The newly-stored image's metadata. """ - headers = wsgi.image_meta_to_http_headers(image_meta or {}) + headers = utils.image_meta_to_http_headers(image_meta or {}) if image_data: body = image_data @@ -159,7 +162,7 @@ class V1Client(base_client.BaseClient): if image_meta is None: image_meta = {} - headers = wsgi.image_meta_to_http_headers(image_meta) + headers = utils.image_meta_to_http_headers(image_meta) if image_data: body = image_data diff --git a/glance/common/client.py b/glance/common/client.py index bf172843fb..578f5b45a8 100644 --- a/glance/common/client.py +++ b/glance/common/client.py @@ -26,13 +26,11 @@ import os import urllib import urlparse -from eventlet.green import socket, ssl - -# See http://code.google.com/p/python-nose/issues/detail?id=373 -# The code below enables glance.client standalone to work with i18n _() blocks -import __builtin__ -if not hasattr(__builtin__, '_'): - setattr(__builtin__, '_', lambda x: x) +try: + from eventlet.green import socket, ssl +except ImportError: + import socket + import ssl from glance.common import auth from glance.common import exception diff --git a/glance/common/utils.py b/glance/common/utils.py index 65d2978c77..a2be1caebc 100644 --- a/glance/common/utils.py +++ b/glance/common/utils.py @@ -34,7 +34,7 @@ import uuid from glance.common import exception -logger = logging.getLogger('glance.utils') +logger = logging.getLogger(__name__) TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" @@ -54,6 +54,76 @@ def chunkiter(fp, chunk_size=65536): break +def image_meta_to_http_headers(image_meta): + """ + Returns a set of image metadata into a dict + of HTTP headers that can be fed to either a Webob + Request object or an httplib.HTTP(S)Connection object + + :param image_meta: Mapping of image metadata + """ + headers = {} + for k, v in image_meta.items(): + if v is None: + v = '' + if k == 'properties': + for pk, pv in v.items(): + if pv is None: + pv = '' + headers["x-image-meta-property-%s" + % pk.lower()] = unicode(pv) + else: + headers["x-image-meta-%s" % k.lower()] = unicode(v) + return headers + + +def get_image_meta_from_headers(response): + """ + Processes HTTP headers from a supplied response that + match the x-image-meta and x-image-meta-property and + returns a mapping of image metadata and properties + + :param response: Response to process + """ + result = {} + properties = {} + + if hasattr(response, 'getheaders'): # httplib.HTTPResponse + headers = response.getheaders() + else: # webob.Response + headers = response.headers.items() + + for key, value in headers: + key = str(key.lower()) + if key.startswith('x-image-meta-property-'): + field_name = key[len('x-image-meta-property-'):].replace('-', '_') + properties[field_name] = value or None + elif key.startswith('x-image-meta-'): + field_name = key[len('x-image-meta-'):].replace('-', '_') + result[field_name] = value or None + result['properties'] = properties + if 'size' in result: + result['size'] = int(result['size']) + if 'is_public' in result: + result['is_public'] = bool_from_header_value(result['is_public']) + if 'deleted' in result: + result['deleted'] = bool_from_header_value(result['deleted']) + return result + + +def bool_from_header_value(value): + """ + Returns True if value is a boolean True or the + string 'true', case-insensitive, False otherwise + """ + if isinstance(value, bool): + return value + elif isinstance(value, (basestring, unicode)): + if str(value).lower() == 'true': + return True + return False + + def bool_from_string(subject): """ Interpret a string as a boolean. diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py index ab1f375cd2..58767243a2 100644 --- a/glance/common/wsgi.py +++ b/glance/common/wsgi.py @@ -406,73 +406,3 @@ class Resource(object): pass return args - - -def image_meta_to_http_headers(image_meta): - """ - Returns a set of image metadata into a dict - of HTTP headers that can be fed to either a Webob - Request object or an httplib.HTTP(S)Connection object - - :param image_meta: Mapping of image metadata - """ - headers = {} - for k, v in image_meta.items(): - if v is None: - v = '' - if k == 'properties': - for pk, pv in v.items(): - if pv is None: - pv = '' - headers["x-image-meta-property-%s" - % pk.lower()] = unicode(pv) - else: - headers["x-image-meta-%s" % k.lower()] = unicode(v) - return headers - - -def get_image_meta_from_headers(response): - """ - Processes HTTP headers from a supplied response that - match the x-image-meta and x-image-meta-property and - returns a mapping of image metadata and properties - - :param response: Response to process - """ - result = {} - properties = {} - - if hasattr(response, 'getheaders'): # httplib.HTTPResponse - headers = response.getheaders() - else: # webob.Response - headers = response.headers.items() - - for key, value in headers: - key = str(key.lower()) - if key.startswith('x-image-meta-property-'): - field_name = key[len('x-image-meta-property-'):].replace('-', '_') - properties[field_name] = value or None - elif key.startswith('x-image-meta-'): - field_name = key[len('x-image-meta-'):].replace('-', '_') - result[field_name] = value or None - result['properties'] = properties - if 'size' in result: - result['size'] = int(result['size']) - if 'is_public' in result: - result['is_public'] = bool_from_header_value(result['is_public']) - if 'deleted' in result: - result['deleted'] = bool_from_header_value(result['deleted']) - return result - - -def bool_from_header_value(value): - """ - Returns True if value is a boolean True or the - string 'true', case-insensitive, False otherwise - """ - if isinstance(value, bool): - return value - elif isinstance(value, (basestring, unicode)): - if str(value).lower() == 'true': - return True - return False diff --git a/glance/store/registries.py b/glance/store/registries.py deleted file mode 100644 index f455bad4db..0000000000 --- a/glance/store/registries.py +++ /dev/null @@ -1,83 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack, LLC -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import httplib -import json -import urlparse - -from glance import client - - -class ImageRegistryException(Exception): - """ Base class for all RegistryAdapter exceptions """ - pass - - -class UnknownImageRegistry(ImageRegistryException): - """ Raised if we don't recognize the requested Registry protocol """ - pass - - -class ImageRegistry(object): - """ Base class for all image endpoints """ - - @classmethod - def lookup(cls, parsed_uri): - """ Subclasses must define a lookup method which returns an dictionary - representing the image. - """ - raise NotImplementedError - - -class Parallax(ImageRegistry): - """ - Parallax stuff - """ - - @classmethod - def lookup(cls, image_id): - """ - Takes an image ID and checks if that image is registered in Parallax, - and if so, returns the image metadata. If the image does not exist, - we raise NotFound - """ - # TODO(jaypipes): Make parallax client configurable via options. - # Unfortunately, the decision to make all adapters have no state - # hinders this... - c = client.ParallaxClient() - return c.get_image(image_id) - - -REGISTRY_ADAPTERS = { - 'parallax': Parallax -} - - -def lookup_by_registry(registry, image_id): - """ - Convenience function to lookup image metadata for the given - opaque image identifier and registry. - - :param registry: String name of registry to use for lookups - :param image_id: Opaque image identifier - """ - try: - adapter = REGISTRY_ADAPTERS[registry] - except KeyError: - raise UnknownImageRegistry("'%s' not found" % registry) - - return adapter.lookup(image_id) diff --git a/glance/tests/functional/__init__.py b/glance/tests/functional/__init__.py index 581bd2f71d..3ddf01b594 100644 --- a/glance/tests/functional/__init__.py +++ b/glance/tests/functional/__init__.py @@ -217,7 +217,7 @@ image_cache_driver = %(image_cache_driver)s pipeline = versionnegotiation context %(cache_pipeline)s apiv1app [app:apiv1app] -paste.app_factory = glance.api.v1:app_factory +paste.app_factory = glance.api.v1.router:app_factory [filter:versionnegotiation] paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory diff --git a/glance/tests/stubs.py b/glance/tests/stubs.py index c2f7f2e6cd..492592342a 100644 --- a/glance/tests/stubs.py +++ b/glance/tests/stubs.py @@ -22,8 +22,8 @@ import shutil import webob -from glance.api import v1 as server from glance.api.middleware import version_negotiation +from glance.api.v1 import router import glance.common.client from glance.common import context from glance.common import exception @@ -154,7 +154,7 @@ def stub_out_registry_and_store_server(stubs): 'default_store': 'file', 'filesystem_store_datadir': FAKE_FILESYSTEM_ROOTDIR} api = version_negotiation.VersionNegotiationFilter( - context.ContextMiddleware(server.API(options), options), + context.ContextMiddleware(router.API(options), options), options) res = self.req.get_response(api) diff --git a/glance/tests/unit/test_api.py b/glance/tests/unit/test_api.py index dbf8aa63c9..51850469cb 100644 --- a/glance/tests/unit/test_api.py +++ b/glance/tests/unit/test_api.py @@ -26,7 +26,7 @@ import unittest import stubout import webob -from glance.api import v1 as server +from glance.api.v1 import router from glance.common import context from glance.common import utils from glance.registry import context as rcontext @@ -1936,7 +1936,7 @@ class TestGlanceAPI(unittest.TestCase): stubs.stub_out_registry_and_store_server(self.stubs) stubs.stub_out_filesystem_backend() sql_connection = os.environ.get('GLANCE_SQL_CONNECTION', "sqlite://") - self.api = context.ContextMiddleware(server.API(OPTIONS), OPTIONS) + self.api = context.ContextMiddleware(router.API(OPTIONS), OPTIONS) self.FIXTURES = [ {'id': UUID1, 'name': 'fake image #1', diff --git a/glance/tests/unit/test_wsgi.py b/glance/tests/unit/test_wsgi.py index 88c9c78f9e..27b219fdbf 100644 --- a/glance/tests/unit/test_wsgi.py +++ b/glance/tests/unit/test_wsgi.py @@ -19,6 +19,7 @@ import unittest import webob from glance.common import wsgi +from glance.common import utils from glance.common import exception @@ -201,7 +202,7 @@ class TestHelpers(unittest.TestCase): 'size': 19, 'location': "file:///tmp/glance-tests/2", 'properties': {'distro': 'Ubuntu 10.04 LTS'}} - headers = wsgi.image_meta_to_http_headers(fixture) + headers = utils.image_meta_to_http_headers(fixture) for k, v in headers.iteritems(): self.assert_(isinstance(v, unicode), "%s is not unicode" % v) @@ -217,14 +218,14 @@ class TestHelpers(unittest.TestCase): 'size': 19, 'location': "file:///tmp/glance-tests/2", 'properties': {'distro': 'Ubuntu 10.04 LTS'}} - headers = wsgi.image_meta_to_http_headers(fixture) + headers = utils.image_meta_to_http_headers(fixture) class FakeResponse(): pass response = FakeResponse() response.headers = headers - result = wsgi.get_image_meta_from_headers(response) + result = utils.get_image_meta_from_headers(response) for k, v in fixture.iteritems(): self.assertEqual(v, result[k]) @@ -243,11 +244,11 @@ class TestHelpers(unittest.TestCase): pass for fixture in fixtures: - headers = wsgi.image_meta_to_http_headers(fixture) + headers = utils.image_meta_to_http_headers(fixture) response = FakeResponse() response.headers = headers - result = wsgi.get_image_meta_from_headers(response) + result = utils.get_image_meta_from_headers(response) for k, v in expected.items(): self.assertEqual(v, result[k]) @@ -261,10 +262,10 @@ class TestHelpers(unittest.TestCase): expected = {'is_public': False} for fixture in fixtures: - headers = wsgi.image_meta_to_http_headers(fixture) + headers = utils.image_meta_to_http_headers(fixture) response = FakeResponse() response.headers = headers - result = wsgi.get_image_meta_from_headers(response) + result = utils.get_image_meta_from_headers(response) for k, v in expected.items(): self.assertEqual(v, result[k])