Skip to content

Commit 33bbb54

Browse files
Added IPTrie library
Added IPTrie - A fast, type-safe data structure for IP lookups with longest-prefix matching. Supports both IPv4 and IPv6.
1 parent 856ce4c commit 33bbb54

File tree

7 files changed

+928
-3
lines changed

7 files changed

+928
-3
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ __pycache__/
99
*.egg
1010
*.pyc
1111
.hypothesis/
12-
.vscode/
12+
.vscode/
13+
.pytest_cache/
14+
.venv/

README.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[![PyPI version](https://badge.fury.io/py/ipdata.svg)](https://badge.fury.io/py/ipdata) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ipdata/python/python-publish.yml?branch=master)
22

3+
> **🎉 Introducing IPTrie** — A fast, type-safe data structure for IP lookups with longest-prefix matching. Supports both IPv4 and IPv6. [Learn more →](#iptrie)
4+
35
# Official Python client library and CLI for the ipdata API
46

57
This is a Python client and command line interface (CLI) for the [ipdata.co](https://ipdata.co) IP Geolocation API. ipdata offers a fast, highly-available API to enrich IP Addresses with Location, Company, Threat Intelligence and numerous other data attributes.
@@ -10,6 +12,43 @@ Visit our [Documentation](https://docs.ipdata.co/) for more examples and tutoria
1012

1113
[![asciicast](https://asciinema.org/a/371292.svg)](https://asciinema.org/a/371292)
1214

15+
## Table of Contents
16+
17+
- [Installation](#installation)
18+
- [Library Usage](#library-usage)
19+
- [Looking up the calling IP Address](#looking-up-the-calling-ip-address)
20+
- [Looking up any IP Address](#looking-up-any-ip-address)
21+
- [Getting only one field](#getting-only-one-field)
22+
- [Getting a number of specific fields](#getting-a-number-of-specific-fields)
23+
- [Bulk Lookups](#bulk-lookups)
24+
- [Using the ipdata CLI](#using-the-ipdata-cli)
25+
- [Windows Installation Notes](#windows-installation-notes)
26+
- [Available commands](#available-commands)
27+
- [Initialize the cli with your API Key](#initialize-the-cli-with-your-api-key)
28+
- [Look up your own IP address](#look-up-your-own-ip-address)
29+
- [Look up any IP address](#look-up-any-ip-address-1)
30+
- [Copying results to clipboard](#copying-results-to-clipboard)
31+
- [Filtering results by a list of fields](#filtering-results-by-a-list-of-fields)
32+
- [Batch lookup](#batch-lookup)
33+
- [Available Fields](#available-fields)
34+
- [Geofeed tools](#geofeed-tools)
35+
- [IPTrie](#iptrie)
36+
- [Features](#features)
37+
- [Quick Start](#quick-start)
38+
- [IPv6 Support](#ipv6-support)
39+
- [Use Cases](#use-cases)
40+
- [Network Classification](#network-classification)
41+
- [GeoIP Lookup](#geoip-lookup)
42+
- [Access Control Lists](#access-control-lists)
43+
- [API Reference](#api-reference)
44+
- [Constructor](#constructor)
45+
- [Methods](#methods)
46+
- [Exceptions](#exceptions)
47+
- [Input Validation](#input-validation)
48+
- [Performance](#performance)
49+
- [Errors](#errors)
50+
- [Tests](#tests)
51+
1352
## Installation
1453

1554
Install the latest version of the cli with `pip`.
@@ -525,6 +564,176 @@ or
525564
ipdata validate geofeed.txt
526565
```
527566
567+
## IPTrie
568+
569+
IPTrie is a production-ready, type-safe trie for IP addresses and CIDR prefixes with longest-prefix matching.
570+
571+
### Features
572+
573+
- **Dual-stack support**: Handles both IPv4 and IPv6 addresses seamlessly
574+
- **Longest-prefix matching**: Automatically finds the most specific matching prefix
575+
- **Type-safe**: Full generic type support with comprehensive type hints
576+
- **Pythonic API**: Familiar dictionary-like interface (`[]`, `in`, `del`, `len`, iteration)
577+
- **Input validation**: Robust validation using Python's `ipaddress` module
578+
- **Custom exceptions**: Clear, specific exceptions for better error handling
579+
- **Well-tested**: Comprehensive test suite with edge cases covered
580+
581+
### Quick Start
582+
583+
```python
584+
from ipdata import IPTrie
585+
586+
# Create an IPTrie with string values
587+
ip_trie: IPTrie[str] = IPTrie()
588+
589+
# Add network prefixes
590+
ip_trie["10.0.0.0/8"] = "class-a-private"
591+
ip_trie["10.1.0.0/16"] = "datacenter"
592+
ip_trie["10.1.1.0/24"] = "web-servers"
593+
594+
# Longest-prefix matching
595+
print(ip_trie["10.1.1.100"]) # "web-servers"
596+
print(ip_trie["10.1.2.100"]) # "datacenter"
597+
print(ip_trie["10.2.0.1"]) # "class-a-private"
598+
599+
# Check membership
600+
print("10.1.1.50" in ip_trie) # True
601+
print("192.168.1.1" in ip_trie) # False
602+
603+
# Get the matching prefix
604+
print(ip_trie.parent("10.1.1.100")) # "10.1.1.0/24"
605+
606+
# Safe access with default
607+
print(ip_trie.get("8.8.8.8", "unknown")) # "unknown"
608+
```
609+
610+
### IPv6 Support
611+
612+
```python
613+
ip_trie: IPTrie[str] = IPTrie()
614+
615+
ip_trie["2001:db8::/32"] = "documentation"
616+
ip_trie["2001:db8:1::/48"] = "specific-block"
617+
618+
print(ip_trie["2001:db8:1::1"]) # "specific-block"
619+
print(ip_trie["2001:db8:2::1"]) # "documentation"
620+
```
621+
622+
### Use Cases
623+
624+
#### Network Classification
625+
626+
```python
627+
from ipdata import IPTrie
628+
629+
classifier: IPTrie[dict] = IPTrie()
630+
classifier["10.0.0.0/8"] = {"type": "private", "rfc": "1918"}
631+
classifier["172.16.0.0/12"] = {"type": "private", "rfc": "1918"}
632+
classifier["192.168.0.0/16"] = {"type": "private", "rfc": "1918"}
633+
classifier["0.0.0.0/0"] = {"type": "public", "rfc": None}
634+
635+
def classify_ip(ip: str) -> dict:
636+
return classifier.get(ip, {"type": "unknown"})
637+
638+
print(classify_ip("192.168.1.100")) # {"type": "private", "rfc": "1918"}
639+
print(classify_ip("8.8.8.8")) # {"type": "public", "rfc": None}
640+
```
641+
642+
#### GeoIP Lookup
643+
644+
```python
645+
from ipdata import IPTrie
646+
647+
geo_db: IPTrie[str] = IPTrie()
648+
geo_db["8.8.8.0/24"] = "US"
649+
geo_db["1.1.1.0/24"] = "AU"
650+
651+
def get_country(ip: str) -> str:
652+
return geo_db.get(ip, "Unknown")
653+
```
654+
655+
#### Access Control Lists
656+
657+
```python
658+
from ipdata import IPTrie
659+
660+
acl: IPTrie[bool] = IPTrie()
661+
acl["192.168.1.0/24"] = True # Allow internal
662+
acl["10.0.0.0/8"] = True # Allow VPN
663+
acl["0.0.0.0/0"] = False # Deny all others
664+
665+
def is_allowed(ip: str) -> bool:
666+
return acl.get(ip, False)
667+
```
668+
669+
### API Reference
670+
671+
#### Constructor
672+
673+
```python
674+
IPTrie[T]() # Create an empty IPTrie with value type T
675+
```
676+
677+
#### Methods
678+
679+
| Method | Description |
680+
|--------|-------------|
681+
| `__setitem__(key, value)` | Set value for IP/prefix |
682+
| `__getitem__(key)` | Get value using longest-prefix match (raises `KeyNotFoundError`) |
683+
| `get(key, default=None)` | Get value or default if not found |
684+
| `__delitem__(key)` | Delete exact prefix |
685+
| `__contains__(key)` | Check if IP matches any prefix |
686+
| `has_key(key)` | Check if exact prefix exists |
687+
| `parent(key)` | Get the longest matching prefix string |
688+
| `children(key)` | Get all more specific prefixes |
689+
| `__len__()` | Count of all prefixes |
690+
| `__iter__()` | Iterate over all prefixes |
691+
| `keys()` | Iterator over prefixes |
692+
| `values()` | Iterator over values |
693+
| `items()` | Iterator over (prefix, value) tuples |
694+
| `clear()` | Remove all entries |
695+
696+
#### Exceptions
697+
698+
| Exception | Description |
699+
|-----------|-------------|
700+
| `IPTrieError` | Base exception class |
701+
| `InvalidIPError` | Invalid IP address or network format |
702+
| `KeyNotFoundError` | No matching prefix found (also a `KeyError`) |
703+
704+
### Input Validation
705+
706+
IPTrie validates all inputs using Python's `ipaddress` module:
707+
708+
```python
709+
from ipdata import IPTrie, InvalidIPError
710+
711+
ip_trie: IPTrie[str] = IPTrie()
712+
713+
# These work
714+
ip_trie["192.168.1.0/24"] = "valid"
715+
ip_trie["2001:db8::1"] = "valid"
716+
717+
# These raise InvalidIPError
718+
try:
719+
ip_trie["not-an-ip"] = "invalid"
720+
except InvalidIPError as e:
721+
print(f"Error: {e}")
722+
723+
try:
724+
ip_trie[""] = "empty"
725+
except InvalidIPError as e:
726+
print(f"Error: {e}")
727+
```
728+
729+
### Performance
730+
731+
IPTrie uses Patricia tries (via `pytricia`) internally, providing:
732+
733+
- **O(k)** lookup time where k is the prefix length (32 for IPv4, 128 for IPv6)
734+
- **Memory efficient** storage of overlapping prefixes
735+
- **Fast iteration** over all prefixes
736+
528737
## Errors
529738
530739
A list of possible errors is available at [Status Codes](https://docs.ipdata.co/api-reference/status-codes)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ click_default_group
55
pyperclip
66
pytest
77
hypothesis
8-
python-dotenv
8+
python-dotenv
9+
pytricia

setup.cfg

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = ipdata
3-
version = 4.0.7
3+
version = 4.0.8
44
author = Jonathan Kosgei
55
author_email = jonathan@ipdata.co
66
description = This is the official IPData client library for Python
@@ -11,6 +11,10 @@ project_urls =
1111
Bug Tracker = https://github.com/ipdata/python/issues
1212
classifiers =
1313
Programming Language :: Python :: 3.9
14+
Programming Language :: Python :: 3.10
15+
Programming Language :: Python :: 3.11
16+
Programming Language :: Python :: 3.12
17+
Programming Language :: Python :: 3.13
1418
License :: OSI Approved :: MIT License
1519
Operating System :: OS Independent
1620

@@ -28,6 +32,7 @@ install_requires =
2832
pytest
2933
hypothesis
3034
python-dotenv
35+
pytricia
3136

3237
[options.entry_points]
3338
console_scripts =

src/ipdata/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
>>> ipdata.lookup() # or ipdata.lookup("8.8.8.8")
88
"""
99
from .ipdata import IPData
10+
from .iptrie import IPTrie
1011

1112
# Configuration
1213
api_key = None

0 commit comments

Comments
 (0)