Skip to content

Commit

Permalink
🎨(models) clean xAPI pydantic models naming convention
Browse files Browse the repository at this point in the history
The naming convention used in xAPI Pydantic modelisation was confusing for
users. Furthermore, some models are being cross used between several profiles.
A new naming convention has been applied for more clarity and to factorize
common vocabulary usable in all profiles.
  • Loading branch information
quitterie-lcs committed May 24, 2023
1 parent 8af6a84 commit 0d1a748
Show file tree
Hide file tree
Showing 79 changed files with 2,263 additions and 1,405 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Changed

- Clean xAPI pydantic models naming convention
- Upgrade `fastapi` to `0.95.2`
- Upgrade `sentry_sdk` to `1.23.1`
- Upgrade `httpx` to `0.24.1`
Expand Down
7 changes: 4 additions & 3 deletions src/ralph/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
Allows to be exactly as lax as we want when it comes to exact object shape and
validation.
"""
from typing import Optional
from typing import Optional, Union
from uuid import UUID

from pydantic import AnyUrl, BaseModel, Extra

from ..models.xapi.fields.actors import ActorField
from ..models.xapi.base.agents import BaseXapiAgent
from ..models.xapi.base.groups import BaseXapiGroup


class ErrorDetail(BaseModel):
Expand Down Expand Up @@ -62,7 +63,7 @@ class LaxStatement(BaseModelWithLaxConfig):
qualify an object as an XAPI statement.
"""

actor: ActorField
actor: Union[BaseXapiAgent, BaseXapiGroup]
id: Optional[UUID]
object: LaxObjectField
verb: LaxVerbField
22 changes: 11 additions & 11 deletions src/ralph/api/routers/statements.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
from ralph.backends.database.base import BaseDatabase, StatementParameters
from ralph.conf import settings
from ralph.exceptions import BackendException, BadFormatException
from ralph.models.xapi.fields.actors import (
AccountActorField,
AgentActorField,
MboxActorField,
MboxSha1SumActorField,
OpenIdActorField,
from ralph.models.xapi.base.agents import (
BaseXapiAgent,
BaseXapiAgentWithAccountIFI,
BaseXapiAgentWithMboxIFI,
BaseXapiAgentWithMboxSha1SumIFI,
BaseXapiAgentWithOpenIdIFI,
)

from ..auth import authenticated_user
Expand Down Expand Up @@ -240,17 +240,17 @@ async def get(
query_params = dict(request.query_params)
if query_params.get("agent") is not None:
# Transform agent to `dict` as FastAPI cannot parse JSON (seen as string)
agent = parse_raw_as(AgentActorField, query_params["agent"])
agent = parse_raw_as(BaseXapiAgent, query_params["agent"])

query_params.pop("agent")

if isinstance(agent, MboxActorField):
if isinstance(agent, BaseXapiAgentWithMboxIFI):
query_params["agent__mbox"] = agent.mbox
elif isinstance(agent, MboxSha1SumActorField):
elif isinstance(agent, BaseXapiAgentWithMboxSha1SumIFI):
query_params["agent__mbox_sha1sum"] = agent.mbox_sha1sum
elif isinstance(agent, OpenIdActorField):
elif isinstance(agent, BaseXapiAgentWithOpenIdIFI):
query_params["agent__openid"] = agent.openid
elif isinstance(agent, AccountActorField):
elif isinstance(agent, BaseXapiAgentWithAccountIFI):
query_params["agent__account__name"] = agent.account.name
query_params["agent__account__home_page"] = agent.account.homePage

Expand Down
16 changes: 9 additions & 7 deletions src/ralph/models/edx/converters/xapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

from ralph.exceptions import ConfigurationException
from ralph.models.converter import BaseConversionSet, ConversionItem
from ralph.models.xapi.constants import (
EXTENSION_COURSE_ID,
EXTENSION_MODULE_ID,
EXTENSION_SCHOOL_ID,
from ralph.models.xapi.concepts.constants.acrossx_profile import (
CONTEXT_EXTENSION_SCHOOL_ID,
)
from ralph.models.xapi.concepts.constants.scorm_profile import (
CONTEXT_EXTENSION_COURSE_ID,
CONTEXT_EXTENSION_MODULE_ID,
)


Expand Down Expand Up @@ -51,16 +53,16 @@ def _get_conversion_items(self):
lambda user_id: str(user_id) if user_id else "anonymous",
),
ConversionItem(
"object__definition__extensions__" + EXTENSION_SCHOOL_ID,
"object__definition__extensions__" + CONTEXT_EXTENSION_SCHOOL_ID,
"context__org_id",
),
ConversionItem(
"object__definition__extensions__" + EXTENSION_COURSE_ID,
"object__definition__extensions__" + CONTEXT_EXTENSION_COURSE_ID,
"context__course_id",
(self.parse_course_id, lambda x: x["course"]),
),
ConversionItem(
"object__definition__extensions__" + EXTENSION_MODULE_ID,
"object__definition__extensions__" + CONTEXT_EXTENSION_MODULE_ID,
"context__course_id",
(self.parse_course_id, lambda x: x["module"]),
),
Expand Down
50 changes: 25 additions & 25 deletions src/ralph/models/edx/converters/xapi/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
UISeekVideo,
UIStopVideo,
)
from ralph.models.xapi.constants import LANG_EN_US_DISPLAY
from ralph.models.xapi.video.constants import (
VIDEO_EXTENSION_LENGTH,
VIDEO_EXTENSION_PROGRESS,
VIDEO_EXTENSION_SESSION_ID,
VIDEO_EXTENSION_TIME,
VIDEO_EXTENSION_TIME_FROM,
VIDEO_EXTENSION_TIME_TO,
VIDEO_EXTENSION_USER_AGENT,
from ralph.models.xapi.concepts.constants.video import (
CONTEXT_EXTENSION_LENGTH,
CONTEXT_EXTENSION_PROGRESS,
CONTEXT_EXTENSION_SESSION_ID,
CONTEXT_EXTENSION_TIME,
CONTEXT_EXTENSION_TIME_FROM,
CONTEXT_EXTENSION_TIME_TO,
CONTEXT_EXTENSION_USER_AGENT,
)
from ralph.models.xapi.constants import LANG_EN_US_DISPLAY
from ralph.models.xapi.video.statements import (
VideoInitialized,
VideoPaused,
Expand Down Expand Up @@ -52,7 +52,7 @@ def _get_conversion_items(self):
+ event["event"]["id"],
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
},
Expand All @@ -71,19 +71,19 @@ def _get_conversion_items(self):
return conversion_items.union(
{
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_LENGTH,
"context__extensions__" + CONTEXT_EXTENSION_LENGTH,
None,
# Set the video length to null by default.
# This information is mandatory in the xAPI template
# and does not exist in the edX `load_video` event model.
lambda _: 0.0,
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_USER_AGENT, "agent"
"context__extensions__" + CONTEXT_EXTENSION_USER_AGENT, "agent"
),
},
)
Expand All @@ -101,11 +101,11 @@ def _get_conversion_items(self):
return conversion_items.union(
{
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_TIME,
"result__extensions__" + CONTEXT_EXTENSION_TIME,
"event__currentTime",
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
},
Expand All @@ -124,19 +124,19 @@ def _get_conversion_items(self):
return conversion_items.union(
{
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_TIME,
"result__extensions__" + CONTEXT_EXTENSION_TIME,
"event__currentTime",
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_LENGTH,
"context__extensions__" + CONTEXT_EXTENSION_LENGTH,
None,
# Set the video length to null by default.
# This information is mandatory in the xAPI template
# and does not exist in the edX `pause_video` event model.
lambda _: 0.0,
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
},
Expand All @@ -155,27 +155,27 @@ def _get_conversion_items(self):
return conversion_items.union(
{
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_TIME,
"result__extensions__" + CONTEXT_EXTENSION_TIME,
"event__currentTime",
),
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_PROGRESS,
"result__extensions__" + CONTEXT_EXTENSION_PROGRESS,
None,
# Set the video progress to null by default.
# This information is mandatory in the xAPI template
# and does not exist in the edX `stop_video` event model.
lambda _: 0.0,
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_LENGTH,
"context__extensions__" + CONTEXT_EXTENSION_LENGTH,
None,
# Set the video length to null by default.
# This information is mandatory in the xAPI template
# and does not exist in the edX `stop_video` event model.
lambda _: 0.0,
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
},
Expand All @@ -194,15 +194,15 @@ def _get_conversion_items(self):
return conversion_items.union(
{
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_TIME_FROM,
"result__extensions__" + CONTEXT_EXTENSION_TIME_FROM,
"event__old_time",
),
ConversionItem(
"result__extensions__" + VIDEO_EXTENSION_TIME_TO,
"result__extensions__" + CONTEXT_EXTENSION_TIME_TO,
"event__new_time",
),
ConversionItem(
"context__extensions__" + VIDEO_EXTENSION_SESSION_ID,
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
},
Expand Down
File renamed without changes.
95 changes: 95 additions & 0 deletions src/ralph/models/xapi/base/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Base xAPI `Agent` definitions."""

from typing import Optional, Union

try:
from typing import Literal
except ImportError:
from typing_extensions import Literal

from pydantic import AnyUrl, StrictStr, constr

from ..config import BaseModelWithConfig
from .common import IRI, MailtoEmail


class BaseXapiAgentAccount(BaseModelWithConfig):
"""Pydantic model for `Agent` type account` property.
Attributes:
homePage (IRI): Consists of the home page of the account's service provider.
name (str): Consists of the unique id or name of the Actor's account.
"""

homePage: IRI
name: StrictStr


class BaseXapiAgentCommonProperties(BaseModelWithConfig):
"""Pydantic model for core `Agent` type property.
It defines who performed the action.
Attributes:
objectType (str): Consists of the value `Agent`.
name (str): Consists of the full name of the Agent.
"""

objectType: Optional[Literal["Agent"]]
name: Optional[StrictStr]


class BaseXapiAgentWithMboxIFI(BaseXapiAgentCommonProperties):
"""Pydantic model for `Agent` type property.
It defines a mailto Inverse Functional Identifier.
Attributes:
mbox (MailtoEmail): Consists of the Agent's email address.
"""

mbox: MailtoEmail


class BaseXapiAgentWithMboxSha1SumIFI(BaseXapiAgentCommonProperties):
"""Pydantic model for `Agent` type property.
It defines a hash Inverse Functional Identifier.
Attributes:
mbox_sha1sum (str): Consists of the SHA1 hash of the Agent's email address.
"""

mbox_sha1sum: constr(regex=r"^[0-9a-f]{40}$") # noqa:F722


class BaseXapiAgentWithOpenIdIFI(BaseXapiAgentCommonProperties):
"""Pydantic model for `Agent` type property.
It defines an OpenID Inverse Functional Identifier.
Attributes:
openid (URI): Consists of an openID that uniquely identifies the Agent.
"""

openid: AnyUrl


class BaseXapiAgentWithAccountIFI(BaseXapiAgentCommonProperties):
"""Pydantic model for `Agent` type property.
It defines an account Inverse Functional Identifier.
Attributes:
account (dict): See BaseXapiAgentAccount.
"""

account: BaseXapiAgentAccount


BaseXapiAgent = Union[
BaseXapiAgentWithMboxIFI,
BaseXapiAgentWithMboxSha1SumIFI,
BaseXapiAgentWithOpenIdIFI,
BaseXapiAgentWithAccountIFI,
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Common xAPI attachments field definitions."""
"""Base xAPI `Attachments` definitions."""

from typing import Optional

Expand All @@ -8,8 +8,8 @@
from .common import IRI, LanguageMap


class AttachmentField(BaseModelWithConfig):
"""Pydantic model for `attachment` field.
class BaseXapiAttachment(BaseModelWithConfig):
"""Pydantic model for `attachment` property.
Attributes:
usageType (IRI): Identifies the usage of this Attachment.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Common xAPI field definitions."""
"""Common for xAPI base definitions."""

from typing import Dict

Expand Down
Loading

0 comments on commit 0d1a748

Please sign in to comment.