Skip to content

Commit 9e1644f

Browse files
committed
Replace JavaScript sdtt with Python pydantic2-schemaorg for schema.org validation
1 parent dbe8fd0 commit 9e1644f

File tree

6 files changed

+45
-1041
lines changed

6 files changed

+45
-1041
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ jobs:
3232
# TODO: fix things so we can build with 3.12 and above
3333
python-version: 3.11
3434

35-
- name: setup Node.js
36-
uses: actions/setup-node@v6
37-
with:
38-
node-version: '24'
39-
4035
- name: install bibtool
4136
run: |
4237
sudo apt-get update --fix-missing
@@ -45,9 +40,6 @@ jobs:
4540
- name: install Python dependencies
4641
run: python -m pip install --upgrade pip -r requirements.txt
4742

48-
- name: install Node.js dependencies
49-
run: npm ci
50-
5143
- name: build and deploy
5244
id: build
5345
run: |
@@ -60,9 +52,6 @@ jobs:
6052
github_ref: ${{ github.ref }}
6153
ZULIP_KEY: ${{ secrets.ZULIP_KEY }}
6254

63-
- name: validate semantic data in events.html
64-
run: npm run validate:events
65-
6655
- name: Upload artifact
6756
id: upload-artifact
6857
if: ${{ !cancelled() && (steps.build.outcome == 'success') }}

.github/workflows/build_pr.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,6 @@ jobs:
3939
run: |
4040
./make_site.py
4141
42-
- name: setup Node.js
43-
if: ${{ !cancelled() && (steps.build.outcome == 'success') }}
44-
uses: actions/setup-node@v6
45-
with:
46-
node-version: '24'
47-
48-
- name: install Node.js dependencies
49-
if: ${{ !cancelled() && (steps.build.outcome == 'success') }}
50-
run: npm ci
51-
52-
- name: validate semantic data in events.html
53-
if: ${{ !cancelled() && (steps.build.outcome == 'success') }}
54-
run: npm run validate:events
55-
5642
- name: Upload artifact
5743
id: upload-artifact
5844
if: ${{ !cancelled() && (steps.build.outcome == 'success') }}

make_site.py

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131

3232
import 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+
3439
FilePath = Union[str, Path]
3540

3641
DOWNLOAD = not 'NODOWNLOAD' in os.environ
@@ -351,7 +356,7 @@ def validate(self) -> None:
351356
)
352357

353358
def 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
448443
class Course:

0 commit comments

Comments
 (0)