Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions openproficiency/TopicList.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,11 @@ def timestamp(self, value: Union[str, datetime, None]) -> None:

@property
def full_name(self) -> str:
"""Get the full name of the TopicList in 'owner/name' format."""
return f"{self.owner}/{self.name}"
"""Get the full name of the TopicList in 'owner/name@version' format."""
full_name = f"{self.owner}/{self.name}"
if self.version:
full_name += f"@{self.version}"
return full_name

# Debugging
def __repr__(self) -> str:
Expand Down
88 changes: 75 additions & 13 deletions openproficiency/TranscriptEntry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import json
from datetime import datetime
from typing import Optional, Union
from typing import Any, Dict, Optional, Union
from .ProficiencyScore import ProficiencyScore


Expand All @@ -15,66 +15,128 @@ def __init__(
# Required
user_id: str,
topic_id: str,
topic_list: str,
score: float,
timestamp: Union[datetime, str],
issuer: str,
# Optional
timestamp: Optional[datetime] = None,
certificate: Optional[str] = None,
):
# Required
self.user_id = user_id
self._proficiency_score = ProficiencyScore(
topic_id=topic_id,
score=score,
)
self.topic_list = topic_list
self.timestamp = timestamp
self.issuer = issuer

# Optional
self.timestamp = timestamp or datetime.now()
self.certificate = certificate

# Properties
@property
def proficiency_score(self) -> ProficiencyScore:
"""Get the topic ID from the proficiency score."""
return self._proficiency_score

@property
def topic_list(self) -> str:
"""Get the topic list reference."""
return self._topic_list

@topic_list.setter
def topic_list(self, value: str) -> None:
"""Set the topic list reference from TopicList instance"""
self._topic_list = value

@property
def certificate(self) -> Optional[str]:
"""Get the certificate text."""
return self._certificate

@certificate.setter
def certificate(self, value: Optional[str]) -> None:
"""Set the certificate text."""
self._certificate = value

@property
def timestamp(self) -> datetime:
"""Get the time this entry was created"""
return self._timestamp

@timestamp.setter
def timestamp(self, value: Union[datetime, str]) -> None:
"""Set the time this entry was created"""
# Directly store datetime object.
if isinstance(value, datetime):
self._timestamp = value

# Convert ISO 8601 string to datetime object.
elif isinstance(value, str):
self._timestamp = datetime.fromisoformat(value)

else:
raise ValueError("Timestamp must be a datetime object or ISO 8601 string")

# Methods
def to_dict(self) -> dict[str, Union[str, int, float]]:
def to_dict(self) -> Dict[str, Union[str, float]]:
"""Convert TranscriptEntry to JSON-serializable dictionary."""
return {
data = {
"user_id": self.user_id,
"topic_id": self._proficiency_score.topic_id,
"topic": self._proficiency_score.topic_id,
"topic_list": self.topic_list,
"score": self._proficiency_score.score,
"issuer": self.issuer,
"timestamp": self.timestamp.isoformat(),
"issuer": self.issuer,
}

# Attach certificate if it exists
if self.certificate is not None:
data["certificate"] = self.certificate

return data

def to_json(self) -> str:
"""Convert TranscriptEntry to JSON string."""
return json.dumps(self.to_dict())
data = self.to_dict()
# Change keys to camelCase for JSON output
data["userID"] = data.pop("user_id")
data["topicList"] = data.pop("topic_list")
return json.dumps(data)

# Methods - Static
@staticmethod
def from_dict(data: dict[str, Union[str, int, float]]) -> "TranscriptEntry":
def from_dict(data: Dict[str, Any]) -> "TranscriptEntry":
"""Create a TranscriptEntry from a dictionary."""
return TranscriptEntry(
# Required
user_id=data["user_id"],
topic_id=data["topic_id"],
topic_id=data["topic"],
topic_list=data["topic_list"],
score=data["score"],
timestamp=data["timestamp"],
issuer=data["issuer"],
timestamp=datetime.fromisoformat(data["timestamp"]),
# Optional
certificate=data.get("certificate"),
)

@staticmethod
def from_json(json_str: str) -> "TranscriptEntry":
"""Create a TranscriptEntry from a JSON string."""
return TranscriptEntry.from_dict(json.loads(json_str))
# Load JSON string. Map camelCase to snake_case if needed
data = json.loads(json_str)
data["user_id"] = data.pop("userID")
data["topic_list"] = data.pop("topicList")
return TranscriptEntry.from_dict(data)

# Debugging
def __repr__(self) -> str:
"""String representation of TranscriptEntry."""
return (
f"TranscriptEntry(user_id='{self.user_id}', "
f"topic_id='{self._proficiency_score.topic_id}', "
f"topic_list='{self.topic_list}', "
f"score={self._proficiency_score.score}, "
f"issuer='{self.issuer}'"
)
18 changes: 18 additions & 0 deletions tests/TopicList_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,24 @@ def test_full_name(self):
# Assert
assert full_name == "example.com/example-topic-list"

def test_full_name_with_version(self):
"""Test getting the full name of the topic list, including version."""

# Arrange
owner = "example.com"
name = "example-topic-list"
topic_list = TopicList(
owner=owner,
name=name,
version="1.2.3",
)

# Act
full_name = topic_list.full_name

# Assert
assert full_name == "example.com/example-topic-list@1.2.3"

def test_version_setter_valid_format(self):
"""Test setting version with valid semantic versioning."""

Expand Down
Loading