learning_sequence doc tweaks and new type annotation style (#27311)

* docs: Add a statement comment. Update learning sequences README.

* refactor: Add Python3.6+-style type annotations to attrs classes where possible.
This commit is contained in:
Julia Eskew
2021-04-14 11:12:01 -04:00
committed by GitHub
parent 031d99cb95
commit 7dbad0bd9a
3 changed files with 53 additions and 59 deletions

View File

@@ -67,6 +67,7 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
# import here, because signal is registered at startup, but items in tasks are not yet able to be loaded
from cms.djangoapps.contentstore.tasks import update_outline_from_modulestore_task, update_search_index
if key_supports_outlines(course_key):
# Push the course outline to learning_sequences asynchronously.
update_outline_from_modulestore_task.delay(str(course_key))
# Finally call into the course search subsystem

View File

@@ -9,17 +9,12 @@ users through the LMS, though it is also available to Studio for pushing data
into the system. The first API this app implements is computing the Course
Outline.
.. important::
This package should _not_ depend on the modulestore directly.
---------------
Direction: Keep
---------------
This package is being actively developed, but in a very early state. We're not
going to start feeding data into it (by triggering off of publish) until it's a
little more complete, so that it's easier to make drastic changes to the data
model if necessary. During development, you can seed data into it by using the
``update_course_outline`` management command.
The primary path which feeds course outline data into learning sequence models
is a signal handler upon a Studio course publish. However, you can also seed
data into it by using the ``update_course_outline`` management command.
-----
Usage
@@ -49,7 +44,7 @@ rules will have to be reimplmentedas we've already seen with mobile APIs?
a single sequence.
3. Block Transformers are extremely powerful, but also complex and slow.
Optimizing them to suit our use case would be a lot of work and result in
even more complexity. Sequence and outline related metadata is much smaller,
even more complexity. Sequence- and outline-related metadata is much smaller,
and we can make simplifying assumptions like treating the outline as a tree
and not a DAG (i.e. each Sequence being in only one Section).
@@ -59,7 +54,8 @@ How to Extend?
This app is experimenting with some new conventions, so please read the decision
docs (``docs/decisions``). Many of the modules also have a long top-level
docstring explaining what they should be used forplease read these. Many of the
docstring explaining for what they should be used please read these docstrings.
Many of the
conventions are there to promote predictable behavior and are dramatically less
effective if broken even in little ways.
@@ -71,7 +67,7 @@ The public data types are in ``api/data.py``. Database persistence is in
dumb. All real business logic should happen in a module within the ``api``
package. Currently, all the existing API logic is handled in ``api/outlines.py``
and re-exported at the top level in ``api/__init__.py``. If your new
functionality is outlines related, please follow this convention. Otherwise, you
functionality is outline-related, please follow this convention. Otherwise, you
can create a new module in ``api/`` (e.g. ``api/sequences.py``) to hold your
logic, and re-export that via the top level ``api/__init__.py``.
@@ -79,8 +75,8 @@ I want to add a new rule affecting how sequences show up in the outline.
========================================================================
You probably want to create or modify an OutlineProcessor (``api/processors``).
This is not yet a pluggable interface, though it was designed to make it easy to
turn into one. Please see the docstrings in ``api/processors/base.py`` for
This interface is not yet pluggable, though it was designed to make it easy to
become pluggable. Please see the docstrings in ``api/processors/base.py`` for
details on how to write one.
I want to pull data from ModuleStore or Block Structures.

View File

@@ -18,9 +18,6 @@ Guidelines:
or use API functions from other apps. They should not trigger expensive
computation.
Note: we're using old-style syntax for attrs because we need to support Python
3.5, but we can move to the PEP-526 style once we move to Python 3.6+.
TODO: Validate all datetimes to be UTC.
"""
import logging
@@ -50,7 +47,7 @@ class ObjectDoesNotExist(Exception):
pass # lint-amnesty, pylint: disable=unnecessary-pass
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class ContentErrorData:
"""
A human-readable description of something wrong with the content, to ease
@@ -62,11 +59,11 @@ class ContentErrorData:
when things don't show up where we expect then to be and we omit them from
the outline (unknown tag types, sequences where we expect sections, etc.)
"""
message = attr.ib(type=str)
usage_key = attr.ib(type=Optional[UsageKey], default=None)
message: str
usage_key: Optional[UsageKey] = None
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class VisibilityData:
"""
XBlock attributes that help determine item visibility.
@@ -75,29 +72,29 @@ class VisibilityData:
# lets you define a Sequence that is reachable by direct URL but not shown
# in Course navigation. It was used for things like supplementary tutorials
# that were not considered a part of the normal course path.
hide_from_toc = attr.ib(type=bool, default=False)
hide_from_toc: bool = False
# Restrict visibility to course staff, regardless of start date. This is
# often used to hide content that either still being built out, or is a
# scratch space of content that will eventually be copied over to other
# sequences.
visible_to_staff_only = attr.ib(type=bool, default=False)
visible_to_staff_only: bool = False
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class ExamData:
"""
XBlock attributes that describe exams
"""
is_practice_exam = attr.ib(type=bool, default=False)
is_proctored_enabled = attr.ib(type=bool, default=False)
is_time_limited = attr.ib(type=bool, default=False)
is_practice_exam: bool = False
is_proctored_enabled: bool = False
is_time_limited: bool = False
def __bool__(self):
return self.is_practice_exam or self.is_proctored_enabled or self.is_time_limited
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class CourseLearningSequenceData:
"""
A Learning Sequence (a.k.a. subsection) from a Course.
@@ -107,22 +104,22 @@ class CourseLearningSequenceData:
learning sequences in Courses vs. Pathways vs. Libraries. Such an object
would likely not have `visibility` as that holds course-specific concepts.
"""
usage_key = attr.ib(type=UsageKey)
title = attr.ib(type=str)
visibility = attr.ib(type=VisibilityData, default=VisibilityData())
exam = attr.ib(type=ExamData, default=ExamData())
inaccessible_after_due = attr.ib(type=bool, default=False)
usage_key: UsageKey
title: str
visibility: VisibilityData = VisibilityData()
exam: ExamData = ExamData()
inaccessible_after_due: bool = False
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class CourseSectionData:
"""
A Section in a Course (sometimes called a Chapter).
"""
usage_key = attr.ib(type=UsageKey)
title = attr.ib(type=str)
visibility = attr.ib(type=VisibilityData)
sequences = attr.ib(type=List[CourseLearningSequenceData])
usage_key: UsageKey
title: str
visibility: VisibilityData
sequences: List[CourseLearningSequenceData]
@attr.s(frozen=True)
@@ -247,41 +244,41 @@ class CourseOutlineData:
)
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class ScheduleItemData:
"""
Scheduling specific data (start/end/due dates) for a single item.
"""
usage_key = attr.ib(type=UsageKey)
usage_key: UsageKey
# Start date that is specified for this item
start = attr.ib(type=Optional[datetime])
start: Optional[datetime]
# Effective release date that it's available (may be affected by parents)
effective_start = attr.ib(type=Optional[datetime])
due = attr.ib(type=Optional[datetime])
effective_start: Optional[datetime]
due: Optional[datetime]
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class ScheduleData:
"""
Overall course schedule data.
"""
course_start = attr.ib(type=Optional[datetime])
course_end = attr.ib(type=Optional[datetime])
sections = attr.ib(type=Dict[UsageKey, ScheduleItemData])
sequences = attr.ib(type=Dict[UsageKey, ScheduleItemData])
course_start: Optional[datetime]
course_end: Optional[datetime]
sections: Dict[UsageKey, ScheduleItemData]
sequences: Dict[UsageKey, ScheduleItemData]
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class SpecialExamAttemptData:
"""
Overall special exam attempt data.
"""
sequences = attr.ib(type=Dict[UsageKey, Dict])
sequences: Dict[UsageKey, Dict]
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class UserCourseOutlineData(CourseOutlineData):
"""
A course outline that has been customized for a specific user and time.
@@ -298,16 +295,16 @@ class UserCourseOutlineData(CourseOutlineData):
# to reach up into parts of a Course that the user is not normally allowed
# to know the existence of (e.g. Sequences marked `visible_to_staff_only`),
# we can use this attribute.
base_outline = attr.ib(type=CourseOutlineData)
base_outline: CourseOutlineData
# Django User representing who we've customized this outline for. This may
# be the AnonymousUser.
user = attr.ib(type=User)
user: User
# UTC TZ time representing the time for which this user course outline was
# created. It is possible to create UserCourseOutlineData for a time in the
# future (i.e. "What will this user be able to see next week?")
at_time = attr.ib(type=datetime)
at_time: datetime
# What Sequences is this `user` allowed to access? Anything in the `outline`
# is something that the `user` is allowed to know exists, but they might not
@@ -317,15 +314,15 @@ class UserCourseOutlineData(CourseOutlineData):
# * If anonymous course access is enabled in "public_outline" mode,
# unauthenticated users (AnonymousUser) will see the course outline but
# not be able to access anything inside.
accessible_sequences = attr.ib(type=Set[UsageKey])
accessible_sequences: Set[UsageKey]
@attr.s(frozen=True)
@attr.s(frozen=True, auto_attribs=True)
class UserCourseOutlineDetailsData:
"""
Class that has a user's course outline plus useful details (like schedules).
Will eventually expand to include other systems like Completion.
"""
outline = attr.ib(type=UserCourseOutlineData)
schedule = attr.ib(type=ScheduleData)
special_exam_attempts = attr.ib(type=SpecialExamAttemptData)
outline: UserCourseOutlineData
schedule: ScheduleData
special_exam_attempts: SpecialExamAttemptData