{"id":18244015,"url":"https://github.com/jsommers/pytricia","last_synced_at":"2025-04-04T06:06:26.889Z","repository":{"id":4287931,"uuid":"5417789","full_name":"jsommers/pytricia","owner":"jsommers","description":"A library for fast IP address lookup in Python.","archived":false,"fork":false,"pushed_at":"2022-09-25T21:12:06.000Z","size":4337,"stargazers_count":222,"open_issues_count":7,"forks_count":23,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-28T05:09:01.864Z","etag":null,"topics":["ipv4","ipv6","lookup","networking","python","python3"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsommers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-08-14T19:52:18.000Z","updated_at":"2025-03-25T14:18:21.000Z","dependencies_parsed_at":"2022-08-02T10:35:34.563Z","dependency_job_id":null,"html_url":"https://github.com/jsommers/pytricia","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsommers%2Fpytricia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsommers%2Fpytricia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsommers%2Fpytricia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsommers%2Fpytricia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsommers","download_url":"https://codeload.github.com/jsommers/pytricia/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247128744,"owners_count":20888235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ipv4","ipv6","lookup","networking","python","python3"],"created_at":"2024-11-05T09:04:49.061Z","updated_at":"2025-04-04T06:06:26.871Z","avatar_url":"https://github.com/jsommers.png","language":"C","funding_links":["https://www.buymeacoffee.com/joelsommers"],"categories":[],"sub_categories":[],"readme":"# pytricia: an IP address lookup module for Python\n\nPytricia is a new python module to store IP prefixes in a patricia tree. \nIt's based on Dave Plonka's modified patricia tree code, and has three things \nto recommend it over related modules (including py-radix and SubnetTree): \n\n 1. it's faster (see below),\n 2. it works in Python 3, and \n 3. there are a few nicer library features for manipulating the structure.\n\nCopyright (c) 2012-2022  Joel Sommers.  All rights reserved.\n\nPytricia is released under terms of the GNU Lesser General Public License,\nversion 3.0 and greater.\n\n## Support further development of Pytricia\n\nI originally wrote this code with funding from the US National Science Foundation.  Development since 2016 has been on an \"as I have time and motivation\" basis.  If you or your organization gets benefit from this software and you'd like to see further development, [please consider donating](https://www.buymeacoffee.com/joelsommers).\n\n\n# Building \n\nBuilding pytricia is done in the standard pythonic way: \n\n    python setup.py build\n    python setup.py install\n\nThis code is beta quality at present but has been tested on OS X 10.11 and Ubuntu 14.04 (both 64 bit) and Python 2.7.6 and Python 3.6.1.\n\n[![Build Status](https://travis-ci.org/jsommers/pytricia.svg?branch=master)](https://travis-ci.org/jsommers/pytricia)    \n\n[![Research software impact](http://depsy.org/api/package/pypi/pytricia/badge.svg)](http://depsy.org/package/python/pytricia)\n\n# Examples\n\nCreate a pytricia object and load a couple prefixes into it:\n\n    \u003e\u003e\u003e import pytricia\n    \u003e\u003e\u003e pyt = pytricia.PyTricia()\n    \u003e\u003e\u003e pyt[\"10.0.0.0/8\"] = 'a'\n    \u003e\u003e\u003e pyt[\"10.1.0.0/16\"] = 'b'\n    \u003e\u003e\u003e len(pyt)\n    2\n    \u003e\u003e\u003e \n\nThe ``PyTricia`` class takes an optional parameter, which is the maximum number of bits to consider when constructing the trie.  By default, the number of bits is 32.  For IPv6, you can set this value higher (up to 128):\n\n    \u003e\u003e\u003e import pytricia\n    \u003e\u003e\u003e pyt = pytricia.PyTricia(128)\n    \u003e\u003e\u003e pyt[\"fe80::/64\"] = 'a'\n    \u003e\u003e\u003e pyt[\"dead::/32\"] = 'b'\n    \u003e\u003e\u003e len(pyt)\n    2\n    \u003e\u003e\u003e \n\nIP prefixes and addresses can be expressed in a few different ways:\n  * The most obvious way is as a string (as in the examples above).  \n  * For IPv4, an integer may also be used (just for an address, not a prefix, somewhat obviously).\n  * A bytes object may also be used, with a length of 4 bytes (IPv4) or 16 bytes (IPv6).  As with using an int for IPv4, this option is mostly useful for expressing an individual address, not a prefix.\n  * For Python 3.4 and later, an address or network using the ``ipaddress`` module can also be used.  In particular, ``IPv4Address`` and ``IPv4Network`` objects can be used, as well as ``IPv6Address`` and ``IPv4Network``.\n\nThe ``insert`` method can also be used to add prefixes/values to a PyTricia object.  This method returns ``None``.\n\n    \u003e\u003e\u003e pyt.insert(\"10.2.0.0/16\", \"c\")\n\nThe ``insert`` method can optionally accept three parameters, where the first parameter is an address, the second parameter is the prefix length, and the third parameter is some object to be associated with the network prefix:\n\n    \u003e\u003e\u003e import pytricia\n    \u003e\u003e\u003e from ipaddress import IPv6Address, IPv6Network\n    \u003e\u003e\u003e pyt = pytricia.PyTricia(128)\n    \u003e\u003e\u003e pyt.insert(IPv6Address(\"2001:218:200e:abc::1\"), 56, \"hello!\")\n    \u003e\u003e\u003e pyt.insert(IPv6Network(\"2001:218:200e::/56\"), \"halo!\")    \n    \u003e\u003e\u003e pyt.insert(bytes([10,0,1,0]), 24, \"ip?\")\n    \u003e\u003e\u003e pyt.keys()\n    ['10.0.1.0/24', '2001:218:200e::/56', '2001:218:200e:abc::1/56']\n    \u003e\u003e\u003e \n\nUse standard dictionary-like access to do longest prefix match lookup:\n\n    \u003e\u003e\u003e pyt[\"10.0.0.0/8\"]\n    a\n    \u003e\u003e\u003e pyt[\"10.1.0.0/16\"]\n    b\n    \u003e\u003e\u003e pyt[\"10.1.0.0/24\"]\n    b\n\nAlternatively, use the ``get`` method:\n\n    \u003e\u003e\u003e pyt.get(\"10.1.0.0/16\")\n    'b'\n    \u003e\u003e\u003e pyt.get(\"10.1.0.0/24\")\n    'b'\n    \u003e\u003e\u003e pyt.get(\"10.1.0.0/32\")\n    'b'\n    \u003e\u003e\u003e pyt.get(\"10.0.0.0/24\")\n    'a'\n\nIf you want access to the key instead (i.e., the longest matching prefix), use ``get_key``:\n\n    \u003e\u003e\u003e pyt.get_key(\"10.1.0.0/16\")\n    '10.1.0.0/16'\n    \u003e\u003e\u003e pyt.get_key(\"10.1.0.0/24\")\n    '10.1.0.0/16'\n    \u003e\u003e\u003e pyt.get_key(\"10.1.0.0/32\")\n    '10.1.0.0/16'\n    \u003e\u003e\u003e pyt.get_key(\"10.0.0.0/24\")\n    '10.0.0.0/8'\n\nThe ``del`` operator works as it does with Python dictionaries (and there is also a ``delete`` method that works similarly):\n\n    \u003e\u003e\u003e del pyt[\"10.0.0.0/8\"]\n    \u003e\u003e\u003e pyt.get(\"10.1.0.0/16\")\n    'b'\n    \u003e\u003e\u003e del pyt[\"10.0.0.0/8\"]\n    Traceback (most recent call last):\n      File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n    KeyError: \"Prefix doesn't exist.\"\n    \u003e\u003e\u003e pyt.delete(\"10.2.0.0/16\")\n    \u003e\u003e\u003e\n\n``PyTricia`` objects can be iterated or coerced into a list:\n\n    \u003e\u003e\u003e list(pyt)\n    ['10.0.0.0/8', '10.1.0.0/16']\n\nThe ``in`` operator can be used to test whether a prefix is contained in the ``PyTricia`` object, or whether an individual address is \"covered\" by a prefix:\n\n    \u003e\u003e\u003e '10.0.0.0/8' in pyt\n    True\n    \u003e\u003e\u003e '10.2.0.0/8' in pyt\n    True\n    \u003e\u003e\u003e '192.168.0.0/16' in pyt\n    False\n    \u003e\u003e\u003e '192.168.0.0' in pyt\n    False\n    \u003e\u003e\u003e '10.1.2.3' in pyt\n    True\n    \u003e\u003e\u003e \n\nThe ``has_key`` method is also implement, but it's important to note that the behavior of ``in`` differs from ``has_key``.  The ``has_key`` method checks for an *exact match* of a network prefix.  The ``in`` operator checks whether the left-hand operand (i.e., an IP address) is contained within one of the prefixes in the ``PyTricia`` object.  The ``get`` method and the indexing operation (``[]``) (each described above) have lookup behavior similar like the ``in`` operator --- they do *not* search for an exact match, but rather for the most closely matching prefix.  For example:\n\n    \u003e\u003e\u003e pyt.has_key('10.1.0.0/16')\n    True\n    \u003e\u003e\u003e pyt.has_key('10.1.0.0')\n    False\n    \u003e\u003e\u003e pyt.has_key('10.0.0.0/8')\n    True\n    \u003e\u003e\u003e pyt.has_key('10.0.0.0')\n    False\n    \u003e\u003e\u003e pyt.has_key('10.0.0.0/12')\n    False\n    \u003e\u003e\u003e '10.0.0.0/12' in pyt\n    True\n    \u003e\u003e\u003e '10.0.0.0' in pyt\n    True\n    \u003e\u003e\u003e '10.0.0.0/8' in pyt\n    True\n    \u003e\u003e\u003e \n\nIt is also possible to find the ``parent`` and ``children`` of a given prefix in the tree.  Similarly to the ``has_key`` method, the prefix must be present as an exact match in the tree.  For instance:\n\n    \u003e\u003e\u003e pyt.parent('10.1.0.0/16')\n    '10.0.0.0/8'\n    \u003e\u003e\u003e pyt.parent('10.0.0.0/8')\n    None\n    \u003e\u003e\u003e pyt[\"10.1.1.0/24\"] = 'c'\n    \u003e\u003e\u003e pyt.children('10.0.0.0/8')\n    ['10.1.0.0/16', '10.1.1.0/24']\n    \u003e\u003e\u003e pyt.children('10.1.0.0/16')\n    ['10.1.1.0/24']\n    \u003e\u003e\u003e pyt.children('10.1.1.0/24')\n    []\n    \u003e\u003e\u003e pyt.parent('10.1.42.0/24')\n    Traceback (most recent call last):\n      File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n    KeyError: Prefix doesn't exist.\n\nIf you want to get the longest matching prefix for arbitrary prefixes, you should use ``get_key``, not ``parent``.\n\nA ``PyTricia`` object is *almost* like a dictionary, but not quite.   You can extract the keys, but not the values:\n\n    \u003e\u003e\u003e pyt.keys()\n    ['10.0.0.0/8', '10.1.0.0/16']\n    \u003e\u003e\u003e pyt.values()\n    Traceback (most recent call last):\n      File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n    AttributeError: 'pytricia.PyTricia' object has no attribute 'values'\n\nAs with a dictionary, you can iterate over a ``PyTricia`` object.  Currently, there's no ``items()``-like method for iterating over both keys and values; you can just iterate over keys (network prefixes).\n\n    \u003e\u003e for prefix in pyt:\n    ...     print (prefix,pyt[prefix])\n    ... \n    10.0.0.0/8 a\n    10.1.0.0/16 b\n    \u003e\u003e\u003e \n\nAlthough it is possible to store IPv4 and IPv6 subnets in the same trie, this is generally not advisable. Consider the following example:\n\n    \u003e\u003e\u003e import pytricia\n    \u003e\u003e\u003e pyt = pytricia.PyTricia(128)\n    \u003e\u003e\u003e pyt.insert('2000::/8', 'test')\n    \u003e\u003e\u003e pyt.get_key('32.0.0.1')\n    '2000::/8'\n\nIPv4 address `32.0.0.1` matches `2000::/8` prefix due to the first octet being the same in both. In order to avoid this, separate tries should be used for IPv4 and IPv6 prefixes. Alternatively, [IPv4 addresses can be mapped to IPv6 addresses](https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses).\n\n# Performance\n\nFor API usage, the usual Python advice applies: using indexing is the fastest method for insertion, lookup, and removal.  See the ``apiperf.py`` script in the repo for some comparative numbers.  For Python 3, using ``ipaddress``-module objects is the slowest.  There's a price to pay for the convenience, unfortunately.\n\nThe numbers below are based on running the program ``perftest.py`` (in the repo) against snapshots of py-radix and pysubnettree from February 2, 2016.  All tests were run in Python 2.7.6 and 3.4.3 on a Linux 3.13 kernel system (Ubuntu 14.04 server) which has 12 cores (Intel Xeon E5645 2.4GHz) and was very lightly loaded at the time of the test.\n\n    $ python perftest.py \n    Average execution time for PyTricia: 0.902257204056\n    Average execution time for radix: 1.09275889397\n    Average execution time for subnet: 0.984920787811\n\n    $ python3 perftest.py \n    Average execution time for PyTricia: 1.0562857019998773\n    Average execution time for radix: 1.306612914499965\n    Average execution time for subnet: 1.1982004833000246\n\n# Acknowledgments\n\nThis software is based up on work supported by the National Science Foundation under Grant No. CNS-1054985.  Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsommers%2Fpytricia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsommers%2Fpytricia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsommers%2Fpytricia/lists"}