Skip to content
Merged
20 changes: 20 additions & 0 deletions examples/get_assembly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Add the notubiz folder to the path
import sys, os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from notubiz import ApiClient, Configuration
from notubiz.api.clients import AssemblyClient, MeetingClient

configuration = Configuration(organisation_id = 686) # Gemeente Eindhoven

api_client = ApiClient(configuration)

assembly_client = AssemblyClient(api_client)
meeting_client = MeetingClient(api_client)

assembly = assembly_client.get(1253866)

for assembly_meeting in assembly.meetings:
meeting = meeting_client.get(assembly_meeting.id)

print("{} - {} ({})".format(assembly_meeting.plannings[0].start_date, meeting.title, meeting.location))
4 changes: 2 additions & 2 deletions examples/get_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

event_client = EventsClient(api_client)

start_date = datetime(2019, 1, 1)
end_date = datetime(2020, 3, 31, 23, 59, 59)
start_date = datetime(2025, 1, 5)
end_date = datetime(2025, 1, 7, 23, 59, 59)

events = event_client.get(start_date, end_date)

Expand Down
15 changes: 15 additions & 0 deletions notubiz/api/_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from cattr import Converter
from datetime import datetime

from notubiz.api._helpers import parse_date
from notubiz.api._hooks_registry import Hooks

def get_converter() -> Converter:
converter = Converter()
converter.register_structure_hook(datetime, lambda date_string, _: parse_date(date_string))

# Custom hooks
for cls, hook in Hooks.items():
converter.register_structure_hook(cls, hook)

return converter
12 changes: 12 additions & 0 deletions notubiz/api/_hooks_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from notubiz.api.dataclasses import Event, event_hook
from notubiz.api.dataclasses import Meeting, meeting_hook
from notubiz.api.dataclasses import AgendaItem, agenda_item_hook
from notubiz.api.dataclasses import Speakers, Speaker, speakers_hook, speaker_hook

Hooks = {
Event: event_hook,
Meeting: meeting_hook,
AgendaItem: agenda_item_hook,
Speakers: speakers_hook,
Speaker: speaker_hook
}
3 changes: 2 additions & 1 deletion notubiz/api/clients/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from notubiz.api.clients.events_client import EventsClient
from notubiz.api.clients.meeting_client import MeetingClient
from notubiz.api.clients.speakers_client import SpeakersClient
from notubiz.api.clients.speakers_client import SpeakersClient
from notubiz.api.clients.assembly_client import AssemblyClient
17 changes: 17 additions & 0 deletions notubiz/api/clients/assembly_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from notubiz import ApiClient
from notubiz.api.dataclasses.assembly import Assembly

from notubiz.api._converter import get_converter

class AssemblyClient:
api_client : ApiClient

def __init__(self, api_client : ApiClient):
self.api_client = api_client

def get(self, assembly_event_id : int) -> Assembly:
json_object = self.api_client.get("events/assemblies/{}".format(assembly_event_id))

json_assembly = json_object["assembly"]

return get_converter().structure(json_assembly, Assembly)
5 changes: 4 additions & 1 deletion notubiz/api/clients/events_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from notubiz.api.dataclasses import Event
from datetime import datetime

from notubiz.api._converter import get_converter

class EventsClient:
api_client : ApiClient

Expand All @@ -10,6 +12,7 @@ def __init__(self, api_client : ApiClient):


def get(self, date_from: datetime, date_to: datetime, gremia: list[int] = None) -> list[Event]:
converter = get_converter()

json_events : list[dict] = []
has_more_pages = True
Expand All @@ -34,4 +37,4 @@ def get(self, date_from: datetime, date_to: datetime, gremia: list[int] = None)
page += 1

# Now run the deserialization based on the merged events
return [Event.from_json(json_event) for json_event in json_events]
return [converter.structure(json_event, Event) for json_event in json_events]
4 changes: 3 additions & 1 deletion notubiz/api/clients/meeting_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from notubiz import ApiClient
from notubiz.api.dataclasses import Meeting
from notubiz.api._converter import get_converter

class MeetingClient:
api_client : ApiClient
Expand All @@ -9,4 +10,5 @@ def __init__(self, api_client : ApiClient):

def get(self, meeting_id : int) -> Meeting:
json_object = self.api_client.get("events/meetings/{}".format(meeting_id))
return Meeting.from_json(json_object)

return get_converter().structure(json_object, Meeting)
3 changes: 2 additions & 1 deletion notubiz/api/clients/speakers_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from notubiz.api_client import ApiClient
from notubiz.api.dataclasses import Speakers
from notubiz.api._converter import get_converter

class SpeakersClient:
api_client : ApiClient
Expand All @@ -9,4 +10,4 @@ def __init__(self, api_client : ApiClient):

def get(self) -> Speakers:
json_object = self.api_client.get("speakers")
return Speakers.from_json(json_object)
return get_converter().structure(json_object, Speakers)
9 changes: 5 additions & 4 deletions notubiz/api/dataclasses/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from notubiz.api.dataclasses.event import Event
from notubiz.api.dataclasses.meeting import Meeting
from notubiz.api.dataclasses.event import Event, event_hook
from notubiz.api.dataclasses.meeting import Meeting, meeting_hook
from notubiz.api.dataclasses.assembly import Assembly
from notubiz.api.dataclasses.document import Document
from notubiz.api.dataclasses.agenda_item import AgendaItem
from notubiz.api.dataclasses.speakers import Speaker, Speakers
from notubiz.api.dataclasses.agenda_item import AgendaItem, agenda_item_hook
from notubiz.api.dataclasses.speakers import Speakers, Speaker, speakers_hook, speaker_hook
68 changes: 33 additions & 35 deletions notubiz/api/dataclasses/agenda_item.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
from attrs import define, field
import cattrs
from cattr import Converter

from datetime import datetime
from typing import Optional, Dict, Any
from typing import Optional

from notubiz.api._helpers import parse_date, get_attribute, get_title, get_description
from notubiz.api.dataclasses.document import Document

@define
class AgendaItem:
# Auto-filled fields
id : int
last_modified : datetime
title : str
description: str
start_date : Optional[datetime]
end_date : Optional[datetime]
is_heading : bool
documents: list[Document]
agenda_items : list['AgendaItem'] = field(factory=list)

class AgendaItems:
@staticmethod
def from_json(json_object : any) -> list[AgendaItem]:
c = cattrs.Converter()

c.register_structure_hook(datetime, lambda date_string, _: parse_date(date_string))
c.register_structure_hook(AgendaItem, agenda_item_structure_hook)

agenda_items = [c.structure(item, AgendaItem) for item in json_object]

return agenda_items

# Filled manually
title : str = field(init=False)
description: str = field(init=False)
start_date : Optional[datetime] = field(init=False)
end_date : Optional[datetime] = field(init=False)
is_heading : bool = field(init=False)
agenda_items : list['AgendaItem'] = field(factory=list)

def get_start_date(attributes) -> datetime:
try:
Expand All @@ -44,22 +34,30 @@ def get_end_date(attributes) -> datetime:
except Exception: # The nested agenda items seem to have no end dates.
return None

def agenda_item_structure_hook(data: Dict[str, Any], cls: type) -> AgendaItem:
type_data = data.get("type_data", {})
attributes = type_data["attributes"]
def agenda_item_hook(data: dict[str, any], cls: type) -> AgendaItem:
# Auto-fill fields

documents = [Document.from_json(item) for item in data["documents"]]
agenda_items = AgendaItems.from_json(data["agenda_items"])
converter = Converter()
converter.register_structure_hook(datetime, lambda date_string, _: parse_date(date_string))
converter.register_structure_hook(AgendaItem, agenda_item_hook)
documents = [converter.structure(item, Document) for item in data.get("documents", [])]

return AgendaItem(
id=data["id"],
last_modified = parse_date(data["last_modified"]),
title = get_title(attributes),
description = get_description(attributes),
start_date = get_start_date(attributes),
end_date = get_end_date(attributes),
is_heading = data["type_data"]["heading"],
documents = documents,
agenda_items = agenda_items
agenda_item = AgendaItem(
id = data.get("id"),
last_modified = parse_date(data.get("last_modified")),
documents = documents
)

# Manually add some fields
type_data = data.get("type_data", {})
attributes = type_data["attributes"]

agenda_item.title = get_title(attributes)
agenda_item.description = get_description(attributes)
agenda_item.start_date = get_start_date(attributes)
agenda_item.end_date = get_end_date(attributes)
agenda_item.is_heading = type_data.get("heading", False)

agenda_item.agenda_items = [converter.structure(item, AgendaItem) for item in data.get("agenda_items", [])]

return agenda_item
14 changes: 14 additions & 0 deletions notubiz/api/dataclasses/assembly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from attrs import define

from notubiz.api.dataclasses.event import Event
from notubiz.api.dataclasses.planning import Planning

@define
class AssemblyMeeting:
id: int
order: int
plannings: list[Planning]

@define
class Assembly(Event):
meetings: list[AssemblyMeeting]
18 changes: 6 additions & 12 deletions notubiz/api/dataclasses/document.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from attrs import define, field
import cattrs
from typing import Optional, List
from datetime import datetime

@define
class DocumentVersion:
id: int
file_name: str
file_size: int
mime_type: Optional[str] = field(default="") # Apparently some documents have no MIME type
type: str # Known possible values: file, link
url: Optional[str] = field(default=None)
file_name: Optional[str] = field(default=None)
file_size: Optional[int] = field(default=None)
mime_type: Optional[str] = field(default=None) # Some documents have no MIME type

@define
class Document:
Expand All @@ -17,11 +18,4 @@ class Document:
version: int
url: str
versions: List[DocumentVersion]
id: Optional[int] = field(default=None) # Apparently some documents have no ID

@staticmethod
def from_json(json_object : any) -> 'Document':
c = cattrs.Converter()
c.register_structure_hook(datetime, lambda d, _: datetime.strptime(d, "%Y-%m-%d %H:%M:%S"))

return c.structure(json_object, Document)
id: Optional[int] = field(default=None) # Apparently some documents have no ID
39 changes: 14 additions & 25 deletions notubiz/api/dataclasses/event.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
from attrs import define, field
import cattrs
from cattrs import transform_error

from cattrs import Converter
from typing import Optional

from datetime import datetime
from notubiz.api._helpers import parse_date, get_title, get_location

@define
class Planning:
# Auto-filled
start_date : datetime
end_date : Optional[datetime]
from notubiz.api.dataclasses.planning import Planning

@define
class Event:
Expand All @@ -37,21 +30,17 @@ class Event:
location: str = field(init=False)
gremium_id: int = field(init=False)

@staticmethod
def from_json(json_object : any) -> 'Event':
c = cattrs.Converter()

c.register_structure_hook(datetime, lambda date_string, _: parse_date(date_string))

try:
meeting = c.structure(json_object, Event)
except Exception as exc:
print("\n".join(transform_error(exc)))
quit()

attributes = json_object.get("attributes", [])
meeting.title = get_title(attributes)
meeting.location = get_location(attributes)
meeting.gremium_id = json_object["gremium"]["id"]
def event_hook(data : dict, cls: type) -> Event:
# Auto-fill fields
converter = Converter()
converter.register_structure_hook(datetime, lambda date_string, _: parse_date(date_string))
event = converter.structure(data, cls)

return meeting
# Manually add some fields
attributes = data.get("attributes", [])
event.title = get_title(attributes)
event.location = get_location(attributes)
event.gremium_id = data["gremium"]["id"]

return event
Loading
Loading