fix: convert BDAY datetime.date to string before Pydantic validation

pythonvCard4 parses vCard BDAY fields into datetime.date objects, but
the Contact model expects Optional[str]. This caused a validation error
that crashed the entire contact list. Convert at the client layer
(consistent with the calendar client pattern) with a defensive check
at the server mapping layer.

Closes #672

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-04-01 09:11:45 +02:00
co-authored by Claude Opus 4.6
parent 97ae750916
commit da380a38c6
3 changed files with 50 additions and 2 deletions
+4 -1
View File
@@ -2,6 +2,7 @@
import logging
import xml.etree.ElementTree as ET
from datetime import date
from pythonvCard4.vcard import Contact
@@ -272,7 +273,9 @@ class ContactsClient(BaseNextcloudClient):
"contact": {
"fullname": contact.fn,
"nickname": contact.nickname,
"birthday": contact.bday,
"birthday": contact.bday.isoformat()
if isinstance(contact.bday, date)
else contact.bday,
"email": contact.email,
"tel": contact.tel,
},
+4 -1
View File
@@ -1,4 +1,5 @@
import logging
from datetime import date
from typing import Any
from mcp.server.fastmcp import Context, FastMCP
@@ -83,7 +84,9 @@ def _raw_contact_to_model(raw: dict) -> Contact:
uid=raw["vcard_id"],
fn=contact_info.get("fullname", ""),
etag=raw.get("getetag"),
birthday=contact_info.get("birthday"),
birthday=contact_info["birthday"].isoformat()
if isinstance(contact_info.get("birthday"), date)
else contact_info.get("birthday"),
emails=emails,
phones=phones,
custom_fields=custom_fields,
+42
View File
@@ -1,5 +1,7 @@
"""Unit tests for Pydantic response models."""
from datetime import date
import pytest
from nextcloud_mcp_server.models.contacts import (
@@ -310,6 +312,46 @@ def test_contact_mapping_preserves_email_birthday_nickname():
assert contact.custom_fields["nickname"] == "JD"
@pytest.mark.unit
def test_contact_mapping_birthday_datetime_date_object():
"""Test that a datetime.date birthday is converted to ISO string.
Regression test for GH #672: pythonvCard4 returns datetime.date objects
for BDAY fields, which caused Pydantic validation errors.
"""
raw_contact = {
"vcard_id": "bday-date-1",
"contact": {
"fullname": "Date Object",
"birthday": date(1990, 5, 15),
},
}
contact = _map_contact(raw_contact)
assert contact.birthday == "1990-05-15"
@pytest.mark.unit
def test_contact_mapping_birthday_apple_unknown_year():
"""Test Apple/iOS unknown-year birthday convention (year 1604).
Apple contacts use BDAY;VALUE=DATE:16040808 when the birth year is unknown.
pythonvCard4 parses this as datetime.date(1604, 8, 8).
"""
raw_contact = {
"vcard_id": "bday-apple-1",
"contact": {
"fullname": "Apple Contact",
"birthday": date(1604, 8, 8),
},
}
contact = _map_contact(raw_contact)
assert contact.birthday == "1604-08-08"
@pytest.mark.unit
def test_contact_mapping_multiple_emails():
"""Test that multiple emails are mapped correctly."""