← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jml/launchpad/flush-out-canonical into lp:launchpad

 

Jonathan Lange has proposed merging lp:~jml/launchpad/flush-out-canonical into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jml/launchpad/flush-out-canonical/+merge/50192

This branch flushes out a lot of the modules that were living in the 'canonical' package.  Easiest to take them module-by-module.

autodecorate: 
 * moved to lp.services.utils
 * doctest replaced with unit tests
base: 
 * moved to lp.services.utils
 * doctest replaced with unit tests
 * inspired new compress_hash method, changed all the call sites to use that
chunkydiff:
 * Only used to display different output for page tests
 * Saw the output wasn't that different, so deleted it and all its kind.
encoding:
 * Moved to lp.services
functional:
 * Only contained xmlrpc stuff, so moved to lp.services.xmlrpc
__init__:
 * Moved the content to lp_sitecustomize (which is where it belongs)
 * Added docstring
mem:
 * Moved to lp.services.profile. Yes we are using this.

-- 
https://code.launchpad.net/~jml/launchpad/flush-out-canonical/+merge/50192
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jml/launchpad/flush-out-canonical into lp:launchpad.
=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf	2010-09-17 09:16:27 +0000
+++ configs/testrunner/launchpad-lazr.conf	2011-02-17 17:10:46 +0000
@@ -6,7 +6,6 @@
 extends: ../development/launchpad-lazr.conf
 
 [canonical]
-chunkydiff: False
 cron_control_url: file:lib/lp/services/scripts/tests/cronscripts.ini
 
 [archivepublisher]

=== modified file 'lib/canonical/__init__.py'
--- lib/canonical/__init__.py	2010-03-30 08:50:10 +0000
+++ lib/canonical/__init__.py	2011-02-17 17:10:46 +0000
@@ -1,21 +1,8 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# This is the python package that defines the 'canonical' package namespace.
-
-# We currently only translate launchpad specific stuff using the Zope3 i18n
-# routines, so this is not needed.
-#from zope.i18n.messageid import MessageFactory
-#_ = MessageFactory("canonical")
-
-# Filter all deprecation warnings for Zope 3.6, which eminate from
-# the zope package.
-import warnings
-filter_pattern = '.*(Zope 3.6|provide.*global site manager).*'
-warnings.filterwarnings(
-    'ignore', filter_pattern, category=DeprecationWarning)
-
-# XXX wgrant 2010-03-30 bug=551510:
-# Also filter apt_pkg warnings, since Lucid's python-apt has a new API.
-warnings.filterwarnings(
-    'ignore', '.*apt_pkg.*', category=DeprecationWarning)
+"""The canonical namespace package.
+
+WARNING: This package is deprecated.  New code should go into the `lp`
+package.
+"""

=== removed file 'lib/canonical/autodecorate.py'
--- lib/canonical/autodecorate.py	2009-06-25 05:30:52 +0000
+++ lib/canonical/autodecorate.py	1970-01-01 00:00:00 +0000
@@ -1,29 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Metaclass to automatically decorate methods."""
-
-from types import FunctionType
-
-
-__metaclass__ = type
-__all__ = ['AutoDecorate']
-
-
-def AutoDecorate(*decorators):
-    """Factory to generate metaclasses that automatically apply decorators."""
-
-    class AutoDecorateMetaClass(type):
-        def __new__(cls, class_name, bases, class_dict):
-            new_class_dict = {}
-            for name, value in class_dict.items():
-                if type(value) == FunctionType:
-                    for decorator in decorators:
-                        value = decorator(value)
-                        assert callable(value), (
-                            "Decorator %s didn't return a callable."
-                            % repr(decorator))
-                new_class_dict[name] = value
-            return type.__new__(cls, class_name, bases, new_class_dict)
-
-    return AutoDecorateMetaClass

=== removed file 'lib/canonical/base.py'
--- lib/canonical/base.py	2010-02-09 00:17:40 +0000
+++ lib/canonical/base.py	1970-01-01 00:00:00 +0000
@@ -1,81 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Convert numbers to an arbitrary base numbering scheme
-
-This file is based on work from the Python Cookbook and is under the Python
-license.
-
-"""
-__all__ = ['base']
-
-import string
-abc = string.digits + string.ascii_letters
-
-def base(number, radix):
-    """Inverse function to int(str,radix) and long(str,radix)
-
-    >>> base(35, 36)
-    'z'
-
-    We can go higher than base 36, but we do this by using upper
-    case letters. This is not a standard representation, but
-    useful for using this method as a compression algorithm.
-
-    >>> base(61, 62)
-    'Z'
-
-    We get identical results to the hex builtin, without the 0x prefix
-
-    >>> [i for i in range(0, 5000, 9) if hex(i)[2:] != base(i, 16)]
-    []
-
-    This method is useful for shrinking sha1 and md5 hashes, but keeping
-    them in simple ASCII suitable for URL's etc.
-
-    >>> import hashlib
-    >>> s = hashlib.sha1('foo').hexdigest()
-    >>> s
-    '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
-    >>> i = long(s, 16)
-    >>> i
-    68123873083688143418383284816464454849230703155L
-    >>> base(i, 62)
-    '1HyPQr2xj1nmnkQXBCJXUdQoy5l'
-    >>> base(int(hashlib.md5('foo').hexdigest(), 16), 62)
-    '5fX649Stem9fET0lD46zVe'
-
-    A sha1 hash can be compressed to 27 characters or less
-    >>> len(base(long('F'*40, 16), 62))
-    27
-
-    A md5 hash can be compressed to 22 characters or less
-    >>> len(base(long('F'*32, 16), 62))
-    22
-
-    """
-    if not 2 <= radix <= 62:
-        raise ValueError, "radix must be in 2..62"
-
-    result = []
-    addon = result.append
-    if number < 0:
-        number = -number
-        addon('-')
-    elif number == 0:
-        addon('0')
-
-    _divmod, _abc = divmod, abc
-    while number:
-        number, rdigit = _divmod(number, radix)
-        addon(_abc[rdigit])
-
-    result.reverse()
-    return ''.join(result)
-
-def _test():
-    import doctest, base
-    doctest.testmod(base)
-
-if __name__ == '__main__':
-    _test()

=== removed file 'lib/canonical/chunkydiff.py'
--- lib/canonical/chunkydiff.py	2009-06-25 05:30:52 +0000
+++ lib/canonical/chunkydiff.py	1970-01-01 00:00:00 +0000
@@ -1,249 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Chunky diffs.
-
-Useful for page tests that have elisions.
-"""
-
-import re
-
-__metaclass__ = type
-
-def elided_source(tested, actual, debug=False, show=False,
-                  normalize_whitespace=False):
-    if debug:
-        import pdb; pdb.set_trace()
-    chunks = tested.split('...')
-
-    previous_chunk = None
-    chunk = None
-    Unknown = None
-    currentpos = 0
-    results = []
-    for next_chunk in chunks + [None]:
-        if chunk is None:
-            chunk = next_chunk
-            continue
-        if chunk != '':
-            if previous_chunk is None:
-                chunk_starts_with_ellipsis = False
-            else:
-                chunk_starts_with_ellipsis = True
-            if next_chunk is None:
-                chunk_ends_with_ellipsis = False
-            else:
-                chunk_ends_with_ellipsis = True
-
-            result = find_chunk(
-                chunk, actual[currentpos:],
-                anchor_start=not chunk_starts_with_ellipsis,
-                anchor_end=not chunk_ends_with_ellipsis,
-                debug=debug, show=show,
-                normalize_whitespace=normalize_whitespace)
-            if result is None:
-                results.append(None)
-            else:
-                string, startofremainder = result
-                currentpos += startofremainder
-                # XXX: ddaa 2005-03-25:
-                # Off by one. Should be += startofremainder + 1
-                results.append(ResultChunk(string, currentpos))
-        previous_chunk, chunk = chunk, next_chunk
-
-    starts_with_ellipsis = chunks[0] == ''
-    ends_with_ellipsis = chunks[-1] == ''
-
-    resultsummary = ''.join(
-        [mnemonic_for_result(result) for result in results]
-        )
-    if re.match('^N+$', resultsummary):
-        # If all results are None...
-        output = actual
-    elif re.match('^S+$', resultsummary):
-        # If no results are None...
-        output = '...'.join([result.text for result in results])
-        if starts_with_ellipsis:
-            output = '...' + output
-        if ends_with_ellipsis:
-            output = output + '...'
-    elif re.match('^S+N+$', resultsummary) and ends_with_ellipsis:
-        # Where there are one or more None values at the end of results,
-        # and ends_with_ellipsis, we can end without an ellipsis while
-        # including the remainder of 'actual' from the end of the last
-        # matched chunk.
-
-        # Find last non-None result.
-        for result in reversed(results):
-            if result is not None:
-                break
-        # Get the remainder value from it.
-        if starts_with_ellipsis:
-            output = '...'
-        else:
-            # XXX: ddaa 2005-03-25: Test this code path!
-            output = ''
-        last_result = None
-        for result in results:
-            if result is not None:
-                output += result.text
-                last_result = result
-            else:
-                output += actual[last_result.remainderpos:]
-                break
-
-    else:
-        # XXX: ddaa 2005-03-25: Test this code path!
-        output = actual
-
-    return output
-
-class ResultChunk:
-
-    def __init__(self, text, remainderpos):
-        self.text = text
-        self.remainderpos = remainderpos
-
-
-def reversed(seq):
-    L = list(seq)
-    L.reverse()
-    return L
-
-def mnemonic_for_result(result):
-    """Returns 'N' if result is None, otherwise 'S'."""
-    if result is None:
-        return 'N'
-    else:
-        return 'S'
-
-def find_chunk(chunk, actual, anchor_start=False, anchor_end=False,
-               debug=False, show=False, normalize_whitespace=False):
-    if debug:
-        import pdb; pdb.set_trace()
-    if not anchor_start:
-        # Find the start of the chunk.
-        beginning = ''
-        beginning_for_regex = ''
-        manyfound = False
-        for char in chunk:
-            if normalize_whitespace and char.isspace():
-                if beginning_for_regex[-2:] != r'\s':
-                    beginning_for_regex += r'\s'
-            else:
-                beginning_for_regex += re.escape(char)
-            beginning += char
-            numfound = len(re.findall(beginning_for_regex, actual))
-            #numfound = actual.count(beginning)
-            if numfound == 0:
-                if manyfound:
-                    beginning = manyfound_beginning
-                    beginning_for_regex = manyfound_beginning_for_regex
-                    if anchor_end:
-                        beginning_pos = list(re.finditer(
-                            beginning_for_regex, actual))[-1].start()
-                        #beginning_pos = actual.rfind(beginning)
-                    else:
-                        beginning_pos = re.search(
-                            beginning_for_regex, actual).start()
-                        # XXX ddaa 2005-03-25: This should be .span()[1]. 
-                        # Needs a test.
-                        #beginning_pos = actual.find(beginning)
-                    break
-                else:
-                    beginning = ''
-                    beginning_for_regex = ''
-                    beginning_pos = 0
-                    break
-            elif numfound == 1:
-                beginning_pos = re.search(
-                    beginning_for_regex, actual).span()[1]
-                #beginning_pos = actual.find(beginning) + len(beginning)
-                break
-            else:
-                manyfound = True
-                manyfound_beginning = beginning
-                manyfound_beginning_for_regex = beginning_for_regex
-        else:
-            if manyfound:
-                if anchor_end:
-                    beginning_pos = list(re.finditer(
-                        beginning_for_regex, actual))[-1].start()
-                    #beginning_pos = actual.rfind(beginning)
-                else:
-                    beginning_pos = re.search(
-                        beginning_for_regex, actual).start()
-                    # XXX ddaa 2005-03-25: This should be .span()[1]. 
-                    # Needs a test.
-                    #beginning_pos = actual.find(beginning)
-            else:
-                return None
-    else:
-        beginning_pos = 0
-        beginning = ''
-        beginning_for_regex = ''
-
-    # Find the end of the chunk.
-    end = ''
-    end_for_regex = ''
-    chunk_with_no_beginning = chunk[len(beginning):]
-    if not chunk_with_no_beginning:
-        end_pos = beginning_pos
-    elif not anchor_end:
-        # Remove the beginning from the chunk.
-        reversed_chunk = list(chunk_with_no_beginning)
-        reversed_chunk.reverse()
-        manyfound = False
-        for char in reversed_chunk:
-            end = char + end
-            if normalize_whitespace and char.isspace():
-                if end_for_regex[:2] != r'\s':
-                    end_for_regex = r'\s' + end_for_regex
-            else:
-                end_for_regex = re.escape(char) + end_for_regex
-            numfound = len(re.findall(end_for_regex, actual))
-            #numfound = actual.count(end)
-            if numfound == 0:
-                # None found this time around.  If we previously found more
-                # than one match, then choose the closest to the beginning.
-                if manyfound:
-                    end = manyfound_end
-                    end_for_regex = manyfound_end_for_regex
-                    end_pos = re.search(end_for_regex, actual).start()
-                    #end_pos = actual.find(end, beginning_pos)
-                    # XXX: ddaa 2005-03-25:
-                    #      This was wrong -- shouldn't be beginning_pos as
-                    #      we've already chopped off the beginning!
-                    #      Or is it?  We chopped the beginning of the chunk,
-                    #      not the actual stuff. So, using beginning_pos
-                    #      still holds. Need to chop that off and add on
-                    #      its length.
-                    break
-                else:
-                    return None
-            elif numfound == 1:
-                end_pos = re.search(end_for_regex, actual).start()
-                #end_pos = actual.rfind(end)
-                # XXX: ddaa 2005-03-25: Only one found, so why not use find()?
-                break
-            else:
-                manyfound = True
-                manyfound_end = end
-                manyfound_end_for_regex = end_for_regex
-        else:
-            if manyfound:
-                end_pos = re.search(end_for_regex, actual).start()
-            else:
-                return None
-    else:
-        end_pos = len(actual)
-        end = ''
-        end_for_regex = ''
-
-    chunk_equivalent = actual[beginning_pos:end_pos]
-    if show:
-        output = '[%s]%s[%s]' % (beginning, chunk_equivalent, end)
-    else:
-        output = '%s%s%s' % (beginning, chunk_equivalent, end)
-    # XXX: ddaa 2005-03-25: end_pos+1 is the end of chunk_equivalent, not end.
-    return (output, end_pos+1)

=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf	2011-02-03 03:49:36 +0000
+++ lib/canonical/config/schema-lazr.conf	2011-02-17 17:10:46 +0000
@@ -193,9 +193,6 @@
 
 [canonical]
 # datatype: boolean
-chunkydiff: True
-
-# datatype: boolean
 show_tracebacks: False
 
 # datatype: string

=== modified file 'lib/canonical/launchpad/database/message.py'
--- lib/canonical/launchpad/database/message.py	2011-02-10 01:18:39 +0000
+++ lib/canonical/launchpad/database/message.py	2011-02-17 17:10:46 +0000
@@ -61,7 +61,6 @@
 from canonical.database.datetimecol import UtcDateTimeCol
 from canonical.database.enumcol import EnumCol
 from canonical.database.sqlbase import SQLBase
-from canonical.encoding import guess as ensure_unicode
 from canonical.launchpad.helpers import get_filename_from_message_id
 from canonical.launchpad.interfaces.librarian import (
     ILibraryFileAliasSet,
@@ -83,6 +82,7 @@
     PersonCreationRationale,
     validate_public_person,
     )
+from lp.services.encoding import guess as ensure_unicode
 from lp.services.job.model.job import Job
 from lp.services.propertycache import cachedproperty
 

=== removed file 'lib/canonical/launchpad/doc/autodecorate.txt'
--- lib/canonical/launchpad/doc/autodecorate.txt	2009-03-27 03:29:31 +0000
+++ lib/canonical/launchpad/doc/autodecorate.txt	1970-01-01 00:00:00 +0000
@@ -1,39 +0,0 @@
-
-= AutoDecorate =
-
-AutoDecorate is a metaclass factory that can be used to make a class
-implicitely wrap all its methods with one or more decorators.
-
-    >>> def decorator_1(func):
-    ...     def decorated_1(*args, **kw):
-    ...         print 'Decorated 1'
-    ...         return func(*args, **kw)
-    ...     return decorated_1
-
-    >>> def decorator_2(func):
-    ...     def decorated_2(*args, **kw):
-    ...         print 'Decorated 2'
-    ...         return func(*args, **kw)
-    ...     return decorated_2
-
-    >>> from canonical.autodecorate import AutoDecorate
-
-    >>> class MyClass(object):
-    ...     __metaclass__ = AutoDecorate(decorator_1, decorator_2)
-    ...     def method_a(self):
-    ...         print 'Method A'
-    ...     def method_b(self):
-    ...         print 'Method B'
-
-    >>> obj = MyClass()
-
-    >>> obj.method_a()
-    Decorated 2
-    Decorated 1
-    Method A
-
-    >>> obj.method_b()
-    Decorated 2
-    Decorated 1
-    Method B
-

=== modified file 'lib/canonical/launchpad/doc/old-testing.txt'
--- lib/canonical/launchpad/doc/old-testing.txt	2010-12-24 09:28:21 +0000
+++ lib/canonical/launchpad/doc/old-testing.txt	2011-02-17 17:10:46 +0000
@@ -18,12 +18,6 @@
 zope, we should not be testing it with the full Z3 functional test
 harness).
 
-canonical.functional.FunctionalTestCase
----------------------------------------
-
-This is a customised zope3 FunctionalTestCase and should be used when you
-simply need the zope3 utilities etc available.
-
 PgTestSetup
 -----------
 

=== modified file 'lib/canonical/launchpad/doc/private-xmlrpc.txt'
--- lib/canonical/launchpad/doc/private-xmlrpc.txt	2010-10-09 16:36:22 +0000
+++ lib/canonical/launchpad/doc/private-xmlrpc.txt	2011-02-17 17:10:46 +0000
@@ -15,7 +15,7 @@
 external XML-RPC port.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> external_api = xmlrpclib.ServerProxy(
     ...    public_root + 'mailinglists/',
     ...    transport=XMLRPCTestTransport())

=== modified file 'lib/canonical/launchpad/doc/profiling.txt'
--- lib/canonical/launchpad/doc/profiling.txt	2010-10-22 10:24:18 +0000
+++ lib/canonical/launchpad/doc/profiling.txt	2011-02-17 17:10:46 +0000
@@ -166,8 +166,8 @@
 useful to try to figure out what requests are causing the memory usage of the
 server to increase.
 
-This is not blessed for production use at this time: the implementation
-relies on lib/canonical/mem.py, which as of this writing warns in its
+This is not blessed for production use at this time: the implementation relies
+on lib/lp/services/profile/mem.py, which as of this writing warns in its
 docstring that "[n]one of this should be in day-to-day use."  We should
 document the source of these concerns and evaluate them before using it in
 production.  Staging may be more acceptable.

=== modified file 'lib/canonical/launchpad/doc/xmlrpc-authserver.txt'
--- lib/canonical/launchpad/doc/xmlrpc-authserver.txt	2010-10-19 18:44:31 +0000
+++ lib/canonical/launchpad/doc/xmlrpc-authserver.txt	2011-02-17 17:10:46 +0000
@@ -28,7 +28,7 @@
 about users.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> authserver= xmlrpclib.ServerProxy(
     ...     'http://xmlrpc-private.launchpad.dev:8087/authserver',
     ...     transport=XMLRPCTestTransport())

=== modified file 'lib/canonical/launchpad/doc/xmlrpc-selftest.txt'
--- lib/canonical/launchpad/doc/xmlrpc-selftest.txt	2010-11-08 14:16:17 +0000
+++ lib/canonical/launchpad/doc/xmlrpc-selftest.txt	2011-02-17 17:10:46 +0000
@@ -16,7 +16,7 @@
 which talks with the publisher directly.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> selftest = xmlrpclib.ServerProxy(
     ...     'http://xmlrpc.launchpad.dev/', transport=XMLRPCTestTransport())
     >>> selftest.concatenate('foo', 'bar')

=== modified file 'lib/canonical/launchpad/ftests/test_system_documentation.py'
--- lib/canonical/launchpad/ftests/test_system_documentation.py	2010-12-13 15:25:03 +0000
+++ lib/canonical/launchpad/ftests/test_system_documentation.py	2011-02-17 17:10:46 +0000
@@ -194,10 +194,6 @@
     'old-testing.txt': LayeredDocFileSuite(
         '../doc/old-testing.txt', layer=FunctionalLayer),
 
-    'autodecorate.txt':
-        LayeredDocFileSuite('../doc/autodecorate.txt', layer=BaseLayer),
-
-
     # And this test want minimal environment too.
     'package-relationship.txt': LayeredDocFileSuite(
         '../doc/package-relationship.txt',

=== modified file 'lib/canonical/launchpad/helpers.py'
--- lib/canonical/launchpad/helpers.py	2010-12-02 16:13:51 +0000
+++ lib/canonical/launchpad/helpers.py	2011-02-17 17:10:46 +0000
@@ -20,16 +20,15 @@
 import tarfile
 import warnings
 
-import gettextpo
 from zope.component import getUtility
 from zope.security.interfaces import ForbiddenAttribute
 
-import canonical
 from canonical.launchpad.webapp.interfaces import ILaunchBag
 from lp.services.geoip.interfaces import (
     IRequestLocalLanguages,
     IRequestPreferredLanguages,
     )
+from lp.services.utils import compress_hash
 
 
 def text_replaced(text, replacements, _cache={}):
@@ -442,9 +441,7 @@
 
     It generates a file name that's not easily guessable.
     """
-    return '%s.msg' % (
-            canonical.base.base(
-                long(hashlib.sha1(message_id).hexdigest(), 16), 62))
+    return '%s.msg' % compress_hash(hashlib.sha1(message_id))
 
 
 def intOrZero(value):
@@ -499,7 +496,7 @@
     The templates are located in 'lib/canonical/launchpad/emailtemplates'.
     """
     if app is None:
-        base = os.path.dirname(canonical.launchpad.__file__)
+        base = os.path.dirname(__file__)
         fullpath = os.path.join(base, 'emailtemplates', filename)
     else:
         import lp

=== modified file 'lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt'
--- lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt	2010-10-18 22:24:59 +0000
+++ lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt	2011-02-17 17:10:46 +0000
@@ -4,7 +4,7 @@
 We can access them via XML-RPC:
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> lp_xmlrpc = xmlrpclib.ServerProxy(
     ...     'http://xmlrpc.launchpad.dev/+opstats',
     ...     transport=XMLRPCTestTransport()

=== modified file 'lib/canonical/launchpad/scripts/debsync.py'
--- lib/canonical/launchpad/scripts/debsync.py	2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/scripts/debsync.py	2011-02-17 17:10:46 +0000
@@ -17,7 +17,6 @@
 from zope.component import getUtility
 
 from canonical.database.sqlbase import flush_database_updates
-from canonical.encoding import guess as ensure_unicode
 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
 from canonical.launchpad.interfaces.message import (
     IMessageSet,
@@ -31,6 +30,7 @@
 from lp.bugs.interfaces.bugwatch import IBugWatchSet
 from lp.bugs.interfaces.cve import ICveSet
 from lp.bugs.scripts import debbugs
+from lp.services.encoding import guess as ensure_unicode
 
 
 def bug_filter(bug, previous_import_set, target_bugs, target_package_set,

=== modified file 'lib/canonical/launchpad/scripts/logger.py'
--- lib/canonical/launchpad/scripts/logger.py	2011-02-08 15:19:24 +0000
+++ lib/canonical/launchpad/scripts/logger.py	2011-02-17 17:10:46 +0000
@@ -50,7 +50,6 @@
 from zope.component import getUtility
 from zope.exceptions.log import Formatter
 
-from canonical.base import base
 from canonical.config import config
 from canonical.launchpad.webapp.errorlog import (
     globalErrorUtility,
@@ -61,6 +60,7 @@
     UploadFailed,
     )
 from lp.services.log import loglevels
+from lp.services.utils import compress_hash
 
 # Reexport our custom loglevels for old callsites. These callsites
 # should be importing the symbols from lp.services.log.loglevels
@@ -149,8 +149,7 @@
 
         expiry = datetime.now().replace(tzinfo=utc) + timedelta(days=90)
         try:
-            filename = base(long(
-                hashlib.sha1(traceback).hexdigest(), 16), 62) + '.txt'
+            filename = compress_hash(hashlib.sha1(traceback)) + '.txt'
             url = librarian.remoteAddFile(
                     filename, len(traceback), StringIO(traceback),
                     'text/plain;charset=%s' % sys.getdefaultencoding(),

=== modified file 'lib/canonical/launchpad/testing/pages.py'
--- lib/canonical/launchpad/testing/pages.py	2011-02-04 14:41:18 +0000
+++ lib/canonical/launchpad/testing/pages.py	2011-02-17 17:10:46 +0000
@@ -52,7 +52,6 @@
     )
 from canonical.launchpad.testing.systemdocs import (
     LayeredDocFileSuite,
-    SpecialOutputChecker,
     stop,
     strip_prefix,
     )
@@ -918,7 +917,7 @@
     unnumberedfilenames = sorted(unnumberedfilenames)
 
     suite = unittest.TestSuite()
-    checker = SpecialOutputChecker()
+    checker = doctest.OutputChecker()
     # Add unnumbered tests to the suite individually.
     if unnumberedfilenames:
         suite.addTest(LayeredDocFileSuite(

=== modified file 'lib/canonical/launchpad/testing/systemdocs.py'
--- lib/canonical/launchpad/testing/systemdocs.py	2010-11-08 12:52:43 +0000
+++ lib/canonical/launchpad/testing/systemdocs.py	2011-02-17 17:10:46 +0000
@@ -7,7 +7,6 @@
 __all__ = [
     'default_optionflags',
     'LayeredDocFileSuite',
-    'SpecialOutputChecker',
     'setUp',
     'setGlobs',
     'stop',
@@ -26,7 +25,6 @@
 from zope.component import getUtility
 from zope.testing.loggingsupport import Handler
 
-from canonical.chunkydiff import elided_source
 from canonical.config import config
 from canonical.database.sqlbase import flush_database_updates
 from canonical.launchpad.interfaces.launchpad import ILaunchBag
@@ -141,27 +139,6 @@
     return suite
 
 
-class SpecialOutputChecker(doctest.OutputChecker):
-    """An OutputChecker that runs the 'chunkydiff' checker if appropriate."""
-    def output_difference(self, example, got, optionflags):
-        if config.canonical.chunkydiff is False:
-            return doctest.OutputChecker.output_difference(
-                self, example, got, optionflags)
-
-        if optionflags & doctest.ELLIPSIS:
-            normalize_whitespace = optionflags & doctest.NORMALIZE_WHITESPACE
-            newgot = elided_source(example.want, got,
-                                   normalize_whitespace=normalize_whitespace)
-            if newgot == example.want:
-                # There was no difference.  May be an error in
-                # elided_source().  In any case, return the whole thing.
-                newgot = got
-        else:
-            newgot = got
-        return doctest.OutputChecker.output_difference(
-            self, example, newgot, optionflags)
-
-
 def ordered_dict_as_string(dict):
     """Return the contents of a dict as an ordered string.
 

=== removed file 'lib/canonical/launchpad/tests/test_chunkydiff_setting.py'
--- lib/canonical/launchpad/tests/test_chunkydiff_setting.py	2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/tests/test_chunkydiff_setting.py	1970-01-01 00:00:00 +0000
@@ -1,26 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Fail if the chunkydiff option is off.
-
-This ensures that people can't accidently commit the main config file with
-this option turned off to rocketfuel.
-"""
-__metaclass__ = type
-
-import unittest
-
-from canonical.config import config
-
-
-class TestChunkydiffSetting(unittest.TestCase):
-
-    def test(self):
-        self.failUnless(
-                config.canonical.chunkydiff is False,
-                'This test is failing to ensure that the chunkydiff '
-                'config setting cannot be committed in "on" mode.'
-                )
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/canonical/librarian/tests/test_upload.py'
--- lib/canonical/librarian/tests/test_upload.py	2009-06-25 05:30:52 +0000
+++ lib/canonical/librarian/tests/test_upload.py	2011-02-17 17:10:46 +0000
@@ -54,9 +54,6 @@
     """Librarian upload server test helper, process a request and report what
     happens.
 
-    Inspired by the canonical.functional.http function used by the Launchpad
-    page tests.
-
     Hands a request to a librarian file upload protocol, and prints the reply
     from the server, a summary of the file uploaded, and whether the connection
     closed, e.g.::

=== removed file 'lib/canonical/tests/chunkydiff.txt'
--- lib/canonical/tests/chunkydiff.txt	2005-10-31 18:29:12 +0000
+++ lib/canonical/tests/chunkydiff.txt	1970-01-01 00:00:00 +0000
@@ -1,202 +0,0 @@
-============
-Chunky diffs
-============
-
-  run this using
-
-    python test.py -u canonical.tests.test_chunkydiff
-
-Consider this desired output
-
-  nnnnnnABCxyzDEFnnnnnn
-
-and this actual output
-
-  nnnnnnABClmnopDEFnnnnnn
-
-the test says this
-
-  ...ABCxyzDEF...
-
-useful output would be
-
-  ...ABClmnopDEF...
-        ^^^^^
-
-and not
-
-  nnnnnnABClmnopDEFnnnnnn
-           ^^^^^
-
-How do we do this?
-
-If the comparison has failed, we take the first character after the
-ellipsis in the test, and look for that in the actual output.  If it occurs
-only once, then fine.  If it does not occur at all, then print out the whole
-diff.  If it occurs more than once, take the next character from the test,
-and look for those two characters in the actual output. Repeat until
-there is no occurence, or there is just one occurence.
-
-The same goes for the characters before the trailing ellipsis.
-
-The search can be narrowed because the characters at the end must follow
-those at the start.
-
-    >>> from canonical.chunkydiff import elided_source
-
-    >>> actual = "nnnnnnABClmnopDEFnnnnnn"
-    >>> tested = "...ABCxyzDEF..."
-    >>> elided_source(tested, actual)
-    '...ABClmnopDEF...'
-
-Trivial modification, so that the code needs to take the input into account
-and not just parrot out a hard-coded return value.
-
-    >>> actual = "nnnnnnABClmnopzDEFnnnnnn"
-    >>> tested = "...ABCxyzDEF..."
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEF...'
-
-No interesting output between the arbitrary markers.  This is really no
-different from the above, as far as the system is concerned.
-
-    >>> actual = "nnnnnnABCDEFnnnnnn"
-    >>> tested = "...ABCxyzDEF..."
-    >>> elided_source(tested, actual)
-    '...ABCDEF...'
-
-If there are two chunks that differ by the second or third characters in,
-choose the one that matches best.
-
-    >>> actual = "nnnnnnABClmnopzDEFnnnnnABXuuuuXEFnnnn"
-    >>> tested = "...ABCxyzDEF..."
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEF...'
-
-What happens when there is no ellipsis at the start?
-
-    >>> actual = "ABClmnopzDEFnnnn"
-    >>> tested = "ABCxyzDEF..."
-    >>> elided_source(tested, actual)
-    'ABClmnopzDEF...'
-
-What happens when there is no ellipsis at the end, but extra data at the end?
-
-    >>> actual = "ABClmnopzDEFnnnn"
-    >>> tested = "...ABCxyzDEF"
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEFnnnn'
-
-What happens when there is no ellipsis at the end?
-
-    >>> actual = "nnnnABClmnopzDEF"
-    >>> tested = "...ABCxyzDEF"
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEF'
-
-What happens when there is no ellipsis at all?
-
-    >>> actual = "ABClmnopzDEF"
-    >>> tested = "ABCxyzDEF"
-    >>> elided_source(tested, actual)
-    'ABClmnopzDEF'
-
-What happens when there is more than one chunk?
-
-    >>> actual = "nnnnnnABClmnopzDEFnnnnnGHIzponmJKLnnnn"
-    >>> tested = "...ABCxyzDEF...GHIxyzJKL..."
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEF...GHIzponmJKL...'
-
-What about when the chunks are presented in the wrong order?
-
-The first chunk from "tested" will have been found, but the second chunk
-will be absent.  We want to keep the "nnnn" at the end of 'actual' because
-it is not matched by anything.  We want to elide the start as it matches the
-elision, but not the end, as there is unmatched stuff in tested that we may
-want to compare.
-
-We may want to choose from among the following possible output in this case:
-
-'...GHIzponmJKLnnnn'
-'...GHIzponmJKL...'
-'...ABCmnopzDEF...GHIzponmJKL...'
-
-We'll use the first case for now, and see how it works in practice.
-
-Implementing this involves recognising how much of the actual string has
-been consumed by matching each chunk, and using only that remainder for the
-next chunks.
-
-    >>> actual = "nnnnnnABClmnopzDEFnnnnnGHIzponmJKLnnnn"
-    >>> tested = "...GHIzponmJKL...ABClmnopzDEF..."
-    >>> elided_source(tested, actual)
-    '...GHIzponmJKLnnnn'
-
-Where there is more than one option for the end match, choose the closest
-one.
-
-    >>> actual = "nnnnnnABClmnopzDEFnnnnnGHIzponmDEFnnnn"
-    >>> tested = "...ABCxxxxDEF..."
-    >>> elided_source(tested, actual)
-    '...ABClmnopzDEF...'
-
-Check anchoring to the start with elided junk after the first matched chunk.
-
-    >>> actual = "ABClmnopznnnDEFzponmGHInnnn"
-    >>> tested = "ABC...DEFxxxGHI..."
-    >>> elided_source(tested, actual)
-    'ABC...DEFzponmGHI...'
-
-Check anchoring to the end with elided junk immediately before.
-
-    >>> actual = "ABCDEzxcvbX"
-    >>> tested = "ABCDE...X"
-    >>> elided_source(tested, actual)
-    'ABCDE...X'
-
-Test single character within ellipses.
-
-    >>> actual = "abcdeXfghij"
-    >>> tested = "...X..."
-    >>> elided_source(tested, actual)
-    '...X...'
-
-Multiple single characters.
-
-    >>> actual = "ABCDEnnXnnXnnX"
-    >>> tested = "ABCDE...X"
-    >>> elided_source(tested, actual)
-    'ABCDE...X'
-
-
-    >>> actual = "ABCDEnnXnn"
-    >>> tested = "ABCDE...X..."
-    >>> elided_source(tested, actual)
-    'ABCDE...X...'
-
-Test with differences in whitespace.
-
-    >>> actual = "ABC\nxxx DEF"
-    >>> tested = "ABC ... DEF"
-    >>> elided_source(tested, actual)#xx
-    'ABC\nxxx ...DEF'
-
-    >>> actual = "ABC xxx DEF"
-    >>> tested = "ABC\n... DEF"
-    >>> elided_source(tested, actual, normalize_whitespace=True)
-    'ABC\n... DEF'
-
-Test with multiple whitespace characters.
-
-    >>> actual = "ABC xxx DEF"
-    >>> tested = "ABC\n\n... DEF"
-    >>> elided_source(tested, actual, normalize_whitespace=True)
-    'ABC\n\n... DEF'
-
-Test brad's case:
-
-    >>> actual = '\'Bug #7: "firefox crashes all the time" added\'\n'
-    >>> tested = "'...task added'\n"
-    >>> elided_source(tested, actual)
-    '\'...ttime" added\'\n'

=== removed file 'lib/canonical/tests/test_base.py'
--- lib/canonical/tests/test_base.py	2010-10-12 01:11:41 +0000
+++ lib/canonical/tests/test_base.py	1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-from doctest import DocTestSuite
-import canonical.base
-
-def test_suite():
-    suite = DocTestSuite(canonical.base)
-    return suite

=== removed file 'lib/canonical/tests/test_chunkydiff.py'
--- lib/canonical/tests/test_chunkydiff.py	2009-06-25 05:30:52 +0000
+++ lib/canonical/tests/test_chunkydiff.py	1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-from canonical.launchpad.testing.systemdocs import LayeredDocFileSuite
-
-
-def test_suite():
-    return LayeredDocFileSuite('chunkydiff.txt', stdout_logging=False)
-

=== modified file 'lib/lp/answers/doc/person.txt'
--- lib/lp/answers/doc/person.txt	2010-10-18 22:24:59 +0000
+++ lib/lp/answers/doc/person.txt	2011-02-17 17:10:46 +0000
@@ -169,7 +169,7 @@
 But Carlos has one.
 
     # Because not everyone uses a real editor <wink>
-    >>> from canonical.encoding import ascii_smash
+    >>> from lp.services.encoding import ascii_smash
     >>> carlos_raw = personset.getByName('carlos')
     >>> carlos = IQuestionsPerson(carlos_raw)
     >>> for question in carlos.searchQuestions(

=== modified file 'lib/lp/answers/doc/questionsets.txt'
--- lib/lp/answers/doc/questionsets.txt	2010-11-15 21:56:43 +0000
+++ lib/lp/answers/doc/questionsets.txt	2011-02-17 17:10:46 +0000
@@ -48,7 +48,7 @@
 regular full text algorithm.
 
     # Because not everyone uses a real editor <wink>
-    >>> from canonical.encoding import ascii_smash
+    >>> from lp.services.encoding import ascii_smash
     >>> for question in question_set.searchQuestions(search_text='firefox'):
     ...     print ascii_smash(question.title), question.target.displayname
     Problemas de Impressao no Firefox                Mozilla Firefox

=== modified file 'lib/lp/app/stories/launchpad-root/site-search.txt'
--- lib/lp/app/stories/launchpad-root/site-search.txt	2010-09-27 19:39:21 +0000
+++ lib/lp/app/stories/launchpad-root/site-search.txt	2011-02-17 17:10:46 +0000
@@ -5,7 +5,7 @@
 specific search with Launchpad's prominent objects (projects, bugs,
 teams, etc.).
 
-    >>> from canonical.encoding import ascii_smash
+    >>> from lp.services.encoding import ascii_smash
 
     # Our very helpful function for printing all the page results.
 

=== modified file 'lib/lp/archivepublisher/utils.py'
--- lib/lp/archivepublisher/utils.py	2011-02-04 09:07:36 +0000
+++ lib/lp/archivepublisher/utils.py	2011-02-17 17:10:46 +0000
@@ -31,7 +31,7 @@
     IStoreSelector,
     MAIN_STORE,
     )
-from canonical.mem import resident
+from lp.services.profile.mem import resident
 from lp.soyuz.enums import ArchivePurpose
 from lp.soyuz.interfaces.archive import (
     default_name_by_purpose,

=== modified file 'lib/lp/archiveuploader/dscfile.py'
--- lib/lp/archiveuploader/dscfile.py	2010-12-02 16:15:46 +0000
+++ lib/lp/archiveuploader/dscfile.py	2011-02-17 17:10:46 +0000
@@ -28,7 +28,7 @@
 from debian.deb822 import Deb822Dict
 from zope.component import getUtility
 
-from canonical.encoding import guess as guess_encoding
+from lp.services.encoding import guess as guess_encoding
 from canonical.launchpad.interfaces.gpghandler import (
     GPGVerificationError,
     IGPGHandler,

=== modified file 'lib/lp/archiveuploader/nascentuploadfile.py'
--- lib/lp/archiveuploader/nascentuploadfile.py	2010-10-19 09:00:29 +0000
+++ lib/lp/archiveuploader/nascentuploadfile.py	2011-02-17 17:10:46 +0000
@@ -30,7 +30,6 @@
 
 from zope.component import getUtility
 
-from canonical.encoding import guess as guess_encoding
 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
 from canonical.librarian.utils import filechunks
 from lp.app.errors import NotFoundError
@@ -47,10 +46,10 @@
     re_valid_version,
     )
 from lp.buildmaster.enums import BuildStatus
+from lp.services.encoding import guess as guess_encoding
 from lp.soyuz.enums import (
     BinaryPackageFormat,
     PackagePublishingPriority,
-    PackagePublishingStatus,
     PackageUploadCustomFormat,
     )
 from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet

=== modified file 'lib/lp/archiveuploader/utils.py'
--- lib/lp/archiveuploader/utils.py	2010-12-09 16:20:20 +0000
+++ lib/lp/archiveuploader/utils.py	2011-02-17 17:10:46 +0000
@@ -32,7 +32,7 @@
 import signal
 import subprocess
 
-from canonical.encoding import (
+from lp.services.encoding import (
     ascii_smash,
     guess as guess_encoding,
     )

=== modified file 'lib/lp/bugs/doc/bugtracker-tokens.txt'
--- lib/lp/bugs/doc/bugtracker-tokens.txt	2010-10-09 16:36:22 +0000
+++ lib/lp/bugs/doc/bugtracker-tokens.txt	2011-02-17 17:10:46 +0000
@@ -4,7 +4,7 @@
 
     >>> import xmlrpclib
     >>> from zope.component import getUtility
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
     >>> bugtracker_api = xmlrpclib.ServerProxy(
     ...     'http://xmlrpc-private.launchpad.dev:8087/bugs',

=== modified file 'lib/lp/bugs/doc/malone-xmlrpc.txt'
--- lib/lp/bugs/doc/malone-xmlrpc.txt	2010-10-19 18:44:31 +0000
+++ lib/lp/bugs/doc/malone-xmlrpc.txt	2011-02-17 17:10:46 +0000
@@ -3,7 +3,7 @@
 Malone provides an XML-RPC interface for filing bugs.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> filebug_api = xmlrpclib.ServerProxy(
     ...     'http://test@xxxxxxxxxxxxx:test@xxxxxxxxxxxxxxxxxxxx/bugs/',
     ...     transport=XMLRPCTestTransport())

=== modified file 'lib/lp/bugs/stories/bugtracker/xx-bugtracker-handshake-tokens.txt'
--- lib/lp/bugs/stories/bugtracker/xx-bugtracker-handshake-tokens.txt	2009-10-22 13:02:12 +0000
+++ lib/lp/bugs/stories/bugtracker/xx-bugtracker-handshake-tokens.txt	2011-02-17 17:10:46 +0000
@@ -5,7 +5,7 @@
 done using the internal XML-RPC service.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> bugtracker_api = xmlrpclib.ServerProxy(
     ...     'http://xmlrpc-private.launchpad.dev:8087/bugs',
     ...     transport=XMLRPCTestTransport())

=== modified file 'lib/lp/code/doc/branch-xmlrpc.txt'
--- lib/lp/code/doc/branch-xmlrpc.txt	2010-10-03 15:30:06 +0000
+++ lib/lp/code/doc/branch-xmlrpc.txt	2011-02-17 17:10:46 +0000
@@ -5,7 +5,7 @@
     >>> from datetime import datetime
     >>> import pytz
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> branchset_api = xmlrpclib.ServerProxy(
     ...     'http://foo.bar@xxxxxxxxxxxxx:test@xxxxxxxxxxxxxxxxxxxx/bazaar/',
     ...     transport=XMLRPCTestTransport())

=== modified file 'lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt'
--- lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt	2010-10-03 15:30:06 +0000
+++ lib/lp/code/doc/xmlrpc-codeimport-scheduler.txt	2011-02-17 17:10:46 +0000
@@ -44,7 +44,7 @@
 The point of all this is for it to be accessed over XMLRPC.
 
     >>> import xmlrpclib
-    >>> from canonical.functional import XMLRPCTestTransport
+    >>> from lp.testing.xmlrpc import XMLRPCTestTransport
     >>> codeimportscheduler = xmlrpclib.ServerProxy(
     ...     'http://xmlrpc-private.launchpad.dev:8087/codeimportscheduler',
     ...     transport=XMLRPCTestTransport())

=== modified file 'lib/lp/registry/tests/test_doc.py'
--- lib/lp/registry/tests/test_doc.py	2010-10-04 19:50:45 +0000
+++ lib/lp/registry/tests/test_doc.py	2011-02-17 17:10:46 +0000
@@ -51,7 +51,7 @@
     # Use a real XMLRPC server proxy so that the same test is run through the
     # full security machinery.  This is more representative of the real-world,
     # but more difficult to debug.
-    from canonical.functional import XMLRPCTestTransport
+    from lp.testing.xmlrpc import XMLRPCTestTransport
     from xmlrpclib import ServerProxy
     mailinglist_api = ServerProxy(
         'http://xmlrpc-private.launchpad.dev:8087/mailinglists/',

=== modified file 'lib/lp/registry/tests/test_xmlrpc.py'
--- lib/lp/registry/tests/test_xmlrpc.py	2010-10-20 20:51:26 +0000
+++ lib/lp/registry/tests/test_xmlrpc.py	2011-02-17 17:10:46 +0000
@@ -11,7 +11,6 @@
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from canonical.functional import XMLRPCTestTransport
 from canonical.launchpad.interfaces.account import AccountStatus
 from canonical.launchpad.interfaces.launchpad import IPrivateApplication
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
@@ -24,6 +23,7 @@
     )
 from lp.registry.xmlrpc.softwarecenteragent import SoftwareCenterAgentAPI
 from lp.testing import TestCaseWithFactory
+from lp.testing.xmlrpc import XMLRPCTestTransport
 
 
 class TestSoftwareCenterAgentAPI(TestCaseWithFactory):

=== modified file 'lib/lp/registry/xmlrpc/mailinglist.py'
--- lib/lp/registry/xmlrpc/mailinglist.py	2010-10-26 03:51:12 +0000
+++ lib/lp/registry/xmlrpc/mailinglist.py	2011-02-17 17:10:46 +0000
@@ -16,7 +16,7 @@
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
-from canonical.encoding import escape_nonascii_uniquely
+from lp.services.encoding import escape_nonascii_uniquely
 from canonical.launchpad.interfaces.emailaddress import (
     EmailAddressStatus,
     IEmailAddressSet,

=== renamed file 'lib/canonical/encoding.py' => 'lib/lp/services/encoding.py'
=== modified file 'lib/lp/services/memcache/tales.py'
--- lib/lp/services/memcache/tales.py	2010-12-13 18:04:24 +0000
+++ lib/lp/services/memcache/tales.py	2011-02-17 17:10:46 +0000
@@ -27,11 +27,11 @@
     )
 from zope.tales.interfaces import ITALESExpression
 
-from canonical.base import base
 from canonical.config import config
 from lp.app import versioninfo
 from canonical.launchpad.webapp.interfaces import ILaunchBag
 from lp.services.memcache.interfaces import IMemcacheClient
+from lp.services.utils import compress_hash
 
 # Request annotation key.
 COUNTER_KEY = 'lp.services.memcache.tales.counter'
@@ -238,7 +238,7 @@
         # with a hash. A short hash is good, provided it is still unique,
         # to preserve readability as much as possible. We include the
         # unsanitized URL in the hash to ensure uniqueness.
-        key_hash = base(int(md5(key + url).hexdigest(), 16), 62)
+        key_hash = compress_hash(md5(key + url))
         key = key[:250-len(key_hash)] + key_hash
 
         return key

=== modified file 'lib/lp/services/profile/__init__.py'
--- lib/lp/services/profile/__init__.py	2010-07-01 01:39:46 +0000
+++ lib/lp/services/profile/__init__.py	2011-02-17 17:10:46 +0000
@@ -1,7 +1,7 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-"""lp.services.profile - profiling for zope applications.
+"""Profiling for Python and Zope applications.
 
 Tests for this package are currently services stories.
 """

=== renamed file 'lib/canonical/mem.py' => 'lib/lp/services/profile/mem.py'
=== modified file 'lib/lp/services/profile/profile.py'
--- lib/lp/services/profile/profile.py	2010-10-22 10:24:18 +0000
+++ lib/lp/services/profile/profile.py	2011-02-17 17:10:46 +0000
@@ -26,7 +26,7 @@
 from canonical.config import config
 import canonical.launchpad.webapp.adapter as da
 from canonical.launchpad.webapp.interfaces import IStartRequestEvent
-from canonical.mem import (
+from lp.services.profile.mem import (
     memory,
     resident,
     )

=== renamed file 'lib/canonical/tests/test_encoding.py' => 'lib/lp/services/tests/test_encoding.py'
--- lib/canonical/tests/test_encoding.py	2010-10-12 01:11:41 +0000
+++ lib/lp/services/tests/test_encoding.py	2011-02-17 17:10:46 +0000
@@ -3,8 +3,8 @@
 
 from doctest import DocTestSuite, ELLIPSIS
 
-import canonical.encoding
+import lp.services.encoding
 
 def test_suite():
-    suite = DocTestSuite(canonical.encoding, optionflags=ELLIPSIS)
+    suite = DocTestSuite(lp.services.encoding, optionflags=ELLIPSIS)
     return suite

=== modified file 'lib/lp/services/tests/test_utils.py'
--- lib/lp/services/tests/test_utils.py	2011-02-09 10:59:00 +0000
+++ lib/lp/services/tests/test_utils.py	2011-02-17 17:10:46 +0000
@@ -1,15 +1,19 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-"""Module docstring goes here."""
+"""Tests for lp.services.utils."""
 
 __metaclass__ = type
 
 from contextlib import contextmanager
+import hashlib
 import itertools
 import unittest
 
 from lp.services.utils import (
+    AutoDecorate,
+    base,
+    compress_hash,
     CachingIterator,
     decorate_with,
     docstring_dedent,
@@ -19,6 +23,82 @@
 from lp.testing import TestCase
 
 
+
+class TestAutoDecorate(TestCase):
+    """Tests for AutoDecorate."""
+
+    def setUp(self):
+        super(TestAutoDecorate, self).setUp()
+        self.log = None
+
+    def decorator_1(self, f):
+        def decorated(*args, **kwargs):
+            self.log.append(1)
+            return f(*args, **kwargs)
+        return decorated
+
+    def decorator_2(self, f):
+        def decorated(*args, **kwargs):
+            self.log.append(2)
+            return f(*args, **kwargs)
+        return decorated
+
+    def test_auto_decorate(self):
+        # All of the decorators passed to AutoDecorate are applied as
+        # decorators in reverse order.
+
+        class AutoDecoratedClass:
+            __metaclass__ = AutoDecorate(self.decorator_1, self.decorator_2)
+            def method_a(s):
+                self.log.append('a')
+            def method_b(s):
+                self.log.append('b')
+
+        obj = AutoDecoratedClass()
+        self.log = []
+        obj.method_a()
+        self.assertEqual([2, 1, 'a'], self.log)
+        self.log = []
+        obj.method_b()
+        self.assertEqual([2, 1, 'b'], self.log)
+
+
+class TestBase(TestCase):
+
+    def test_simple_base(self):
+        # 35 in base 36 is lowercase 'z'
+        self.assertEqual('z', base(35, 36))
+
+    def test_extended_base(self):
+        # There is no standard representation for numbers in bases above 36
+        # (all the digits, all the letters of the English alphabet). However,
+        # we can represent bases up to 62 by using upper case letters on top
+        # of lower case letters. This is useful as a cheap compression
+        # algorithm.
+        self.assertEqual('A', base(36, 62))
+        self.assertEqual('B', base(37, 62))
+        self.assertEqual('Z', base(61, 62))
+
+    def test_base_matches_builtin_hex(self):
+        # We get identical results to the hex builtin, without the 0x prefix
+        numbers = list(range(5000))
+        using_hex = [hex(i)[2:] for i in numbers]
+        using_base = [base(i, 16) for i in numbers]
+        self.assertEqual(using_hex, using_base)
+
+    def test_compress_md5_hash(self):
+        # compress_hash compresses MD5 hashes down to 22 URL-safe characters.
+        compressed = compress_hash(hashlib.md5('foo'))
+        self.assertEqual('5fX649Stem9fET0lD46zVe', compressed)
+        self.assertEqual(22, len(compressed))
+
+    def test_compress_sha1_hash(self):
+        # compress_hash compresses SHA1 hashes down to 27 URL-safe characters.
+        compressed = compress_hash(hashlib.sha1('foo'))
+        self.assertEqual('1HyPQr2xj1nmnkQXBCJXUdQoy5l', compressed)
+        self.assertEqual(27, len(compressed))
+
+
 class TestIterateSplit(TestCase):
     """Tests for iter_split."""
 

=== modified file 'lib/lp/services/utils.py'
--- lib/lp/services/utils.py	2011-02-08 21:17:56 +0000
+++ lib/lp/services/utils.py	2011-02-17 17:10:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Generic Python utilities.
@@ -9,7 +9,10 @@
 
 __metaclass__ = type
 __all__ = [
+    'AutoDecorate',
+    'base',
     'CachingIterator',
+    'compress_hash',
     'decorate_with',
     'docstring_dedent',
     'iter_split',
@@ -20,14 +23,77 @@
     ]
 
 from itertools import tee
+import string
 import sys
 from textwrap import dedent
+from types import FunctionType
 
 from lazr.enum import BaseItem
 from twisted.python.util import mergeFunctionMetadata
 from zope.security.proxy import isinstance as zope_isinstance
 
 
+def AutoDecorate(*decorators):
+    """Factory to generate metaclasses that automatically apply decorators.
+
+    AutoDecorate is a metaclass factory that can be used to make a class
+    implicitly wrap all of its methods with one or more decorators.
+    """
+
+    class AutoDecorateMetaClass(type):
+        def __new__(cls, class_name, bases, class_dict):
+            new_class_dict = {}
+            for name, value in class_dict.items():
+                if type(value) == FunctionType:
+                    for decorator in decorators:
+                        value = decorator(value)
+                        assert callable(value), (
+                            "Decorator %s didn't return a callable."
+                            % repr(decorator))
+                new_class_dict[name] = value
+            return type.__new__(cls, class_name, bases, new_class_dict)
+
+    return AutoDecorateMetaClass
+
+
+def base(number, radix):
+    """Convert 'number' to an arbitrary base numbering scheme, 'radix'.
+
+    This function is based on work from the Python Cookbook and is under the
+    Python license.
+
+    Inverse function to int(str, radix) and long(str, radix)
+    """
+    if not 2 <= radix <= 62:
+        raise ValueError("radix must be between 2 and 62")
+
+    result = []
+    addon = result.append
+    if number < 0:
+        number = -number
+        addon('-')
+    elif number == 0:
+        addon('0')
+
+    ABC = string.digits + string.ascii_letters
+    while number:
+        number, rdigit = divmod(number, radix)
+        addon(ABC[rdigit])
+
+    result.reverse()
+    return ''.join(result)
+
+
+def compress_hash(hash_obj):
+    """Compress a hash_obj using `base`.
+
+    Given an ``md5`` or ``sha1`` hash object, compress it down to either 22 or
+    27 characters in a way that's safe to be used in URLs. Takes the hex of
+    the hash and converts it to base 62.
+    """
+    return base(int(hash_obj.hexdigest(), 16), 62)
+
+
 def iter_split(string, splitter):
     """Iterate over ways to split 'string' in two with 'splitter'.
 

=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py	2011-01-27 15:05:34 +0000
+++ lib/lp/soyuz/model/queue.py	2011-02-17 17:10:46 +0000
@@ -40,7 +40,7 @@
     SQLBase,
     sqlvalues,
     )
-from canonical.encoding import (
+from lp.services.encoding import (
     ascii_smash,
     guess as guess_encoding,
     )

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-02-16 11:18:48 +0000
+++ lib/lp/testing/factory.py	2011-02-17 17:10:46 +0000
@@ -61,7 +61,6 @@
     removeSecurityProxy,
     )
 
-from canonical.autodecorate import AutoDecorate
 from canonical.config import config
 from canonical.database.constants import (
     DEFAULT,
@@ -233,6 +232,7 @@
 from lp.services.mail.signedmessage import SignedMessage
 from lp.services.openid.model.openididentifier import OpenIdIdentifier
 from lp.services.propertycache import clear_property_cache
+from lp.services.utils import AutoDecorate
 from lp.services.worlddata.interfaces.country import ICountrySet
 from lp.services.worlddata.interfaces.language import ILanguageSet
 from lp.soyuz.adapters.packagelocation import PackageLocation

=== renamed file 'lib/canonical/functional.py' => 'lib/lp/testing/xmlrpc.py'
--- lib/canonical/functional.py	2009-06-25 05:30:52 +0000
+++ lib/lp/testing/xmlrpc.py	2011-02-17 17:10:46 +0000
@@ -1,7 +1,11 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-"""Alter the standard functional testing environment for Launchpad."""
+"""Tools for testing XML-RPC services."""
+
+__all__ = [
+    'XMLRPCTestTransport',
+    ]
 
 from cStringIO import StringIO
 import httplib

=== modified file 'lib/lp_sitecustomize.py'
--- lib/lp_sitecustomize.py	2010-12-24 13:03:02 +0000
+++ lib/lp_sitecustomize.py	2011-02-17 17:10:46 +0000
@@ -92,6 +92,15 @@
         "ignore",
         category=DeprecationWarning,
         module="Crypto")
+    # Filter all deprecation warnings for Zope 3.6, which eminate from
+    # the zope package.
+    filter_pattern = '.*(Zope 3.6|provide.*global site manager).*'
+    warnings.filterwarnings(
+        'ignore', filter_pattern, category=DeprecationWarning)
+    # XXX wgrant 2010-03-30 bug=551510:
+    # Also filter apt_pkg warnings, since Lucid's python-apt has a new API.
+    warnings.filterwarnings(
+        'ignore', '.*apt_pkg.*', category=DeprecationWarning)
 
 
 def customize_logger():

=== modified file 'utilities/migrater/file-ownership.txt'
--- utilities/migrater/file-ownership.txt	2010-11-25 04:42:51 +0000
+++ utilities/migrater/file-ownership.txt	2011-02-17 17:10:46 +0000
@@ -3846,7 +3846,6 @@
     ./tests/test_branchtarget.py
     ./tests/test_branchurifield.py
     ./tests/test_bugnotification.py
-    ./tests/test_chunkydiff_setting.py
     ./tests/test_datetimeutils.py
     ./tests/test_helpers.py
     ./tests/test_imports.py