Skip to content

Commit

Permalink
Implement fetching grades, terms, courses and api documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoni-Czaplicki committed Jul 10, 2024
1 parent b0d8c7f commit b06d084
Show file tree
Hide file tree
Showing 19 changed files with 546 additions and 46 deletions.
6 changes: 6 additions & 0 deletions docs/full-docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ Services
:members:


Helper
^^^^^^

.. autoclass:: usos_api.helper.APIHelper
:members:
:show-inheritance:
45 changes: 45 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
""" Example of fetching user data from the USOS API. """

import asyncio
import json
import os

from dotenv import load_dotenv
Expand Down Expand Up @@ -40,13 +41,55 @@ async def fetch_data(api_base_address: str, consumer_key: str, consumer_secret:
ongoing_terms_only=True
)
print(groups)
print(await client.helper.get_user_end_grades_with_weights())
except USOSAPIException as e:
print(f"Error fetching data: {e}")
finally:
if client:
await client.close()


async def fetch_documentation(
api_base_address: str, consumer_key: str, consumer_secret: str
):
async with USOSClient(api_base_address, consumer_key, consumer_secret) as client:
await client.load_access_token_from_file()
documentation_service = client.api_documentation_service

method_index = await documentation_service.get_method_index()
modules = set()

for method in method_index:
module = method.name.split("/")[1]
modules.add(module)

modules_info = []
for module in modules:
print(f"Fetching documentation for module {module}")
module_info = await documentation_service.get_module_info(module)
module_info = module_info.dict()
module_info["methods_info"] = {}
for method in module_info["methods"]:
print(f"Fetching documentation for method {method}")
method_info = await documentation_service.get_method_info(method)
module_info["methods_info"][method] = method_info.dict()

modules_info.append(module_info)

# Combine fetched data
documentation = {
"method_index": [method.dict() for method in method_index],
"modules": [module for module in modules_info],
}

return documentation


async def save_documentation_to_file(file_path, documentation):
with open(file_path, "w") as file:
json.dump(documentation, file, indent=4)


if __name__ == "__main__":
api_base_address = os.environ.get("USOS_API_BASE_ADDRESS", DEFAULT_API_BASE_ADDRESS)
consumer_key = os.environ.get("USOS_CONSUMER_KEY")
Expand All @@ -58,3 +101,5 @@ async def fetch_data(api_base_address: str, consumer_key: str, consumer_secret:
)

asyncio.run(fetch_data(api_base_address, consumer_key, consumer_secret))
# documentation = asyncio.run(fetch_documentation(api_base_address, consumer_key, consumer_secret))
# asyncio.run(save_documentation_to_file("usos_api_documentation.json", documentation))
11 changes: 11 additions & 0 deletions usos_api/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import json

from .connection import USOSAPIConnection
from .helper import APIHelper
from .logger import get_logger
from .services import UserService
from .services.api_documentation import APIDocumentationService
from .services.api_server import APIServerService
from .services.courses import CourseService
from .services.grades import GradeService
from .services.groups import GroupService
from .services.terms import TermService

_LOGGER = get_logger("USOSClient")

Expand Down Expand Up @@ -35,7 +40,13 @@ def __init__(
)
self.user_service = UserService(self.connection)
self.group_service = GroupService(self.connection)
self.course_service = CourseService(self.connection)
self.term_service = TermService(self.connection)
self.grade_service = GradeService(self.connection)
self.api_server_service = APIServerService(self.connection)
self.api_documentation_service = APIDocumentationService(self.connection)

self.helper = APIHelper(self)

async def __aenter__(self) -> "USOSClient":
"""
Expand Down
1 change: 0 additions & 1 deletion usos_api/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ async def get(self, service: str, **kwargs) -> dict:
url, headers, body = self.auth_manager.sign_request(
"".join(url_parts), headers=headers
)
print(url, headers, body)
async with self._session.get(url, params=kwargs, headers=headers) as response:
await self._handle_response_errors(response)
return await response.json()
Expand Down
54 changes: 54 additions & 0 deletions usos_api/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from typing import TYPE_CHECKING, List

from usos_api.models import CourseEdition, Grade

if TYPE_CHECKING:
from usos_api import USOSClient


class APIHelper:
def __init__(self, client: "USOSClient"):
self.client = client

async def get_user_end_grades_with_weights(
self, current_term_only: bool = False
) -> List[Grade]:
"""
Get user end grades with weights.
:param current_term_only: If True, only consider the current term.
:return: A list of user end grades with weights.
"""
ects_by_term = await self.client.course_service.get_user_courses_ects()
terms = await self.client.term_service.get_terms(list(ects_by_term.keys()))
term_ids = [
term.id for term in terms if not current_term_only or term.is_current
]
course_ids = [
course_id
for term_id in term_ids
for course_id in ects_by_term[term_id].keys()
]

courses = {
course.id: course
for course in await self.client.course_service.get_courses(course_ids)
}
grades_by_term = await self.client.grade_service.get_grades_by_terms(term_ids)

user_grades = []
for term in terms:
if term.id not in term_ids:
continue
for course_id, ects in ects_by_term[term.id].items():
grades = (
grades_by_term[term.id].get(course_id, {}).get("course_grades", [])
)
for grade in grades:
grade.weight = ects
grade.course_edition = CourseEdition(
course=courses[course_id], term=term
)
user_grades.append(grade)

return user_grades
27 changes: 26 additions & 1 deletion usos_api/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
from .api_documentation import (
APIMethodIndexItem,
APIMethodInfo,
APIModuleInfo,
Argument,
AuthOptions,
DeprecatedInfo,
ResultField,
ScopeInfo,
)
from .consumer import Consumer
from .course import Course, CourseAttribute, CourseEdition, CourseEditionConducted
from .course import (
Course,
CourseAttribute,
CourseEdition,
CourseEditionConducted,
CourseUnit,
)
from .grade import Grade
from .group import Group
from .lang_dict import LangDict
Expand Down Expand Up @@ -50,4 +66,13 @@
"CourseEditionConducted",
"Term",
"Consumer",
"CourseUnit",
"AuthOptions",
"Argument",
"ResultField",
"DeprecatedInfo",
"APIMethodInfo",
"APIMethodIndexItem",
"APIModuleInfo",
"ScopeInfo",
]
68 changes: 68 additions & 0 deletions usos_api/models/api_documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import Any

from pydantic import BaseModel


class AuthOptions(BaseModel):
consumer: str | None = None
token: str | None = None
administrative_only: bool | None = None
ssl_required: bool | None = None


class Argument(BaseModel):
name: str | None = None
is_required: bool | None = None
is_deprecated: bool | None = None
default_value: Any | None = None
description: str | None = None


class ResultField(BaseModel):
name: str | None = None
description: str | None = None
is_primary: bool | None = None
is_secondary: bool | None = None


class DeprecatedInfo(BaseModel):
deprecated_by: str | None = None
present_until: str | None = None


class APIMethodInfo(BaseModel):
name: str | None = None
short_name: str | None = None
description: str | None = None
brief_description: str | None = None
ref_url: str | None = None
auth_options: AuthOptions | None = None
scopes: list[str] | None = None
arguments: list[Argument] | None = None
returns: str | None = None
errors: str | None = None
result_fields: list[ResultField] | None = None
beta: bool | None = None
deprecated: DeprecatedInfo | None = None
admin_access: bool | None = None
is_internal: bool | None = None


class APIMethodIndexItem(BaseModel):
name: str | None = None
brief_description: str | None = None


class APIModuleInfo(BaseModel):
name: str | None = None
title: str | None = None
brief_description: str | None = None
description: str | None = None
submodules: list[str] | None = None
methods: list[str] | None = None
beta: bool | None = None


class ScopeInfo(BaseModel):
key: str | None = None
developers_description: str | None = None
5 changes: 2 additions & 3 deletions usos_api/models/consumer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from datetime import datetime
from typing import List

from pydantic import BaseModel

Expand All @@ -13,5 +12,5 @@ class Consumer(BaseModel):
url: str | None = None
email: str | None = None
date_registered: datetime | None = None
administrative_methods: List[str] | None = None
token_scopes: List[str] | None = None
administrative_methods: list[str] | None = None
token_scopes: list[str] | None = None
45 changes: 26 additions & 19 deletions usos_api/models/course.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, Optional

from pydantic import BaseModel

from .group import Group
from .term import Term

if TYPE_CHECKING:
from .user import User
pass

from .grade import Grade
from .lang_dict import LangDict


Expand All @@ -18,7 +17,7 @@ class CourseAttribute(BaseModel):
"""

name: LangDict | None = None
values: List[LangDict] | None = None
values: list[LangDict] | None = None


class Course(BaseModel):
Expand All @@ -31,7 +30,7 @@ class Course(BaseModel):
homepage_url: str | None = None
profile_url: str | None = None
is_currently_conducted: bool | None = None
terms: List[Term] | None = None
terms: list[Term] | None = None
fac_id: str | None = None
lang_id: str | None = None
ects_credits_simplified: float | None = None
Expand All @@ -40,31 +39,39 @@ class Course(BaseModel):
learning_outcomes: LangDict | None = None
assessment_criteria: LangDict | None = None
practical_placement: LangDict | None = None
attributes: List[CourseAttribute] | None = None
attributes2: List[CourseAttribute] | None = None
attributes: list[CourseAttribute] | None = None
attributes2: list[CourseAttribute] | None = None


class CourseUnit(BaseModel):
"""
Class representing a course unit.
"""

id: str
homepage_url: str | None = None
profile_url: str | None = None
learning_outcomes: LangDict | None = None
assessment_criteria: LangDict | None = None
topics: LangDict | None = None
teaching_methods: LangDict | None = None
bibliography: LangDict | None = None
course_edition: Optional["CourseEdition"] = None
class_groups: list[Group] | None = None


class CourseEdition(BaseModel):
"""
Class representing a course edition.
"""

course_id: str | None = None
course_name: LangDict | None = None
term_id: str | None = None
course: Course | None = None
term: Term | None = None
homepage_url: str | None = None
profile_url: str | None = None
coordinators: List["User"] | None = None
lecturers: List["User"] | None = None
passing_status: str | None = None
user_groups: List[Group] | None = None
description: LangDict | None = None
bibliography: LangDict | None = None
notes: LangDict | None = None
course_units_ids: List[str] | None = None
participants: List["User"] | None = None
grades: List[Grade] | None = None
attributes: List[CourseAttribute] | None = None
course_units: list[CourseUnit] | None = None


class CourseEditionConducted(BaseModel):
Expand Down
Loading

0 comments on commit b06d084

Please sign in to comment.