Skip to content

Commit 1e2bcfc

Browse files
author
George Schneeloch
committed
Added support for Python 3
1 parent cee052f commit 1e2bcfc

22 files changed

+249
-214
lines changed

.coveragerc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[run]
22
branch = True
3-
source = ./opaque_keys
3+
source = ./opaque_keys
4+
omit = ./.tox/*

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ David Baumgold <[email protected]>
88
Gabe Mulley <[email protected]>
99
Braden MacDonald <[email protected]>
1010
Clinton Blackburn <[email protected]>
11+
George Schneeloch <[email protected]>

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ requirements:
1313
pip install -r requirements.txt
1414

1515
test:
16-
coverage run -m nose
16+
tox

opaque_keys/__init__.py

+21-16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
from abc import ABCMeta, abstractmethod
1111
from functools import total_ordering
1212

13+
from six import (
14+
iteritems,
15+
python_2_unicode_compatible,
16+
text_type,
17+
viewkeys,
18+
viewitems,
19+
with_metaclass,
20+
)
1321
from stevedore.enabled import EnabledExtensionManager
1422

1523

@@ -34,8 +42,9 @@ def __new__(mcs, name, bases, attrs):
3442
return super(OpaqueKeyMetaclass, mcs).__new__(mcs, name, bases, attrs)
3543

3644

45+
@python_2_unicode_compatible
3746
@total_ordering
38-
class OpaqueKey(object):
47+
class OpaqueKey(with_metaclass(OpaqueKeyMetaclass)):
3948
"""
4049
A base-class for implementing pluggable opaque keys. Individual key subclasses identify
4150
particular types of resources, without specifying the actual form of the key (or
@@ -89,7 +98,6 @@ class constructor will not validate any of the ``KEY_FIELDS`` arguments, and wil
8998
Serialization of an :class:`OpaqueKey` is performed by using the :func:`unicode` builtin.
9099
Deserialization is performed by the :meth:`from_string` method.
91100
"""
92-
__metaclass__ = OpaqueKeyMetaclass
93101
__slots__ = ('_initialized', 'deprecated')
94102

95103
KEY_FIELDS = []
@@ -156,7 +164,7 @@ def _to_deprecated_string(self):
156164

157165
# ============= SERIALIZATION ==============
158166

159-
def __unicode__(self):
167+
def __str__(self):
160168
"""
161169
Serialize this :class:`OpaqueKey`, in the form ``<CANONICAL_NAMESPACE>:<value of _to_string>``.
162170
"""
@@ -265,13 +273,13 @@ def __init__(self, *args, **kwargs):
265273

266274
# a flag used to indicate that this instance was deserialized from the
267275
# deprecated form and should serialize to the deprecated form
268-
self.deprecated = kwargs.pop('deprecated', False)
276+
self.deprecated = kwargs.pop('deprecated', False) # pylint: disable=assigning-non-slot
269277

270278
if self.CHECKED_INIT:
271279
self._checked_init(*args, **kwargs)
272280
else:
273281
self._unchecked_init(**kwargs)
274-
self._initialized = True
282+
self._initialized = True # pylint: disable=assigning-non-slot
275283

276284
def _checked_init(self, *args, **kwargs):
277285
"""
@@ -285,13 +293,13 @@ def _checked_init(self, *args, **kwargs):
285293
))
286294

287295
keyed_args = dict(zip(self.KEY_FIELDS, args))
288-
overlapping_args = keyed_args.viewkeys() & kwargs.viewkeys()
296+
overlapping_args = viewkeys(keyed_args) & viewkeys(kwargs)
289297
if overlapping_args:
290298
raise TypeError('__init__() got multiple values for keyword argument {!r}'.format(overlapping_args[0]))
291299

292300
keyed_args.update(kwargs)
293301

294-
for key in keyed_args.viewkeys():
302+
for key in viewkeys(keyed_args):
295303
if key not in self.KEY_FIELDS:
296304
raise TypeError('__init__() got an unexpected argument {!r}'.format(key))
297305

@@ -301,7 +309,7 @@ def _unchecked_init(self, **kwargs):
301309
"""
302310
Set all kwargs as attributes.
303311
"""
304-
for key, value in kwargs.viewitems():
312+
for key, value in viewitems(kwargs):
305313
setattr(self, key, value)
306314

307315
def replace(self, **kwargs):
@@ -315,7 +323,7 @@ def replace(self, **kwargs):
315323
existing_values = {key: getattr(self, key) for key in self.KEY_FIELDS} # pylint: disable=no-member
316324
existing_values['deprecated'] = self.deprecated
317325

318-
if all(value == existing_values[key] for (key, value) in kwargs.iteritems()):
326+
if all(value == existing_values[key] for (key, value) in iteritems(kwargs)):
319327
return self
320328

321329
existing_values.update(kwargs)
@@ -325,7 +333,7 @@ def __setattr__(self, name, value):
325333
if getattr(self, '_initialized', False):
326334
raise AttributeError("Can't set {!r}. OpaqueKeys are immutable.".format(name))
327335

328-
super(OpaqueKey, self).__setattr__(name, value)
336+
super(OpaqueKey, self).__setattr__(name, value) # pylint: disable=no-member
329337

330338
def __delattr__(self, name):
331339
raise AttributeError("Can't delete {!r}. OpaqueKeys are immutable.".format(name))
@@ -348,8 +356,8 @@ def __setstate__(self, state_dict):
348356
for key in state_dict:
349357
if key in self.KEY_FIELDS: # pylint: disable=no-member
350358
setattr(self, key, state_dict[key])
351-
self.deprecated = state_dict['deprecated']
352-
self._initialized = True
359+
self.deprecated = state_dict['deprecated'] # pylint: disable=assigning-non-slot
360+
self._initialized = True # pylint: disable=assigning-non-slot
353361

354362
def __getstate__(self):
355363
# used by pickle to get fields on an unpickled object
@@ -380,9 +388,6 @@ def __lt__(self, other):
380388
def __hash__(self):
381389
return hash(self._key)
382390

383-
def __str__(self):
384-
return unicode(self).encode('utf-8')
385-
386391
def __repr__(self):
387392
return '{}({})'.format(
388393
self.__class__.__name__,
@@ -391,4 +396,4 @@ def __repr__(self):
391396

392397
def __len__(self):
393398
"""Return the number of characters in the serialized OpaqueKey"""
394-
return len(unicode(self))
399+
return len(text_type(self))

opaque_keys/edx/asides.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
store scoped data alongside the definition and usage of the particular XBlock usage that they're
1313
commenting on.
1414
"""
15+
from six import text_type
1516

1617
from opaque_keys.edx.keys import AsideDefinitionKey, AsideUsageKey, DefinitionKey, UsageKey
1718

@@ -90,7 +91,7 @@ def _to_string(self):
9091
9192
This serialization should not include the namespace prefix.
9293
"""
93-
return u'{}::{}'.format(_encode(unicode(self.definition_key)), _encode(unicode(self.aside_type)))
94+
return u'{}::{}'.format(_encode(text_type(self.definition_key)), _encode(text_type(self.aside_type)))
9495

9596

9697
class AsideUsageKeyV1(AsideUsageKey): # pylint: disable=abstract-method
@@ -179,4 +180,4 @@ def _to_string(self):
179180
180181
This serialization should not include the namespace prefix.
181182
"""
182-
return u'{}::{}'.format(_encode(unicode(self.usage_key)), _encode(unicode(self.aside_type)))
183+
return u'{}::{}'.format(_encode(text_type(self.usage_key)), _encode(text_type(self.aside_type)))

opaque_keys/edx/keys.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
import json
66
from abc import abstractmethod, abstractproperty
7+
from six import text_type
78

89
from opaque_keys import OpaqueKey
910

@@ -207,7 +208,7 @@ class i4xEncoder(json.JSONEncoder): # pylint: disable=invalid-name
207208
"""
208209
def default(self, key): # pylint: disable=method-hidden
209210
if isinstance(key, OpaqueKey):
210-
return unicode(key)
211+
return text_type(key)
211212
super(i4xEncoder, self).default(key)
212213

213214

opaque_keys/edx/locator.py

+17-31
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import re
1010
import warnings
1111
from abc import abstractproperty
12+
from six import string_types, text_type
1213

1314
from bson.errors import InvalidId
1415
from bson.objectid import ObjectId
@@ -45,12 +46,6 @@ class Locator(OpaqueKey):
4546
ALLOWED_ID_CHARS = r'[\w\-~.:]'
4647
DEPRECATED_ALLOWED_ID_CHARS = r'[\w\-~.:%]'
4748

48-
def __str__(self):
49-
"""
50-
str(self) returns something like this: "mit.eecs.6002x"
51-
"""
52-
return unicode(self).encode('utf-8')
53-
5449
@abstractproperty
5550
def version(self): # pragma: no cover
5651
"""
@@ -159,9 +154,6 @@ class CourseLocator(BlockLocatorBase, CourseKey): # pylint: disable=abstract-m
159154
# Characters that are forbidden in the deprecated format
160155
INVALID_CHARS_DEPRECATED = re.compile(r"[^\w.%-]", re.UNICODE)
161156

162-
# stubs to fake out the abstractproperty class instrospection and allow treatment as attrs in instances
163-
org = None
164-
165157
def __init__(self, org=None, course=None, run=None, branch=None, version_guid=None, deprecated=False, **kwargs):
166158
"""
167159
Construct a CourseLocator
@@ -223,7 +215,7 @@ def __init__(self, org=None, course=None, run=None, branch=None, version_guid=No
223215
def _check_location_part(cls, val, regexp): # pylint: disable=missing-docstring
224216
if val is None:
225217
return
226-
if not isinstance(val, basestring):
218+
if not isinstance(val, string_types):
227219
raise InvalidKeyError(cls, "{!r} is not a string".format(val))
228220
if regexp.search(val) is not None:
229221
raise InvalidKeyError(cls, "Invalid characters in {!r}.".format(val))
@@ -278,7 +270,7 @@ def html_id(self):
278270
place, but I have no way to override. We should clearly define the purpose and restrictions of this
279271
(e.g., I'm assuming periods are fine).
280272
"""
281-
return unicode(self)
273+
return text_type(self)
282274

283275
def make_usage_key(self, block_type, block_id):
284276
return BlockUsageLocator(
@@ -367,7 +359,7 @@ def to_deprecated_string(self):
367359
DeprecationWarning,
368360
stacklevel=2
369361
)
370-
return unicode(self)
362+
return text_type(self)
371363

372364
@classmethod
373365
def _from_deprecated_string(cls, serialized):
@@ -421,12 +413,6 @@ class LibraryLocator(BlockLocatorBase, CourseKey):
421413
__slots__ = KEY_FIELDS
422414
CHECKED_INIT = False
423415

424-
# declare our fields explicitly to avoid pylint warnings
425-
org = None
426-
library = None
427-
branch = None
428-
version_guid = None
429-
430416
def __init__(self, org=None, library=None, branch=None, version_guid=None, **kwargs):
431417
"""
432418
Construct a LibraryLocator
@@ -472,7 +458,7 @@ def __init__(self, org=None, library=None, branch=None, version_guid=None, **kwa
472458
**kwargs
473459
)
474460

475-
if self.version_guid is None and (self.org is None or self.library is None):
461+
if self.version_guid is None and (self.org is None or self.library is None): # pylint: disable=no-member
476462
raise InvalidKeyError(self.__class__, "Either version_guid or org and library should be set")
477463

478464
@property
@@ -489,7 +475,7 @@ def course(self):
489475
Deprecated. Return a 'course' for compatibility with CourseLocator.
490476
"""
491477
warnings.warn("Accessing 'course' on a LibraryLocator is deprecated.", DeprecationWarning, stacklevel=2)
492-
return self.library
478+
return self.library # pylint: disable=no-member
493479

494480
@property
495481
def version(self):
@@ -502,7 +488,7 @@ def version(self):
502488
DeprecationWarning,
503489
stacklevel=2
504490
)
505-
return self.version_guid
491+
return self.version_guid # pylint: disable=no-member
506492

507493
@classmethod
508494
def _from_string(cls, serialized):
@@ -526,7 +512,7 @@ def html_id(self):
526512
"""
527513
Return an id which can be used on an html page as an id attr of an html element.
528514
"""
529-
return unicode(self)
515+
return text_type(self)
530516

531517
def make_usage_key(self, block_type, block_id):
532518
return LibraryUsageLocator(
@@ -579,12 +565,12 @@ def _to_string(self):
579565
Return a string representing this location.
580566
"""
581567
parts = []
582-
if self.library:
568+
if self.library: # pylint: disable=no-member
583569
parts.extend([self.org, self.course])
584-
if self.branch:
585-
parts.append(u"{prefix}@{branch}".format(prefix=self.BRANCH_PREFIX, branch=self.branch))
586-
if self.version_guid:
587-
parts.append(u"{prefix}@{guid}".format(prefix=self.VERSION_PREFIX, guid=self.version_guid))
570+
if self.branch: # pylint: disable=no-member
571+
parts.append(u"{prefix}@{branch}".format(prefix=self.BRANCH_PREFIX, branch=self.branch)) # pylint: disable=no-member
572+
if self.version_guid: # pylint: disable=no-member
573+
parts.append(u"{prefix}@{guid}".format(prefix=self.VERSION_PREFIX, guid=self.version_guid)) # pylint: disable=no-member
588574
return u"+".join(parts)
589575

590576
def _to_deprecated_string(self):
@@ -977,7 +963,7 @@ def to_deprecated_string(self):
977963
DeprecationWarning,
978964
stacklevel=2
979965
)
980-
return unicode(self)
966+
return text_type(self)
981967

982968
@classmethod
983969
def _from_deprecated_string(cls, serialized):
@@ -1176,7 +1162,7 @@ class DefinitionLocator(Locator, DefinitionKey):
11761162
definition_id = None
11771163

11781164
def __init__(self, block_type, definition_id, deprecated=False): # pylint: disable=unused-argument
1179-
if isinstance(definition_id, basestring):
1165+
if isinstance(definition_id, string_types):
11801166
try:
11811167
definition_id = self.as_object_id(definition_id)
11821168
except ValueError:
@@ -1188,7 +1174,7 @@ def _to_string(self):
11881174
Return a string representing this location.
11891175
unicode(self) returns something like this: "519665f6223ebd6980884f2b+type+problem"
11901176
"""
1191-
return u"{}+{}@{}".format(unicode(self.definition_id), self.BLOCK_TYPE_PREFIX, self.block_type)
1177+
return u"{}+{}@{}".format(text_type(self.definition_id), self.BLOCK_TYPE_PREFIX, self.block_type)
11921178

11931179
URL_RE = re.compile(
11941180
r"^(?P<definition_id>[A-F0-9]+)\+{}@(?P<block_type>{ALLOWED_ID_CHARS}+)$".format(
@@ -1298,7 +1284,7 @@ def to_deprecated_string(self):
12981284
DeprecationWarning,
12991285
stacklevel=2
13001286
)
1301-
return unicode(self)
1287+
return text_type(self)
13021288

13031289
@property
13041290
def tag(self):

opaque_keys/edx/tests/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ def assertDeprecationWarning(self, count=1):
2626
"""Asserts that the contained code raises `count` deprecation warnings"""
2727
with warnings.catch_warnings(record=True) as caught:
2828
yield
29-
self.assertEquals(count,
30-
len([warning for warning in caught if issubclass(warning.category, DeprecationWarning)]))
29+
self.assertEqual(count,
30+
len([warning for warning in caught if issubclass(warning.category, DeprecationWarning)]))
3131

3232

3333
class LocatorBaseTest(TestCase):

0 commit comments

Comments
 (0)