# netaddr-pydantic
[](https://pypi.python.org/pypi/netaddr-pydantic/)
[](https://pypi.python.org/pypi/netaddr-pydantic/)
[](https://github.com/Anvil/netaddr-pydantic/blob/main/LICENSE)
[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/pylint.yml)
[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/mypy.yml)
[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/python-app.yml)
Use [Netaddr](https://pypi.org/project/netaddr/) objects in [Pydantic](https://docs.pydantic.dev/latest/) Models
## Rational
### Origin of the issue.
The [ipaddress](https://docs.python.org/3/library/ipaddress.html) 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.
```python
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:
```python
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:
```python
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:
```python
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 `IPRange`s and/or `IPSet`s 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 `str`s or `int`s | `list` of `str`s. 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
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 `IPSet` if provided type is `list`, `tuple` or `set`
* an `IPNetwork` if value is a CIDR `str`
* an `IPRange` if value is an `str` containing a `-` character
* an `IPGlob` if value is an `str` containing a `*` char.
* an `IPAddress` in other cases.
Raw data
{
"_id": null,
"home_page": "https://github.com/Anvil/netaddr-pydantic/",
"name": "netaddr-pydantic",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "pydantic, netaddr",
"author": "Damien Nad\u00e9",
"author_email": "anvil.github+netaddr-pydantic@livna.org",
"download_url": "https://files.pythonhosted.org/packages/bd/5d/3030a7c3a3abc1d28647336fff3a0d8d93dbec12bafa957b029ffad71382/netaddr_pydantic-0.2.0.tar.gz",
"platform": null,
"description": "# netaddr-pydantic\n\n[](https://pypi.python.org/pypi/netaddr-pydantic/)\n[](https://pypi.python.org/pypi/netaddr-pydantic/)\n[](https://github.com/Anvil/netaddr-pydantic/blob/main/LICENSE)\n\n[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/pylint.yml)\n[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/mypy.yml)\n[](https://github.com/Anvil/netaddr-pydantic/actions/workflows/python-app.yml)\n\nUse [Netaddr](https://pypi.org/project/netaddr/) objects in [Pydantic](https://docs.pydantic.dev/latest/) Models\n\n\n## Rational\n\n### Origin of the issue.\n\nThe [ipaddress](https://docs.python.org/3/library/ipaddress.html) module supports Iternet Protocol addresses and networks but lacks support for funny objects such as ranges, IP sets, globbing and so on.\n\nThe `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.\n\n```python\nimport pydantic\n\nclass Model(pydantic.BaseModel):\n address: pydantic.IPvAnyAddress\n\n\nm = Model(address=\"1.2.3.4\")\nprint(type(m.address))\nprint(m.model_dump_json())\n```\n\nThis produces:\n\n```\n<class 'ipaddress.IPv4Address'>\n{\"address\":\"1.2.3.4\"}\n```\n\n\nUnfortunately, once the data is parsed, `ipaddress` objects cannot be inserted as-is to a `netaddr.IPSet` for example.\n\nAlternatively, you would want to switch to `netaddr`-typed fields with like this:\n\n\n```python\nimport pydantic\nimport netaddr\n\nclass Model(pydantic.BaseModel):\n address: netaddr.IPAddress\n```\n\nUnfortunately, `pydantic` cannot compile such thing:\n\n```\npydantic.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.\n\nIf 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.\n\nFor further information visit https://errors.pydantic.dev/2.11/u/schema-for-unknown-type\n```\n\nThis 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).\n\n\n### Should you use netaddr-pydantic?\n\n\nA 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.\n\nThis code:\n\n```python\nimport pydantic\nimport netaddr_pydantic\n\nclass Model(pydantic.BaseModel):\n address: netaddr_pydantic.IPAddress\n\nm = Model(address=\"1.2.3.4\")\nprint(type(m.address))\nprint(m.model_dump_json())\n```\n\nNaturally produces the following:\n\n```\n<class 'netaddr.ip.IPAddress'>\n{\"address\":\"1.2.3.4\"}\n```\n\nBasically, `netaddr-pydantic` just defines `Annotated` types with `pydantic` functional validators and serializers. For instance, `IPAddress` and `IPNetwork` are just defined this way:\n\n```python\nfrom typing import Annotated\nfrom pydantic import PlainValidator, PlainSerializer\nimport netaddr\n\nIPAddress = Annotated[\n netaddr.IPAddress, PlainValidator(netaddr.IPAddress), PlainSerializer(str)\n]\n\nIPNetwork = Annotated[\n netaddr.IPNetwork, PlainValidator(netaddr.IPNetwork), PlainSerializer(str)\n]\n```\n\nAnd by all means, if *this* is all you need, do not bother with `netaddr-pydantic`. But if you need to use `IPRange`s and/or `IPSet`s 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.\n\nPlus this is what *I* need in my own production environment, these days, so it will be maintained for a while.\n\n## Still there? OK, then let's see.\n\nThe `netaddr-pydantic` annotations are only really useful in a `pydantic` context. You can use plain `netaddr` in other places.\n\n### Supported objects and conversions\n\n\n| Object Types | Can be obtained from | Serialized as | Comment |\n| :----------- | :------------------: | :-----------: | :------ |\n| IPAddress | `str`, `int` | `str` | | \n| IPNetwork | `str`, 2-items `tuple` or `list` | a CIDR `str` | |\n| IPRange | `\"<start-ip>-<end-ip>\"` `str` or 2-items `list` or `tuple` | a `\"<start-ip>-<end-ip>\"` `str` | \n| IPSet | `list`, `tuple` or `set` of `str`s or `int`s | `list` of `str`s. 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. |\n| IPGlob | `str` | `str` | `netaddr` implementation seems to be limited to IPv4 \n\n\nThe 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.\n\n### Additionnal features\n\nThat 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\n\n* an `IPSet` if provided type is `list`, `tuple` or `set` \n* an `IPNetwork` if value is a CIDR `str`\n* an `IPRange` if value is an `str` containing a `-` character\n* an `IPGlob` if value is an `str` containing a `*` char.\n* an `IPAddress` in other cases.\n",
"bugtrack_url": null,
"license": "LGPL-2.1-or-later",
"summary": null,
"version": "0.2.0",
"project_urls": {
"Documentation": "https://github.com/Anvil/netaddr-pydantic/README.md",
"Homepage": "https://github.com/Anvil/netaddr-pydantic/",
"Issues": "https://github.com/Anvil/netaddr-pydantic/issues",
"Repository": "https://github.com/Anvil/netaddr-pydantic/"
},
"split_keywords": [
"pydantic",
" netaddr"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "408490f222fab9ee9c089f552c81d9c44418ed3faeb450313e1b5ac134b063d1",
"md5": "308c6019a2449854ddb376e08790cdf1",
"sha256": "268d329f6ae13d93ac186448606b8e979648a17ab7a0f6145e20940251228fab"
},
"downloads": -1,
"filename": "netaddr_pydantic-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "308c6019a2449854ddb376e08790cdf1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 14919,
"upload_time": "2025-07-23T16:37:53",
"upload_time_iso_8601": "2025-07-23T16:37:53.575146Z",
"url": "https://files.pythonhosted.org/packages/40/84/90f222fab9ee9c089f552c81d9c44418ed3faeb450313e1b5ac134b063d1/netaddr_pydantic-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "bd5d3030a7c3a3abc1d28647336fff3a0d8d93dbec12bafa957b029ffad71382",
"md5": "5b55f22a02e3f4d609988ea924f577c9",
"sha256": "4c3247e3e2684308267e954d113ff0312ca2e8f7fcc9557ba93225aaad1b1fd6"
},
"downloads": -1,
"filename": "netaddr_pydantic-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "5b55f22a02e3f4d609988ea924f577c9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 14122,
"upload_time": "2025-07-23T16:37:54",
"upload_time_iso_8601": "2025-07-23T16:37:54.889178Z",
"url": "https://files.pythonhosted.org/packages/bd/5d/3030a7c3a3abc1d28647336fff3a0d8d93dbec12bafa957b029ffad71382/netaddr_pydantic-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-23 16:37:54",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Anvil",
"github_project": "netaddr-pydantic",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "netaddr-pydantic"
}