From 54939182e9f207b420fe5f676ac58456e2cad2c8 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 12 Sep 2019 10:32:56 -0700 Subject: [PATCH] remove lazy importer, fix circular imports (cherry picked from commit 487ab5846932cf15d688451a3d61014981cd6e1e) deprecate top-level imports (cherry picked from commit 08536c457c7125c05e0947e62487fbc4bcf51717) --- CHANGES.rst | 18 ++ src/werkzeug/__init__.py | 430 ++++++++++++++-------------- src/werkzeug/contrib/sessions.py | 4 +- src/werkzeug/datastructures.py | 2 +- src/werkzeug/exceptions.py | 17 +- src/werkzeug/formparser.py | 4 +- src/werkzeug/http.py | 64 +---- src/werkzeug/serving.py | 5 +- src/werkzeug/testapp.py | 4 +- src/werkzeug/urls.py | 8 +- src/werkzeug/useragents.py | 20 +- src/werkzeug/utils.py | 94 ++---- src/werkzeug/wrappers/user_agent.py | 3 +- src/werkzeug/wsgi.py | 74 +---- tests/contrib/test_securecookie.py | 2 +- tests/test_compat.py | 40 --- 16 files changed, 290 insertions(+), 499 deletions(-) delete mode 100644 tests/test_compat.py diff --git a/CHANGES.rst b/CHANGES.rst index 7c2c9076b..f0ad588af 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,23 @@ .. currentmodule:: werkzeug +Version 0.16.0 +-------------- + +Unreleased + +- Deprecate most top-level attributes provided by the ``werkzeug`` + module in favor of direct imports. The deprecated imports will be + removed in version 1.0. + + For example, instead of ``import werkzeug; werkzeug.url_quote``, do + ``from werkzeug.urls import url_quote. A deprecation warning will + show the correct import to use. ``werkzeug.exceptions`` and + ``werkzeug.routing`` should also be imported instead of accessed, + but for technical reasons can't show a warning. + + :issue:`2`, :pr:`1640` + + Version 0.15.6 -------------- diff --git a/src/werkzeug/__init__.py b/src/werkzeug/__init__.py index 2ce1a3cb0..49907e93c 100644 --- a/src/werkzeug/__init__.py +++ b/src/werkzeug/__init__.py @@ -1,233 +1,221 @@ -# -*- coding: utf-8 -*- """ - werkzeug - ~~~~~~~~ +werkzeug +~~~~~~~~ - Werkzeug is the Swiss Army knife of Python web development. +Werkzeug is the Swiss Army knife of Python web development. - It provides useful classes and functions for any WSGI application to make - the life of a python web developer much easier. All of the provided - classes are independent from each other so you can mix it with any other - library. +It provides useful classes and functions for any WSGI application to +make the life of a Python web developer much easier. All of the provided +classes are independent from each other so you can mix it with any other +library. - - :copyright: 2007 Pallets - :license: BSD-3-Clause +:copyright: 2007 Pallets +:license: BSD-3-Clause """ -import sys from types import ModuleType -__version__ = "0.15.6" - -# This import magic raises concerns quite often which is why the implementation -# and motivation is explained here in detail now. -# -# The majority of the functions and classes provided by Werkzeug work on the -# HTTP and WSGI layer. There is no useful grouping for those which is why -# they are all importable from "werkzeug" instead of the modules where they are -# implemented. The downside of that is, that now everything would be loaded at -# once, even if unused. -# -# The implementation of a lazy-loading module in this file replaces the -# werkzeug package when imported from within. Attribute access to the werkzeug -# module will then lazily import from the modules that implement the objects. - -# import mapping to objects in other modules -all_by_module = { - "werkzeug.debug": ["DebuggedApplication"], - "werkzeug.local": [ - "Local", - "LocalManager", - "LocalProxy", - "LocalStack", - "release_local", - ], - "werkzeug.serving": ["run_simple"], - "werkzeug.test": ["Client", "EnvironBuilder", "create_environ", "run_wsgi_app"], - "werkzeug.testapp": ["test_app"], - "werkzeug.exceptions": ["abort", "Aborter"], - "werkzeug.urls": [ - "url_decode", - "url_encode", - "url_quote", - "url_quote_plus", - "url_unquote", - "url_unquote_plus", - "url_fix", - "Href", - "iri_to_uri", - "uri_to_iri", - ], - "werkzeug.formparser": ["parse_form_data"], - "werkzeug.utils": [ - "escape", - "environ_property", - "append_slash_redirect", - "redirect", - "cached_property", - "import_string", - "unescape", - "format_string", - "find_modules", - "header_property", - "html", - "xhtml", - "HTMLBuilder", - "validate_arguments", - "ArgumentValidationError", - "bind_arguments", - "secure_filename", - ], - "werkzeug.wsgi": [ - "get_current_url", - "get_host", - "pop_path_info", - "peek_path_info", - "ClosingIterator", - "FileWrapper", - "make_line_iter", - "LimitedStream", - "responder", - "wrap_file", - "extract_path_info", - ], - "werkzeug.datastructures": [ - "MultiDict", - "CombinedMultiDict", - "Headers", - "EnvironHeaders", - "ImmutableList", - "ImmutableDict", - "ImmutableMultiDict", - "TypeConversionDict", - "ImmutableTypeConversionDict", - "Accept", - "MIMEAccept", - "CharsetAccept", - "LanguageAccept", - "RequestCacheControl", - "ResponseCacheControl", - "ETags", - "HeaderSet", - "WWWAuthenticate", - "Authorization", - "FileMultiDict", - "CallbackDict", - "FileStorage", - "OrderedMultiDict", - "ImmutableOrderedMultiDict", - ], - "werkzeug.useragents": ["UserAgent"], - "werkzeug.http": [ - "parse_etags", - "parse_date", - "http_date", - "cookie_date", - "parse_cache_control_header", - "is_resource_modified", - "parse_accept_header", - "parse_set_header", - "quote_etag", - "unquote_etag", - "generate_etag", - "dump_header", - "parse_list_header", - "parse_dict_header", - "parse_authorization_header", - "parse_www_authenticate_header", - "remove_entity_headers", - "is_entity_header", - "remove_hop_by_hop_headers", - "parse_options_header", - "dump_options_header", - "is_hop_by_hop_header", - "unquote_header_value", - "quote_header_value", - "HTTP_STATUS_CODES", - "dump_cookie", - "parse_cookie", - ], - "werkzeug.wrappers": [ - "BaseResponse", - "BaseRequest", - "Request", - "Response", - "AcceptMixin", - "ETagRequestMixin", - "ETagResponseMixin", - "ResponseStreamMixin", - "CommonResponseDescriptorsMixin", - "UserAgentMixin", - "AuthorizationMixin", - "WWWAuthenticateMixin", - "CommonRequestDescriptorsMixin", - ], - "werkzeug.middleware.dispatcher": ["DispatcherMiddleware"], - "werkzeug.middleware.shared_data": ["SharedDataMiddleware"], - "werkzeug.security": ["generate_password_hash", "check_password_hash"], - # the undocumented easteregg ;-) - "werkzeug._internal": ["_easteregg"], -} - -# modules that should be imported when accessed as attributes of werkzeug -attribute_modules = frozenset(["exceptions", "routing"]) - -object_origins = {} -for module, items in all_by_module.items(): - for item in items: - object_origins[item] = module - - -class module(ModuleType): - """Automatically import objects from the modules.""" - - def __getattr__(self, name): - if name in object_origins: - module = __import__(object_origins[name], None, None, [name]) - for extra_name in all_by_module[module.__name__]: - setattr(self, extra_name, getattr(module, extra_name)) - return getattr(module, name) - elif name in attribute_modules: - __import__("werkzeug." + name) - return ModuleType.__getattribute__(self, name) +__version__ = "0.16.0.dev0" + +__all__ = ["run_simple", "Client", "Request", "Response", "__version__"] + + +class _DeprecatedImportModule(ModuleType): + """Wrap a module in order to raise """ + + def __init__(self, name, available, removed_in): + import sys + + super(_DeprecatedImportModule, self).__init__(name) # noqa F821 + self._real_module = sys.modules[name] # noqa F821 + sys.modules[name] = self + self._removed_in = removed_in + self._origin = {item: mod for mod, items in available.items() for item in items} + mod_all = getattr(self._real_module, "__all__", dir(self._real_module)) + self.__all__ = sorted(mod_all + list(self._origin)) + + def __getattr__(self, item): + # Don't export internal variables. + if item in {"_real_module", "_origin", "_removed_in"}: + raise AttributeError(item) + + if item in self._origin: + from importlib import import_module + + origin = self._origin[item] + + if origin == ".": + # No warning for the "submodule as attribute" case, it's way too messy + # and unreliable to try to distinguish 'from werkzueug import + # exceptions' and 'import werkzeug; werkzeug.exceptions'. + value = import_module(origin + item, self.__name__) + else: + from warnings import warn + + # Import the module, get the attribute, and show a warning about where + # to correctly import it from. + mod = import_module(origin, self.__name__.rsplit(".")[0]) + value = getattr(mod, item) + warn( + "The import '{name}.{item}' is deprecated and will be removed in" + " {removed_in}. Use 'from {name}{origin} import {item}'" + " instead.".format( + name=self.__name__, + item=item, + removed_in=self._removed_in, + origin=origin, + ), + DeprecationWarning, + stacklevel=2, + ) + else: + value = getattr(self._real_module, item) + + # Cache the value so it won't go through this process on subsequent accesses. + setattr(self, item, value) + return value def __dir__(self): - """Just show what we want to show.""" - result = list(new_module.__all__) - result.extend( - ( - "__file__", - "__doc__", - "__all__", - "__docformat__", - "__name__", - "__path__", - "__package__", - "__version__", - ) - ) - return result - - -# keep a reference to this module so that it's not garbage collected -old_module = sys.modules["werkzeug"] - - -# setup the new module and patch it into the dict of loaded modules -new_module = sys.modules["werkzeug"] = module("werkzeug") -new_module.__dict__.update( + return sorted(dir(self._real_module) + list(self._origin)) + + +del ModuleType + +_DeprecatedImportModule( + __name__, { - "__file__": __file__, - "__package__": "werkzeug", - "__path__": __path__, - "__doc__": __doc__, - "__version__": __version__, - "__all__": tuple(object_origins) + tuple(attribute_modules), - "__docformat__": "restructuredtext en", - } + ".": ["exceptions", "routing"], + "._internal": ["_easteregg"], + ".datastructures": [ + "Accept", + "Authorization", + "CallbackDict", + "CharsetAccept", + "CombinedMultiDict", + "EnvironHeaders", + "ETags", + "FileMultiDict", + "FileStorage", + "Headers", + "HeaderSet", + "ImmutableDict", + "ImmutableList", + "ImmutableMultiDict", + "ImmutableOrderedMultiDict", + "ImmutableTypeConversionDict", + "LanguageAccept", + "MIMEAccept", + "MultiDict", + "OrderedMultiDict", + "RequestCacheControl", + "ResponseCacheControl", + "TypeConversionDict", + "WWWAuthenticate", + ], + ".debug": ["DebuggedApplication"], + ".exceptions": ["abort", "Aborter"], + ".formparser": ["parse_form_data"], + ".http": [ + "cookie_date", + "dump_cookie", + "dump_header", + "dump_options_header", + "generate_etag", + "http_date", + "HTTP_STATUS_CODES", + "is_entity_header", + "is_hop_by_hop_header", + "is_resource_modified", + "parse_accept_header", + "parse_authorization_header", + "parse_cache_control_header", + "parse_cookie", + "parse_date", + "parse_dict_header", + "parse_etags", + "parse_list_header", + "parse_options_header", + "parse_set_header", + "parse_www_authenticate_header", + "quote_etag", + "quote_header_value", + "remove_entity_headers", + "remove_hop_by_hop_headers", + "unquote_etag", + "unquote_header_value", + ], + ".local": [ + "Local", + "LocalManager", + "LocalProxy", + "LocalStack", + "release_local", + ], + ".middleware.dispatcher": ["DispatcherMiddleware"], + ".middleware.shared_data": ["SharedDataMiddleware"], + ".security": ["check_password_hash", "generate_password_hash"], + ".test": ["create_environ", "EnvironBuilder", "run_wsgi_app"], + ".testapp": ["test_app"], + ".urls": [ + "Href", + "iri_to_uri", + "uri_to_iri", + "url_decode", + "url_encode", + "url_fix", + "url_quote", + "url_quote_plus", + "url_unquote", + "url_unquote_plus", + ], + ".useragents": ["UserAgent"], + ".utils": [ + "append_slash_redirect", + "ArgumentValidationError", + "bind_arguments", + "cached_property", + "environ_property", + "escape", + "find_modules", + "format_string", + "header_property", + "html", + "HTMLBuilder", + "import_string", + "redirect", + "secure_filename", + "unescape", + "validate_arguments", + "xhtml", + ], + ".wrappers.accept": ["AcceptMixin"], + ".wrappers.auth": ["AuthorizationMixin", "WWWAuthenticateMixin"], + ".wrappers.base_request": ["BaseRequest"], + ".wrappers.base_response": ["BaseResponse"], + ".wrappers.common_descriptors": [ + "CommonRequestDescriptorsMixin", + "CommonResponseDescriptorsMixin", + ], + ".wrappers.etag": ["ETagRequestMixin", "ETagResponseMixin"], + ".wrappers.response": ["ResponseStreamMixin"], + ".wrappers.user_agent": ["UserAgentMixin"], + ".wsgi": [ + "ClosingIterator", + "extract_path_info", + "FileWrapper", + "get_current_url", + "get_host", + "LimitedStream", + "make_line_iter", + "peek_path_info", + "pop_path_info", + "responder", + "wrap_file", + ], + }, + "Werkzeug 1.0", ) - -# Due to bootstrapping issues we need to import exceptions here. -# Don't ask :-( -__import__("werkzeug.exceptions") +from .serving import run_simple +from .test import Client +from .wrappers import Request +from .wrappers import Response diff --git a/src/werkzeug/contrib/sessions.py b/src/werkzeug/contrib/sessions.py index 866e827c1..53567a1cc 100644 --- a/src/werkzeug/contrib/sessions.py +++ b/src/werkzeug/contrib/sessions.py @@ -67,9 +67,9 @@ def application(environ, start_response): from .._compat import text_type from ..datastructures import CallbackDict from ..filesystem import get_filesystem_encoding +from ..http import dump_cookie +from ..http import parse_cookie from ..posixemulation import rename -from ..utils import dump_cookie -from ..utils import parse_cookie from ..wsgi import ClosingIterator warnings.warn( diff --git a/src/werkzeug/datastructures.py b/src/werkzeug/datastructures.py index 9643db96c..0b8097def 100644 --- a/src/werkzeug/datastructures.py +++ b/src/werkzeug/datastructures.py @@ -14,6 +14,7 @@ from copy import deepcopy from itertools import repeat +from . import exceptions from ._compat import BytesIO from ._compat import collections_abc from ._compat import integer_types @@ -2839,7 +2840,6 @@ def __repr__(self): # circular dependencies -from . import exceptions from .http import dump_header from .http import dump_options_header from .http import generate_etag diff --git a/src/werkzeug/exceptions.py b/src/werkzeug/exceptions.py index bfd20dc1d..a7295ca7c 100644 --- a/src/werkzeug/exceptions.py +++ b/src/werkzeug/exceptions.py @@ -59,18 +59,12 @@ def application(environ, start_response): """ import sys -import werkzeug - -# Because of bootstrapping reasons we need to manually patch ourselves -# onto our parent module. -werkzeug.exceptions = sys.modules[__name__] - from ._compat import implements_to_string from ._compat import integer_types from ._compat import iteritems from ._compat import text_type from ._internal import _get_environ -from .wrappers import Response +from .utils import escape @implements_to_string @@ -141,6 +135,8 @@ def description(self, value): @property def name(self): """The status name.""" + from .http import HTTP_STATUS_CODES + return HTTP_STATUS_CODES.get(self.code, "Unknown Error") def get_description(self, environ=None): @@ -176,6 +172,8 @@ def get_response(self, environ=None): on how the request looked like. :return: a :class:`Response` object or a subclass thereof. """ + from .wrappers.response import Response + if self.response is not None: return self.response if environ is not None: @@ -776,11 +774,6 @@ def abort(status, *args, **kwargs): _aborter = Aborter() - #: An exception that is used to signal both a :exc:`KeyError` and a #: :exc:`BadRequest`. Used by many of the datastructures. BadRequestKeyError = BadRequest.wrap(KeyError) - -# imported here because of circular dependencies of werkzeug.utils -from .http import HTTP_STATUS_CODES -from .utils import escape diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py index 0ddc5c8ff..ffdb9b0f1 100644 --- a/src/werkzeug/formparser.py +++ b/src/werkzeug/formparser.py @@ -16,6 +16,7 @@ from itertools import repeat from itertools import tee +from . import exceptions from ._compat import BytesIO from ._compat import text_type from ._compat import to_native @@ -581,6 +582,3 @@ def parse(self, file, boundary, content_length): form = (p[1] for p in formstream if p[0] == "form") files = (p[1] for p in filestream if p[0] == "file") return self.cls(form), self.cls(files) - - -from . import exceptions diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py index af3200750..686824c12 100644 --- a/src/werkzeug/http.py +++ b/src/werkzeug/http.py @@ -1144,6 +1144,8 @@ def dump_cookie( value = to_bytes(value, charset) if path is not None: + from .urls import iri_to_uri + path = iri_to_uri(path, charset) domain = _make_cookie_domain(domain) if isinstance(max_age, timedelta): @@ -1235,7 +1237,7 @@ def is_byte_range_valid(start, stop, length): return 0 <= start < length -# circular dependency fun +# circular dependencies from .datastructures import Accept from .datastructures import Authorization from .datastructures import ContentRange @@ -1246,58 +1248,12 @@ def is_byte_range_valid(start, stop, length): from .datastructures import RequestCacheControl from .datastructures import TypeConversionDict from .datastructures import WWWAuthenticate -from .urls import iri_to_uri - -# DEPRECATED -from .datastructures import CharsetAccept as _CharsetAccept -from .datastructures import Headers as _Headers -from .datastructures import LanguageAccept as _LanguageAccept -from .datastructures import MIMEAccept as _MIMEAccept - -class MIMEAccept(_MIMEAccept): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.http.MIMEAccept' has moved to 'werkzeug" - ".datastructures.MIMEAccept' as of version 0.5. This old" - " import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(MIMEAccept, self).__init__(*args, **kwargs) - - -class CharsetAccept(_CharsetAccept): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.http.CharsetAccept' has moved to 'werkzeug" - ".datastructures.CharsetAccept' as of version 0.5. This old" - " import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(CharsetAccept, self).__init__(*args, **kwargs) +from werkzeug import _DeprecatedImportModule - -class LanguageAccept(_LanguageAccept): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.http.LanguageAccept' has moved to 'werkzeug" - ".datastructures.LanguageAccept' as of version 0.5. This" - " old import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(LanguageAccept, self).__init__(*args, **kwargs) - - -class Headers(_Headers): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.http.Headers' has moved to 'werkzeug" - ".datastructures.Headers' as of version 0.5. This old" - " import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(Headers, self).__init__(*args, **kwargs) +_DeprecatedImportModule( + __name__, + {".datastructures": ["CharsetAccept", "Headers", "LanguageAccept", "MIMEAccept"]}, + "Werkzeug 1.0", +) +del _DeprecatedImportModule diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py index ff9f8805f..d817120f2 100644 --- a/src/werkzeug/serving.py +++ b/src/werkzeug/serving.py @@ -41,7 +41,6 @@ import socket import sys -import werkzeug from ._compat import PY2 from ._compat import reraise from ._compat import WIN @@ -174,7 +173,9 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object): @property def server_version(self): - return "Werkzeug/" + werkzeug.__version__ + from . import __version__ + + return "Werkzeug/" + __version__ def make_environ(self): request_url = url_parse(self.path) diff --git a/src/werkzeug/testapp.py b/src/werkzeug/testapp.py index 8ea23bee1..5ea854904 100644 --- a/src/werkzeug/testapp.py +++ b/src/werkzeug/testapp.py @@ -14,7 +14,7 @@ import sys from textwrap import wrap -import werkzeug +from . import __version__ as _werkzeug_version from .utils import escape from .wrappers import BaseRequest as Request from .wrappers import BaseResponse as Response @@ -205,7 +205,7 @@ def render_testapp(req): "os": escape(os.name), "api_version": sys.api_version, "byteorder": sys.byteorder, - "werkzeug_version": werkzeug.__version__, + "werkzeug_version": _werkzeug_version, "python_eggs": "\n".join(python_eggs), "wsgi_env": "\n".join(wsgi_env), "sys_path": "\n".join(sys_path), diff --git a/src/werkzeug/urls.py b/src/werkzeug/urls.py index 38e9e5adf..566017d7f 100644 --- a/src/werkzeug/urls.py +++ b/src/werkzeug/urls.py @@ -31,8 +31,6 @@ from ._compat import try_coerce_native from ._internal import _decode_idna from ._internal import _encode_idna -from .datastructures import iter_multi_items -from .datastructures import MultiDict # A regular expression for what a valid schema looks like _scheme_re = re.compile(r"^[a-zA-Z0-9+-.]+$") @@ -415,6 +413,8 @@ def _unquote_to_bytes(string, unsafe=""): def _url_encode_impl(obj, charset, encode_keys, sort, key): + from .datastructures import iter_multi_items + iterable = iter_multi_items(obj) if sort: iterable = sorted(iterable, key=key) @@ -825,6 +825,8 @@ def url_decode( or `None` the default :class:`MultiDict` is used. """ if cls is None: + from .datastructures import MultiDict + cls = MultiDict if isinstance(s, text_type) and not isinstance(separator, text_type): separator = separator.decode(charset or "ascii") @@ -884,6 +886,8 @@ def url_decode_stream( return decoder if cls is None: + from .datastructures import MultiDict + cls = MultiDict return cls(decoder) diff --git a/src/werkzeug/useragents.py b/src/werkzeug/useragents.py index e265e0939..8fce41538 100644 --- a/src/werkzeug/useragents.py +++ b/src/werkzeug/useragents.py @@ -12,7 +12,6 @@ :license: BSD-3-Clause """ import re -import warnings class UserAgentParser(object): @@ -203,18 +202,9 @@ def __repr__(self): return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version) -# DEPRECATED -from .wrappers import UserAgentMixin as _UserAgentMixin +from werkzeug import _DeprecatedImportModule - -class UserAgentMixin(_UserAgentMixin): - @property - def user_agent(self, *args, **kwargs): - warnings.warn( - "'werkzeug.useragents.UserAgentMixin' should be imported" - " from 'werkzeug.wrappers.UserAgentMixin'. This old import" - " will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - return super(_UserAgentMixin, self).user_agent +_DeprecatedImportModule( + __name__, {".wrappers.user_agent": ["UserAgentMixin"]}, "Werkzeug 1.0" +) +del _DeprecatedImportModule diff --git a/src/werkzeug/utils.py b/src/werkzeug/utils.py index 20620572c..477164e30 100644 --- a/src/werkzeug/utils.py +++ b/src/werkzeug/utils.py @@ -15,7 +15,6 @@ import pkgutil import re import sys -import warnings from ._compat import iteritems from ._compat import PY2 @@ -757,80 +756,19 @@ def __repr__(self): ) -# DEPRECATED -from .datastructures import CombinedMultiDict as _CombinedMultiDict -from .datastructures import EnvironHeaders as _EnvironHeaders -from .datastructures import Headers as _Headers -from .datastructures import MultiDict as _MultiDict -from .http import dump_cookie as _dump_cookie -from .http import parse_cookie as _parse_cookie - - -class MultiDict(_MultiDict): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.utils.MultiDict' has moved to 'werkzeug" - ".datastructures.MultiDict' as of version 0.5. This old" - " import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(MultiDict, self).__init__(*args, **kwargs) - - -class CombinedMultiDict(_CombinedMultiDict): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.utils.CombinedMultiDict' has moved to 'werkzeug" - ".datastructures.CombinedMultiDict' as of version 0.5. This" - " old import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(CombinedMultiDict, self).__init__(*args, **kwargs) - - -class Headers(_Headers): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.utils.Headers' has moved to 'werkzeug" - ".datastructures.Headers' as of version 0.5. This old" - " import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(Headers, self).__init__(*args, **kwargs) - - -class EnvironHeaders(_EnvironHeaders): - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.utils.EnvironHeaders' has moved to 'werkzeug" - ".datastructures.EnvironHeaders' as of version 0.5. This" - " old import will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(EnvironHeaders, self).__init__(*args, **kwargs) - - -def parse_cookie(*args, **kwargs): - warnings.warn( - "'werkzeug.utils.parse_cookie' as moved to 'werkzeug.http" - ".parse_cookie' as of version 0.5. This old import will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - return _parse_cookie(*args, **kwargs) - - -def dump_cookie(*args, **kwargs): - warnings.warn( - "'werkzeug.utils.dump_cookie' as moved to 'werkzeug.http" - ".dump_cookie' as of version 0.5. This old import will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - return _dump_cookie(*args, **kwargs) +from werkzeug import _DeprecatedImportModule + +_DeprecatedImportModule( + __name__, + { + ".datastructures": [ + "CombinedMultiDict", + "EnvironHeaders", + "Headers", + "MultiDict", + ], + ".http": ["dump_cookie", "parse_cookie"], + }, + "Werkzeug 1.0", +) +del _DeprecatedImportModule diff --git a/src/werkzeug/wrappers/user_agent.py b/src/werkzeug/wrappers/user_agent.py index 72588dd94..a32d8acd2 100644 --- a/src/werkzeug/wrappers/user_agent.py +++ b/src/werkzeug/wrappers/user_agent.py @@ -1,3 +1,4 @@ +from ..useragents import UserAgent from ..utils import cached_property @@ -10,6 +11,4 @@ class UserAgentMixin(object): @cached_property def user_agent(self): """The current user agent.""" - from ..useragents import UserAgent - return UserAgent(self.environ) diff --git a/src/werkzeug/wsgi.py b/src/werkzeug/wsgi.py index f069f2d86..741195547 100644 --- a/src/werkzeug/wsgi.py +++ b/src/werkzeug/wsgi.py @@ -10,7 +10,6 @@ """ import io import re -import warnings from functools import partial from functools import update_wrapper from itertools import chain @@ -1001,67 +1000,14 @@ def readable(self): return True -# DEPRECATED -from .middleware.dispatcher import DispatcherMiddleware as _DispatcherMiddleware -from .middleware.http_proxy import ProxyMiddleware as _ProxyMiddleware -from .middleware.shared_data import SharedDataMiddleware as _SharedDataMiddleware +from werkzeug import _DeprecatedImportModule - -class ProxyMiddleware(_ProxyMiddleware): - """ - .. deprecated:: 0.15 - ``werkzeug.wsgi.ProxyMiddleware`` has moved to - :mod:`werkzeug.middleware.http_proxy`. This import will be - removed in 1.0. - """ - - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.wsgi.ProxyMiddleware' has moved to 'werkzeug" - ".middleware.http_proxy.ProxyMiddleware'. This import is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(ProxyMiddleware, self).__init__(*args, **kwargs) - - -class SharedDataMiddleware(_SharedDataMiddleware): - """ - .. deprecated:: 0.15 - ``werkzeug.wsgi.SharedDataMiddleware`` has moved to - :mod:`werkzeug.middleware.shared_data`. This import will be - removed in 1.0. - """ - - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.wsgi.SharedDataMiddleware' has moved to" - " 'werkzeug.middleware.shared_data.SharedDataMiddleware'." - " This import is deprecated as of version 0.15 and will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(SharedDataMiddleware, self).__init__(*args, **kwargs) - - -class DispatcherMiddleware(_DispatcherMiddleware): - """ - .. deprecated:: 0.15 - ``werkzeug.wsgi.DispatcherMiddleware`` has moved to - :mod:`werkzeug.middleware.dispatcher`. This import will be - removed in 1.0. - """ - - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.wsgi.DispatcherMiddleware' has moved to" - " 'werkzeug.middleware.dispatcher.DispatcherMiddleware'." - " This import is deprecated as of version 0.15 and will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(DispatcherMiddleware, self).__init__(*args, **kwargs) +_DeprecatedImportModule( + __name__, + { + ".middleware.dispatcher": ["DispatcherMiddleware"], + ".middleware.http_proxy": ["ProxyMiddleware"], + ".middleware.shared_data": ["SharedDataMiddleware"], + }, + "Werkzeug 1.0", +) diff --git a/tests/contrib/test_securecookie.py b/tests/contrib/test_securecookie.py index 7231ac889..cea072c2e 100644 --- a/tests/contrib/test_securecookie.py +++ b/tests/contrib/test_securecookie.py @@ -14,7 +14,7 @@ from werkzeug._compat import to_native from werkzeug.contrib.securecookie import SecureCookie -from werkzeug.utils import parse_cookie +from werkzeug.http import parse_cookie from werkzeug.wrappers import Request from werkzeug.wrappers import Response diff --git a/tests/test_compat.py b/tests/test_compat.py deleted file mode 100644 index 98851ba28..000000000 --- a/tests/test_compat.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# flake8: noqa -""" - tests.compat - ~~~~~~~~~~~~ - - Ensure that old stuff does not break on update. - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -from werkzeug.test import create_environ -from werkzeug.wrappers import Response - - -def test_old_imports(): - from werkzeug.utils import ( - Headers, - MultiDict, - CombinedMultiDict, - Headers, - EnvironHeaders, - ) - from werkzeug.http import ( - Accept, - MIMEAccept, - CharsetAccept, - LanguageAccept, - ETags, - HeaderSet, - WWWAuthenticate, - Authorization, - ) - - -def test_exposed_werkzeug_mod(): - import werkzeug - - for key in werkzeug.__all__: - getattr(werkzeug, key)