Summary
The dns_data.Record model's name_in_zone field defaults to "" (empty string). When serialized via to_dict(), this empty string is always included in the request payload - even when absolute_name_spec is used instead. The BloxOne API interprets the presence of both fields as conflicting and rejects the request.
Environment
- SDK Version: universal-ddi-python-client (latest)
- Python Version: 3.14
- API Endpoint:
POST /api/ddi/v1/dns/record
Steps to Reproduce
import dns_data
from universal_ddi_client import ApiClient, Configuration
config = Configuration(
portal_url="https://csp.infoblox.com",
portal_key="YOUR_API_KEY",
)
with ApiClient(config) as client:
api = dns_data.RecordApi(client)
# Create an A record using absolute_name_spec + view
# (documented approach #2 in Record.md)
body = dns_data.Record(
absolute_name_spec="myhost.example.com.",
view="dns/view/<your-view-id>",
type="A",
rdata={"address": "10.0.0.1"},
)
# This fails with HTTP 400
result = api.create(body=body)
Expected Behavior
The record should be created successfully. Per the Record model documentation, creating a record with absolute_name_spec + view is a supported approach:
absolute_name_spec + view: The system looks for the appropriate zone in the provided view to create the DNS resource record object. The value of the zone field is automatically computed as part of this process.
Actual Behavior
The API returns HTTP 400:
{
"error": [
{
"message": "required record name fields are absent: either \"zone\" or \"name_in_zone\" and \"zone\" or \"absolute_name_spec\" and \"view\" should be present"
}
]
}
Root Cause
The Record model defines name_in_zone with a default value of "" (empty string):
# In dns_data/models/record.py
name_in_zone: Optional[StrictStr] = ""
When to_dict() is called during serialization, all fields - including those with default values - are included in the output:
rec = dns_data.Record(
absolute_name_spec="myhost.example.com.",
view="dns/view/some-id",
type="A",
rdata={"address": "10.0.0.1"},
)
print(rec.to_dict())
Output:
{
"absolute_name_spec": "myhost.example.com.",
"name_in_zone": "", # ← This should not be present
"rdata": {"address": "10.0.0.1"},
"type": "A",
"view": "dns/view/some-id"
}
The API receives both absolute_name_spec and name_in_zone: "" in the payload, which creates an ambiguous record name specification that the server rejects.
Additional context: @validate_call prevents workarounds
The RecordApi.create() method is decorated with Pydantic's @validate_call, which means:
- Passing a raw
dict as body triggers Pydantic validation, which converts it to a Record model — re-introducing the name_in_zone: "" default.
- Passing a
Record model constructed with name_in_zone=None also fails because to_dict() still includes the field (as None), and the sanitize_for_serialization method in ApiClient strips None values but not empty strings.
- Using
Record.model_construct() to bypass validation does not help because to_dict() still includes name_in_zone: "".
There is no way to use absolute_name_spec through the SDK without the conflicting name_in_zone field appearing in the serialized payload.
Verification: Direct API call works
The same payload works when sent directly via HTTP, confirming the issue is in the SDK serialization — not the API:
import json
import urllib.request
url = "https://csp.infoblox.com/api/ddi/v1/dns/record"
headers = {
"Authorization": "Token YOUR_API_KEY",
"Content-Type": "application/json",
}
body = json.dumps({
"absolute_name_spec": "myhost.example.com.",
"view": "dns/view/<your-view-id>",
"type": "A",
"rdata": {"address": "10.0.0.1"},
}).encode()
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read())) # Success
Suggested Fix
Change the default value of name_in_zone from "" to None in the Record model:
# Current (broken)
name_in_zone: Optional[StrictStr] = ""
# Proposed fix
name_in_zone: Optional[StrictStr] = None
With None as the default:
to_dict() returns None for unset fields
ApiClient.sanitize_for_serialization() excludes None values from the output
- The API receives only the fields that were explicitly set
This change would allow both documented record creation approaches to work through the SDK:
name_in_zone + zone (explicitly set, included in payload)
absolute_name_spec + view (name_in_zone defaults to None, excluded from payload)
Current Workaround
Bypass the SDK for DNS record create/update and use direct HTTP requests:
import json
import urllib.request
def create_dns_record(portal_url, api_key, body):
"""Create DNS record via direct API call to avoid SDK serialization issue."""
if body.get("absolute_name_spec"):
body.pop("name_in_zone", None)
body.pop("zone", None)
url = f"{portal_url.rstrip('/')}/api/ddi/v1/dns/record"
headers = {
"Authorization": f"Token {api_key}",
"Content-Type": "application/json",
}
req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers, method="POST")
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read()).get("result", {})
Summary
The
dns_data.Recordmodel'sname_in_zonefield defaults to""(empty string). When serialized viato_dict(), this empty string is always included in the request payload - even whenabsolute_name_specis used instead. The BloxOne API interprets the presence of both fields as conflicting and rejects the request.Environment
POST /api/ddi/v1/dns/recordSteps to Reproduce
Expected Behavior
The record should be created successfully. Per the Record model documentation, creating a record with
absolute_name_spec+viewis a supported approach:Actual Behavior
The API returns HTTP 400:
{ "error": [ { "message": "required record name fields are absent: either \"zone\" or \"name_in_zone\" and \"zone\" or \"absolute_name_spec\" and \"view\" should be present" } ] }Root Cause
The
Recordmodel definesname_in_zonewith a default value of""(empty string):When
to_dict()is called during serialization, all fields - including those with default values - are included in the output:Output:
{ "absolute_name_spec": "myhost.example.com.", "name_in_zone": "", # ← This should not be present "rdata": {"address": "10.0.0.1"}, "type": "A", "view": "dns/view/some-id" }The API receives both
absolute_name_specandname_in_zone: ""in the payload, which creates an ambiguous record name specification that the server rejects.Additional context:
@validate_callprevents workaroundsThe
RecordApi.create()method is decorated with Pydantic's@validate_call, which means:dictasbodytriggers Pydantic validation, which converts it to aRecordmodel — re-introducing thename_in_zone: ""default.Recordmodel constructed withname_in_zone=Nonealso fails becauseto_dict()still includes the field (asNone), and thesanitize_for_serializationmethod inApiClientstripsNonevalues but not empty strings.Record.model_construct()to bypass validation does not help becauseto_dict()still includesname_in_zone: "".There is no way to use
absolute_name_specthrough the SDK without the conflictingname_in_zonefield appearing in the serialized payload.Verification: Direct API call works
The same payload works when sent directly via HTTP, confirming the issue is in the SDK serialization — not the API:
Suggested Fix
Change the default value of
name_in_zonefrom""toNonein theRecordmodel:With
Noneas the default:to_dict()returnsNonefor unset fieldsApiClient.sanitize_for_serialization()excludesNonevalues from the outputThis change would allow both documented record creation approaches to work through the SDK:
name_in_zone+zone(explicitly set, included in payload)absolute_name_spec+view(name_in_zone defaults to None, excluded from payload)Current Workaround
Bypass the SDK for DNS record create/update and use direct HTTP requests: