3131
3232import zulip
3333
34+ from pydantic2_schemaorg .Event import Event as SchemaOrgEvent
35+ from pydantic2_schemaorg .Place import Place
36+ from pydantic2_schemaorg .PostalAddress import PostalAddress
37+ from pydantic2_schemaorg .VirtualLocation import VirtualLocation
38+
3439FilePath = Union [str , Path ]
3540
3641DOWNLOAD = not 'NODOWNLOAD' in os .environ
@@ -351,7 +356,7 @@ def validate(self) -> None:
351356 )
352357
353358def generate_schema_org_json (event : Event ) -> str :
354- """Generate schema.org JSON-LD for an event."""
359+ """Generate schema.org JSON-LD for an event using pydantic2-schemaorg models ."""
355360 # Parse dates to ISO 8601 format
356361 try :
357362 start_date_iso = datetime .strptime (event .start_date , '%B %d %Y' ).strftime ('%Y-%m-%d' )
@@ -363,86 +368,76 @@ def generate_schema_org_json(event: Event) -> str:
363368 except (ValueError , TypeError , AttributeError ):
364369 raise ValueError (f"Invalid end date for event '{ event .title } ': { event .end_date } " )
365370
366- # Build the schema.org structure
367- schema_data = {
368- "@context" : "https://schema.org" ,
369- "@type" : "Event" ,
370- "name" : event .title ,
371- "url" : event .url ,
372- "startDate" : start_date_iso ,
373- "endDate" : end_date_iso ,
374- }
375-
376- # Add location - determine if virtual, hybrid, or physical
371+ # Build location - determine if virtual, hybrid, or physical
372+ location = None
373+ event_attendance_mode = None
374+
377375 if event .hybrid :
378376 # Hybrid event: both physical and virtual attendance
379- schema_data [ "eventAttendanceMode" ] = "https://schema.org/MixedEventAttendanceMode"
377+ event_attendance_mode = "https://schema.org/MixedEventAttendanceMode"
380378
381379 # Build physical location
382- address = {
383- "@type" : "PostalAddress" ,
380+ address_kwargs = {
384381 "addressLocality" : event .city ,
385382 "addressCountry" : event .country
386383 }
387384 if event .state :
388- address ["addressRegion" ] = event .state
385+ address_kwargs ["addressRegion" ] = event .state
389386
390387 place_name = event .venue if event .venue else event .city
391388
392389 # Location is an array with both Place and VirtualLocation
393- schema_data ["location" ] = [
394- {
395- "@type" : "Place" ,
396- "name" : place_name ,
397- "address" : address
398- },
399- {
400- "@type" : "VirtualLocation" ,
401- "url" : event .url
402- }
390+ location = [
391+ Place (
392+ name = place_name ,
393+ address = PostalAddress (** address_kwargs )
394+ ),
395+ VirtualLocation (url = event .url )
403396 ]
404397 elif event .is_fully_remote ():
405398 # Purely virtual event
406- schema_data ["eventAttendanceMode" ] = "https://schema.org/OnlineEventAttendanceMode"
407- schema_data ["location" ] = {
408- "@type" : "VirtualLocation" ,
409- "url" : event .url
410- }
399+ event_attendance_mode = "https://schema.org/OnlineEventAttendanceMode"
400+ location = VirtualLocation (url = event .url )
411401 else :
412402 # Physical event only
413- schema_data [ "eventAttendanceMode" ] = "https://schema.org/OfflineEventAttendanceMode"
403+ event_attendance_mode = "https://schema.org/OfflineEventAttendanceMode"
414404
415405 # Use structured address data if available, otherwise fall back to location string
416406 if event .city and event .country :
417- address = {
418- "@type" : "PostalAddress" ,
407+ address_kwargs = {
419408 "addressLocality" : event .city ,
420409 "addressCountry" : event .country
421410 }
422411 # Add state/region for US addresses if available
423412 if event .state :
424- address ["addressRegion" ] = event .state
413+ address_kwargs ["addressRegion" ] = event .state
425414
426415 # Use venue as Place name if available, otherwise use city
427416 place_name = event .venue if event .venue else event .city
428417
429- schema_data ["location" ] = {
430- "@type" : "Place" ,
431- "name" : place_name ,
432- "address" : address
433- }
418+ location = Place (
419+ name = place_name ,
420+ address = PostalAddress (** address_kwargs )
421+ )
434422 else :
435423 # Fall back to simple Place with just name
436- schema_data ["location" ] = {
437- "@type" : "Place" ,
438- "name" : event .location
439- }
440-
441- # Add event status - assume scheduled for future events
442- schema_data ["eventStatus" ] = "https://schema.org/EventScheduled"
424+ location = Place (name = event .location )
425+
426+ # Create the schema.org Event model - Pydantic will validate it
427+ schema_event = SchemaOrgEvent (
428+ name = event .title ,
429+ url = event .url ,
430+ startDate = start_date_iso ,
431+ endDate = end_date_iso ,
432+ location = location ,
433+ eventAttendanceMode = event_attendance_mode ,
434+ eventStatus = "https://schema.org/EventScheduled"
435+ )
443436
444- # Convert to pretty-printed JSON
445- return json .dumps (schema_data , indent = 2 )
437+ # Convert to JSON-LD format with pretty printing and add @context
438+ event_dict = json .loads (schema_event .json (exclude_none = True ))
439+ event_dict ["@context" ] = "https://schema.org"
440+ return json .dumps (event_dict , indent = 2 )
446441
447442@dataclass
448443class Course :
0 commit comments