-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENT-211 add course key v2 #87
base: master
Are you sure you want to change the base?
Conversation
…egagte course key
@cpennington Please let me know if this implementation is what you have suggested. |
5f565ee
to
b824779
Compare
opaque_keys/edx/locator.py
Outdated
""" | ||
The serialized course key without run information. | ||
""" | ||
return '{org}+{course}'.format(org=self.org, course=self.course) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you've implement _to_string
, you don't need serialize
. If you override serialize
like this, it's going to break the namespacing that's built into opaque keys, which will make it impossible to have new/alternate versions of AggregateCourseKeys
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the method serialize
opaque_keys/edx/locator.py
Outdated
@@ -1330,3 +1330,67 @@ def to_deprecated_list_repr(self): | |||
|
|||
# Register AssetLocator as the deprecated fallback for AssetKey | |||
AssetKey.set_deprecated_fallback(AssetLocator) | |||
|
|||
|
|||
class AggregateCourseLocator(AggregateCourseKey): # pylint: disable=abstract-method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What formats of key does this need to parse? Can you add some tests to make sure it parses/serializes the way you expect?
Also, should you be able to get an AggregateCourseKey
from a CourseKey
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpennington @clintonb The only format need is org+course
. I have added/update tests in opaque_keys/edx/tests/test_locators.py
for the AggregateCourseLocator
.
I was unable to directly get a AggregateCourseKey
locator from a CourseKey
locator mainly due to changed KEY_TYPE
and OpaqueKey
Hierarchy. To handle the conversion from CourseKey
to AggregateCourseKey
I have added a new method from_course_key
in AggregateCourseKey
which takes a course key object and returns a locator AggregateCourseLocator
from it. Please let me know your suggestion on this approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) I'm pretty skeptical about the claim that the only format needed is org+course
. We've been down this road before, and being able to version the key formats is quite useful over the long term. Please don't neglect that.
b) My suggestion would be to add a method to CourseKey
called get_aggregate_course_key
which would construct an appropriate AggregateCourseKey
object. That's consistent with how we've managed this in the past. You might also add something to AggregateCourseKey
that let's you construct a particular run CourseKey
by supplying the run_id. (This is all very much in parallel to the relationship between CourseKey
and UsageKey
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) Sorry I forgot to mention that the complete format of course key has version, e.g. course-v2:edX+DemoX
b) I have added the method make_course_key_v2
in CourseKey
which will return CourseKeyV2
locator. Also added the method make_course_run_key
in CourseKeyV2
which will return a CourseKey
locator CourseLocator
from the provided course run.
@cpennington thanks for the suggestions. Please have a look on the changes and let me know if I have missed anything.
…method to create aggregate course key from a course key object + add/update tests
opaque_keys/edx/keys.py
Outdated
An :class:`opaque_keys.OpaqueKey` identifying a | ||
serialized Course Key object. | ||
""" | ||
KEY_TYPE = 'aggregate_course_key' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpennington any reason (aside from confusion) not to call this course-v2
and use course-run-v1
to replace course-v1
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No technical issues that I can think of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we should do as part of this PR? We can certainly make the change but I don't want to introduce two new ideas at one time if we don't have to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would call this course-v2 now. Add a ticket to your backlog to add course-run-v1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpennington @clintonb I have to change the key type to course_key_v2
or just course_v2
because the CANONICAL_NAMESPACE
of AggregateCourseLocator
would be course-v2
? Also should I change the name of aggregate course key and locator names too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have change the CANONICAL_NAMESPACE
of AggregateCourseLocator
to course-v2
. Now the aggregate course key format would be course-v2:org+course
AggregateCourseKey.from_string('course-v2:edX+DemoX')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we agreed to get rid of the word aggregate. I have no clue what it means in this context.
opaque_keys/edx/locator.py
Outdated
|
||
""" | ||
super(AggregateCourseLocator, self).__init__( | ||
org=org, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: one line
opaque_keys/edx/locator.py
Outdated
) | ||
|
||
if self.org is None or self.course is None: | ||
raise InvalidKeyError(self.__class__, 'Both org and course should be set.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/should/must
opaque_keys/edx/locator.py
Outdated
KEY_FIELDS = ('org', 'course') | ||
__slots__ = KEY_FIELDS | ||
|
||
URL_RE_SOURCE = r'^(?P<org>{ALLOWED_ID_CHARS}+)\+(?P<course>{ALLOWED_ID_CHARS}+)$'.format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be a separate variable. Combine it with URL_RE
.
opaque_keys/edx/locator.py
Outdated
ALLOWED_ID_CHARS=Locator.ALLOWED_ID_CHARS | ||
) | ||
|
||
URL_RE = re.compile(URL_RE_SOURCE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give this a more appropriate name, KEY_REGEX
.
aggregate_course_locator = AggregateCourseLocator.from_course_key(course) | ||
|
||
aggregate_course_key = 'aggregate-course:{}+{}'.format(course.org, course.course) | ||
self.assertEqual(aggregate_course_locator, AggregateCourseKey.from_string(aggregate_course_key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of AggregateCourseKey.from_string
is confusing and unnecessary.
course_key = CourseKey.from_string(course_id)
expected = AggregateCourseKey(org=course_key.org, course=course_key.course)
actual = AggregateCourseKey.from_course_key(course_key)
assert expected == actual
aggregate_course_key = 'aggregate-course:{}+{}'.format(course.org, course.course) | ||
self.assertEqual(aggregate_course_locator, AggregateCourseKey.from_string(aggregate_course_key)) | ||
|
||
def test_aggregate_course_locator_serialize(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_serialize_to_string
|
||
def test_aggregate_course_locator_serialize(self): | ||
aggregate_course_key = 'aggregate-course:org+course' | ||
aggregate_course_locator = AggregateCourseKey.from_string(aggregate_course_key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not need to use AggregateCourseKey.from_string
to instantiate a new instance of AggregateCourseKey
. Fix this here, and in all other tests where it is used unnecessarily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@clintonb @cpennington This seems to be the documented way if you try to initialize the AggregateCourseKey
directly you get error:
>>> AggregateCourseKey(org=course_key.org, course=course_key.course)
TypeError: Can't instantiate abstract class AggregateCourseKey with abstract
methods _to_string, from_course_key
opaque_keys/edx/locator.py
Outdated
**kwargs | ||
) | ||
|
||
if self.org is None or self.course is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to account for empty string: if not (self.org and self.course)
opaque_keys/edx/locator.py
Outdated
""" | ||
Return a string representing this location. | ||
""" | ||
return u'{org}+{course}'.format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the u
.
@cpennington @clintonb please give another review. |
|
||
def test_aggregate_course_locator(self): | ||
""" | ||
Verify that the method "from_string" of class "AggregateCourseKey" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use ` instead of " for Python code (classes, methods).
opaque_keys/edx/keys.py
Outdated
An :class:`opaque_keys.OpaqueKey` identifying a | ||
serialized Course Key object. | ||
""" | ||
KEY_TYPE = 'aggregate_course_key' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we agreed to get rid of the word aggregate. I have no clue what it means in this context.
@clintonb @cpennington I have updated the naming. Please review. |
opaque_keys/edx/keys.py
Outdated
__slots__ = () | ||
|
||
@abstractproperty | ||
def from_course_key(self, course_key): # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call this from_course_run_key
. Eventually, course-key-v1 will be replaced with course-run-key-v1.
|
||
def test_course_locator_v2(self): | ||
""" | ||
Verify that the method "from_string" of class "CourseKeyV2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use ` instead of " to format Python entities. This should be fixed in all of your docstrings in this file.
self.assertEqual(expected_course_locator, course_locator_v2) | ||
|
||
@ddt.data( | ||
'org/course/run', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need for ddt
in this instance. Whether we use the old or new format for CourseKey
doesn't matter. The method simply expects an instance of CourseKey
.
course_key = CourseKey.from_string('course-v1:org+course+run') | ||
course_locator_v2 = CourseLocatorV2(org=course_key.org, course=course_key.course) | ||
expected_serialized_key = '{org}+{course}'.format(org=course_key.org, course=course_key.course) | ||
# pylint: disable=protected-access |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to call the protected member. Use str(course_locator_v2)
.
Verify that the method "_to_string" of class "CourseLocatorV2" | ||
serializes a course key v2 to a string with expected format. | ||
""" | ||
course_key = CourseKey.from_string('course-v1:org+course+run') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary.
Tests for :class:`.CourseLocatorV2` | ||
""" | ||
|
||
def test_course_locator_v2(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_from_string
'org/course/run', | ||
'course-v1:org+course+run', | ||
) | ||
def test_course_locator_v2_from_course_key(self, course_id): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to prefix your test methods with course_locator_v2
. The fact that CourseLocatorV2
is given by the name of the test class, CourseLocatorV2Tests
. This should simply be test_from_course_key
.
'org+course+run+foo', | ||
'course-v2:org+course+run', | ||
) | ||
def test_invalid_format_course_key(self, course_key): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_from_string_with_invalid_input
…lementedError as library locators are course agnostic
@clintonb Please have another look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was ready to approve this if the issues highlighted yesterday had been resolved. Now the API has been expanded in a manner that is not necessarily in scope or desired.
It would be ideal to limit the co-mingling of key types to avoid deprecation/cleanup issues down the road. I advocate for removing these new methods. If we actually need them in the future, we can add them. Today, however, there is no use case for them that cannot be met by existing methods.
@@ -61,6 +61,13 @@ def make_asset_key(self, asset_type, path): # pragma: no cover | |||
""" | |||
raise NotImplementedError() | |||
|
|||
@abstractmethod | |||
def make_course_key_v2(self): # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really necessary given that you already have CourseKeyV2.from_course_run_key()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cpennington @clintonb just to confirm, I have to remove this method only or do I need to remove the make_course_run_key
in class CourseKeyV2
too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zubair-arbi: You should have some a method to go from CourseKeyV2
to CourseKey
and back again, but only one of each. I would suggest using regular methods, rather than class methods, in general. So, probably CourseKeyV2.make_course_run_key
, and CourseKey.make_course_key_v2
.
raise NotImplementedError() | ||
|
||
@abstractproperty | ||
def make_course_run_key(self, course_run): # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this needed? We already have existing methods of creating course run keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were my suggestion. The existing keys typically are related by giving the more general key a way to create more specific keys by hydrating additional information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That said, I agree that it doesn't make much sense to have both methods. We should pick one, and stick to it.
I'm concerned about the names |
@asadiqbal08 @saleem-latif @mattdrayer
Add a new type
CourseKeyV2
ofOpaqueKey
for handling course keys without run information.Usage: