-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e360f59
commit 038ff86
Showing
12 changed files
with
543 additions
and
382 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""Wip.""" | ||
|
||
import logging | ||
|
||
import argparse | ||
|
||
import asyncio | ||
|
||
from httpx import HTTPError | ||
from pydantic import ValidationError | ||
|
||
|
||
from warren.xi.client import ExperienceIndex | ||
|
||
from warren.xi.indexers.moodle.client import ( | ||
Moodle | ||
) | ||
|
||
from warren.xi.indexers.factory import IndexerFactory | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
async def run_indexer(args): | ||
"""Wip.""" | ||
|
||
xi = ExperienceIndex() | ||
moodle = Moodle() | ||
|
||
# todo - have a factory for the LMS source | ||
indexer = await IndexerFactory.create_indexer(args.indexer, lms=moodle, xi=xi, ignore_errors=args.ignore_errors, course_iri=args.course_iri) | ||
|
||
try: | ||
logger.debug("Running indexer: %s", indexer) | ||
await indexer.execute() | ||
|
||
except (HTTPError, ValidationError): | ||
logger.exception("Runner has stopped") | ||
|
||
finally: | ||
logger.debug("Closing clients session") | ||
await moodle.close() | ||
await xi.close() | ||
|
||
if __name__ == "__main__": | ||
"""Wip.""" | ||
|
||
parser = argparse.ArgumentParser(description="Wip.") | ||
parser.add_argument('indexer', type=str, default='courses', help='Wip.', nargs='?') | ||
parser.add_argument('-ie', '--ignore_errors', action='store_true', default=False) | ||
parser.add_argument('course_iri', type=str, nargs='?') | ||
|
||
args = parser.parse_args() | ||
asyncio.run(run_indexer(args)) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
"""Wip.""" | ||
|
||
from abc import ABC, abstractmethod | ||
from http import HTTPStatus | ||
from typing import List, Optional, Protocol, Tuple, Union | ||
from urllib.parse import quote | ||
from uuid import UUID | ||
|
||
from httpx import AsyncClient | ||
from pydantic import parse_obj_as | ||
|
||
from warren.fields import IRI | ||
|
||
from .models import ( | ||
Experience, | ||
ExperienceCreate, | ||
ExperienceRead, | ||
ExperienceReadSnapshot, | ||
ExperienceUpdate, | ||
Relation, | ||
RelationCreate, | ||
RelationRead, | ||
RelationType, | ||
RelationUpdate, | ||
) | ||
|
||
|
||
class Client(Protocol): | ||
"""Protocol representing an asynchronous client.""" | ||
|
||
_client: AsyncClient | ||
|
||
async def close(self): | ||
"""Close client pool of connections asynchronously.""" | ||
... | ||
|
||
|
||
class BaseCRUD(ABC): | ||
"""Wip.""" | ||
|
||
_client: AsyncClient | ||
|
||
def __init__(self, client): | ||
"""Wip.""" | ||
self._client = client | ||
|
||
def _construct_url(self, path: Union[str, UUID, IRI]) -> str: | ||
"""Wip.""" | ||
path = quote(str(path)) | ||
return f"{self._base_url.rstrip('/')}/{path.lstrip('/')}" | ||
|
||
@property | ||
@abstractmethod | ||
def _base_url(self): | ||
"""Abstract method to get the URL for specific entity.""" | ||
|
||
@abstractmethod | ||
async def create(self, data): | ||
"""Abstract method for creating an entity.""" | ||
|
||
@abstractmethod | ||
async def get(self, object_id): | ||
"""Abstract method for getting an entity.""" | ||
|
||
@abstractmethod | ||
async def update(self, object_id, data): | ||
"""Abstract method for updating an entity.""" | ||
|
||
@abstractmethod | ||
async def read(self, **kwargs): | ||
"""Abstract method for reading entities.""" | ||
|
||
|
||
class CRUDExperience(BaseCRUD): | ||
"""Wip.""" | ||
|
||
@property | ||
def _base_url(self) -> str: | ||
"""Wip.""" | ||
return "experiences" | ||
|
||
async def create(self, data: ExperienceCreate) -> UUID: | ||
"""Wip.""" | ||
response = await self._client.post( | ||
url=self._construct_url("/"), data=data.json() # type: ignore[arg-type] | ||
) | ||
response.raise_for_status() | ||
return UUID(response.json()) | ||
|
||
async def get(self, object_id: Union[UUID, IRI]) -> Union[ExperienceRead, None]: | ||
"""Wip.""" | ||
response = await self._client.get(url=self._construct_url(object_id)) | ||
|
||
if response.status_code == HTTPStatus.NOT_FOUND: | ||
return None | ||
|
||
response.raise_for_status() | ||
return ExperienceRead(**response.json()) | ||
|
||
async def update( | ||
self, object_id: UUID, data: Union[ExperienceUpdate, ExperienceCreate] | ||
) -> Experience: | ||
"""Wip.""" | ||
response = await self._client.put( | ||
url=self._construct_url(object_id), data=data.json() # type: ignore[arg-type] | ||
) | ||
response.raise_for_status() | ||
return Experience(**response.json()) | ||
|
||
async def read(self, **kwargs) -> List[ExperienceReadSnapshot]: | ||
"""Wip.""" | ||
response = await self._client.get(url=self._construct_url("/"), params=kwargs) | ||
response.raise_for_status() | ||
return parse_obj_as(List[ExperienceReadSnapshot], response.json()) | ||
|
||
async def create_or_update(self, data: ExperienceCreate) -> Union[UUID, Experience]: | ||
"""Wip.""" | ||
db_experience = await self.get(data.iri) | ||
|
||
if db_experience is None: | ||
return await self.create(data) | ||
|
||
return await self.update(db_experience.id, data) | ||
|
||
|
||
class CRUDRelation(BaseCRUD): | ||
"""Wip.""" | ||
|
||
@property | ||
def _base_url(self) -> str: | ||
"""Wip.""" | ||
return "relations" | ||
|
||
async def create(self, data: RelationCreate) -> UUID: | ||
"""Wip.""" | ||
response = await self._client.post( | ||
url=self._construct_url("/"), data=data.json() # type: ignore[arg-type] | ||
) | ||
response.raise_for_status() | ||
return UUID(response.json()) | ||
|
||
async def get(self, object_id: UUID) -> Union[RelationRead, None]: | ||
"""Wip.""" | ||
response = await self._client.get(url=self._construct_url(object_id)) | ||
|
||
if response.status_code == HTTPStatus.NOT_FOUND: | ||
return None | ||
|
||
response.raise_for_status() | ||
return RelationRead(**response.json()) | ||
|
||
async def update( | ||
self, object_id: UUID, data: Union[RelationUpdate, RelationCreate] | ||
) -> Relation: | ||
"""Wip.""" | ||
response = await self._client.put( | ||
url=self._construct_url(object_id), data=data.json() # type: ignore[arg-type] | ||
) | ||
response.raise_for_status() | ||
return Relation(**response.json()) | ||
|
||
async def read(self, **kwargs) -> List[RelationRead]: | ||
"""Wip.""" | ||
response = await self._client.get(url=self._construct_url("/"), params=kwargs) | ||
response.raise_for_status() | ||
return parse_obj_as(List[RelationRead], response.json()) | ||
|
||
async def create_bidirectional( | ||
self, source_id: UUID, target_id: UUID, kinds: Tuple[RelationType, RelationType] | ||
) -> Tuple[UUID, UUID]: | ||
"""Wip.""" | ||
kind, inverted_kind = kinds | ||
|
||
relation = RelationCreate(source_id=source_id, target_id=target_id, kind=kind) | ||
|
||
inverted_relation = RelationCreate( | ||
source_id=target_id, target_id=source_id, kind=inverted_kind | ||
) | ||
|
||
relation_id = await self.create(relation) | ||
inverted_relation_id = await self.create(inverted_relation) | ||
|
||
return relation_id, inverted_relation_id | ||
|
||
|
||
class ExperienceIndex(Client): | ||
"""Wip.""" | ||
|
||
experience: CRUDExperience | ||
relation: CRUDRelation | ||
|
||
def __init__(self, url: Optional[str] = None): | ||
"""Initialize the Warren HTTP client.""" | ||
self._url = url or "http://localhost:8100/api/v1/" | ||
|
||
self._client = AsyncClient( | ||
base_url=self._url, | ||
) | ||
|
||
self.experience = CRUDExperience(client=self._client) | ||
self.relation = CRUDRelation(client=self._client) | ||
|
||
async def close(self): | ||
"""Close the asynchronous HTTP client.""" | ||
await self._client.aclose() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
"""Wip.""" | ||
|
||
from typing import Protocol | ||
|
||
from ..client import Client | ||
|
||
|
||
class LMS(Client, Protocol): | ||
"""Wip.""" | ||
|
||
url: str | ||
|
||
async def get_courses(self, *args, **kwargs): | ||
"""Wip.""" | ||
... | ||
|
||
async def get_course_contents(self, *args, **kwargs): | ||
"""Wip.""" | ||
... |
Oops, something went wrong.