Use Netaddr objects in Pydantic Models
Project description
netaddr-pydantic
Use Netaddr objects in Pydantic Models
Rational
Origin of the issue.
The ipaddress module supports Iternet Protocol addresses and networks but lacks support for funny objects such as ranges, IP sets, globbing and so on.
The Pydantic framework provides out-of-the-box support for IPv4/IPv6 addresses and networks through the ipaddress module and this allows you to easily validate or serialize data from/to interfaces.
import pydantic
class Model(pydantic.BaseModel):
address: pydantic.IPvAnyAddress
m = Model(address="1.2.3.4")
print(type(m.address))
print(m.model_dump_json())
This produces:
<class 'ipaddress.IPv4Address'>
{"address":"1.2.3.4"}
Unfortunately, once the data is parsed, ipaddress objects cannot be inserted as-is to a netaddr.IPSet for example.
Alternatively, you would want to switch to netaddr-typed fields with like this:
import pydantic
import netaddr
class Model(pydantic.BaseModel):
address: netaddr.IPAddress
Unfortunately, pydantic cannot compile such thing:
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for <class 'netaddr.ip.IPAddress'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
For further information visit https://errors.pydantic.dev/2.11/u/schema-for-unknown-type
This is due to the lack of pydantic metadata in netaddr classes. Mainly, pydantic needs to know how to validate (from basic types, such as strings and integers) and how to to serialize the objects (return the basic types).
Should you use netaddr-pydantic?
A way to fix this issue is to write your own validators and serializers and if you're lazy enough (no judgement here :]), this is just what netaddr-pydantic is bringing on the table.
This code:
import pydantic
import netaddr_pydantic
class Model(pydantic.BaseModel):
address: netaddr_pydantic.IPAddress
m = Model(address="1.2.3.4")
print(type(m.address))
print(m.model_dump_json())
Naturally produces the following:
<class 'netaddr.ip.IPAddress'>
{"address":"1.2.3.4"}
Basically, netaddr-pydantic just defines Annotated types with pydantic functional validators and serializers. For instance, IPAddress and IPNetwork are just defined this way:
from typing import Annotated
from pydantic import PlainValidator, PlainSerializer
import netaddr
IPAddress = Annotated[
netaddr.IPAddress, PlainValidator(netaddr.IPAddress), PlainSerializer(str)
]
IPNetwork = Annotated[
netaddr.IPNetwork, PlainValidator(netaddr.IPNetwork), PlainSerializer(str)
]
And by all means, if this is all you need, do not bother with netaddr-pydantic. But if you need to use IPRanges and/or IPSets as well then maybe you should use netaddr-pydantic, because, while IPrange and IPSet validators and serializers are just a very little bit more complex, I dont feel they are worth repeating.
Plus this is what I need in my own production environment, these days, so it will be maintained for a while.
Still there? OK, then let's see.
The netaddr-pydantic annotations are only really useful in a pydantic context. You can use plain netaddr in other places.
Supported objects and conversions
| Object Types | Can be obtained from | Serialized as | Comment |
|---|---|---|---|
| IPAddress | str, int |
str |
|
| IPNetwork | str, 2-items tuple or list |
a CIDR str |
|
| IPRange | "<start-ip>-<end-ip>" str or 2-items list or tuple |
a "<start-ip>-<end-ip>" str |
|
| IPSet | list, tuple or set of strs or ints |
list of strs. The IPSet.iter_cidrs is used to compute the items of the list. |
If you do not want to work with IPSet, use list[IPAddress | IPNetwork], or similar. |
| IPGlob | str |
str |
netaddr implementation seems to be limited to IPv4 |
The validation relies mostly on netaddr objects constructors. There's currently no bijection possible from the validated format to the serialized format. I do not intend to implement it at this time.
Additionnal features
IPAny
That may not be much, but an IPAny type is available. pydantic should produce the most acccurate object depending of the source object. An IPAny field will produce
- an
IPSetif provided type islist,tupleorset - an
IPNetworkif value is a CIDRstr - an
IPRangeif value is anstrcontaining a-character - an
IPGlobif value is anstrcontaining a*char. - an
IPAddressin other cases.
IPv4Address / IPv6Address
In case you want to match only IPv4 or IPv6 addresses (although, for portability, I suggest you don't), you can use these Internet Protocol version specific types annotations.
For instance, the following code:
import pydantic
import netaddr_pydantic
class Model(pydantic.BaseModel):
address: netaddr_pydantic.IPv4Address
m4 = Model(address="1.2.3.4")
print(f"IPv4 has been validated: {m4.address}")
try:
m6 = Model(address="dead:b00b:cafe::1")
except pydantic.ValidationError:
print("IPv6 address did not pass:")
raise
Produces the following:
IPv4 has been validated: 1.2.3.4
IPv6 address did not pass:
Traceback (most recent call last):
File "t.py", line 11, in <module>
m6 = Model(address="dead:b00b:cafe::1")
File "/env/lib/python3.13/site-packages/pydantic/main.py", line 253, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Model
address
Value error, base address 'dead:b00b:cafe::1' is not IPv4 [type=value_error, input_value='dead:b00b:cafe::1', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/value_error
IPv4Network / IPv6Network
Well, duh. :unamused:
import pydantic
import netaddr_pydantic
class Model(pydantic.BaseModel):
network: netaddr_pydantic.IPv6Network
m6 = Model(network="dead:b00b:cafe::/64")
print(f"IPv6 CIDR has been validated: {m6.network}")
try:
m4 = Model(network="1.2.3.0/24")
except pydantic.ValidationError:
print("IPv4 CIDR did not pass:")
raise
:interrobang:
:expressionless:
IPv6 CIDR has been validated: dead:b00b:cafe::/64
IPv4 CIDR did not pass:
Traceback (most recent call last):
File "t.py", line 11, in <module>
m4 = Model(network="1.2.3.0/24")
File "/env/lib/python3.13/site-packages/pydantic/main.py", line 253, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Model
network
Value error, base address '1.2.3.0' is not IPv6 [type=value_error, input_value='1.2.3.0/24', input_type=str]
For further information visit https://errors.pydantic.dev/2.11/v/value_error
:boom: :bangbang:
:tada:
:smirk:
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file netaddr_pydantic-0.2.1.tar.gz.
File metadata
- Download URL: netaddr_pydantic-0.2.1.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.5 Linux/6.15.5-200.fc42.x86_64
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ac50e88b939b43a2d1e842c454805438957e844f3eaaaa407e7330e7b554fdd
|
|
| MD5 |
e2832efd276b6e1689585bcc2814a2e0
|
|
| BLAKE2b-256 |
b2179b4a323bdcbcf97cd22c4126004090b4b6fb2cb653e10d3ed96b78c65ea1
|
File details
Details for the file netaddr_pydantic-0.2.1-py3-none-any.whl.
File metadata
- Download URL: netaddr_pydantic-0.2.1-py3-none-any.whl
- Upload date:
- Size: 15.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.3 CPython/3.13.5 Linux/6.15.5-200.fc42.x86_64
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7efac4dbc836024b32c91e42c7c7866a6eff1f4b7b9205cce48ef131dd15cead
|
|
| MD5 |
a1f802326e32442987975c247fcc8da3
|
|
| BLAKE2b-256 |
a884997f3faf75763fc0a56c0cc2e69d018a86c74029f7c8457b6f9bd9e55039
|