-
Notifications
You must be signed in to change notification settings - Fork 21
ENT-211 add course key v2 #87
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
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
| 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. |
clintonb
left a comment
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.
| 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
CourseKeyV2ofOpaqueKeyfor handling course keys without run information.Usage: