pybgpkit 0.6.2__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: pybgpkit
3
+ Version: 0.8.0
4
+ Summary: BGPKIT tools Python bindings
5
+ Author-email: Mingwei Zhang <mingwei@bgpkit.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/bgpkit/pybgpkit
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: pybgpkit-parser>=0.18.0
17
+ Requires-Dist: requests
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # PyBGPKIT
23
+
24
+ Python bindings for BGPKIT software. For all software offerings, please check out our GitHub
25
+ repository at <https://github.com/bgpkit>.
26
+
27
+ ## SDKs
28
+
29
+ ### BGPKIT Parser
30
+
31
+ See the [bgpkit-parser-py documentation](https://github.com/bgpkit/bgpkit-parser-py) for the full API.
32
+
33
+ ```python
34
+ import bgpkit
35
+
36
+ # Full element parsing
37
+ parser = bgpkit.Parser(url="https://spaces.bgpkit.org/parser/update-example",
38
+ filters={"peer_ips": "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1"})
39
+ for elem in parser:
40
+ print(elem)
41
+
42
+ # Route-level parsing (faster, fewer fields)
43
+ for route in bgpkit.RouteParser(url="https://spaces.bgpkit.org/parser/update-example"):
44
+ print(route.prefix, route.as_path)
45
+
46
+ # Reusable filter objects
47
+ from bgpkit import Filter
48
+ f = Filter.peer_ip("185.1.8.65")
49
+ parser = bgpkit.Parser.from_filters(url, [f])
50
+ ```
51
+
52
+
53
+
54
+ ### BGPKIT Broker
55
+
56
+ ```python
57
+ import bgpkit
58
+ broker = bgpkit.Broker()
59
+
60
+ # Search MRT data files
61
+ items = broker.query(ts_start="2024-01-01T00:00:00Z", ts_end="2024-01-01T01:00:00Z")
62
+ print(len(items))
63
+
64
+ # Get latest files
65
+ latest = broker.latest()
66
+
67
+ # Query peer information
68
+ peers = broker.peers(asn=13335, full_feed=True)
69
+
70
+ # List collectors
71
+ collectors = broker.collectors(project="routeviews", active=True)
72
+ ```
73
+
74
+ ### BGPKIT IP Lookup
75
+
76
+ ```python
77
+ ip_info = bgpkit.IpLookup().query("1.1.1.1")
78
+ print(ip_info.country, ip_info.as_number, ip_info.as_name)
79
+ ```
80
+
81
+ ### BGPKIT ASN Lookup
82
+
83
+ ```python
84
+ result = bgpkit.AsnLookup().query(asn="13335", country="US")
85
+ for asn in result.data:
86
+ print(asn.asn, asn.name, asn.country)
87
+ ```
88
+
89
+ ### BGPKIT Community Lookup
90
+
91
+ ```python
92
+ entries = bgpkit.CommunityLookup().query(asn="13335", page_size=50)
93
+ for entry in entries:
94
+ print(entry.value, entry.description)
95
+
96
+ sources = bgpkit.CommunityLookup().sources()
97
+ ```
98
+
99
+ ### BGPKIT ROAS Lookup
100
+
101
+ ### BGPKIT ROAS Lookup
102
+
103
+ ```python
104
+ roas = bgpkit.Roas()
105
+ data = roas.query(asn=3333, date="2018-01-01")
106
+ for entry in data:
107
+ print(entry.prefix, entry.asn, entry.tal)
108
+
109
+ # Current ROAs only
110
+ current = roas.query(prefix="1.1.1.0/24", current=True)
111
+ ```
112
+
113
+ ## Build and Publish
114
+
115
+ Push a version tag to build and publish automatically via GitHub Actions:
116
+
117
+ ```
118
+ git tag v0.7.0
119
+ git push origin v0.7.0
120
+ ```
121
+
122
+ Manual release:
123
+
124
+ ```
125
+ python -m pip install --upgrade build twine
126
+ python -m build
127
+ twine upload --skip-existing dist/*
128
+ ```
129
+
130
+ PyPI Trusted Publishing is configured for automated releases. See `.github/workflows/release.yml`.
131
+
@@ -0,0 +1,110 @@
1
+ # PyBGPKIT
2
+
3
+ Python bindings for BGPKIT software. For all software offerings, please check out our GitHub
4
+ repository at <https://github.com/bgpkit>.
5
+
6
+ ## SDKs
7
+
8
+ ### BGPKIT Parser
9
+
10
+ See the [bgpkit-parser-py documentation](https://github.com/bgpkit/bgpkit-parser-py) for the full API.
11
+
12
+ ```python
13
+ import bgpkit
14
+
15
+ # Full element parsing
16
+ parser = bgpkit.Parser(url="https://spaces.bgpkit.org/parser/update-example",
17
+ filters={"peer_ips": "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1"})
18
+ for elem in parser:
19
+ print(elem)
20
+
21
+ # Route-level parsing (faster, fewer fields)
22
+ for route in bgpkit.RouteParser(url="https://spaces.bgpkit.org/parser/update-example"):
23
+ print(route.prefix, route.as_path)
24
+
25
+ # Reusable filter objects
26
+ from bgpkit import Filter
27
+ f = Filter.peer_ip("185.1.8.65")
28
+ parser = bgpkit.Parser.from_filters(url, [f])
29
+ ```
30
+
31
+
32
+
33
+ ### BGPKIT Broker
34
+
35
+ ```python
36
+ import bgpkit
37
+ broker = bgpkit.Broker()
38
+
39
+ # Search MRT data files
40
+ items = broker.query(ts_start="2024-01-01T00:00:00Z", ts_end="2024-01-01T01:00:00Z")
41
+ print(len(items))
42
+
43
+ # Get latest files
44
+ latest = broker.latest()
45
+
46
+ # Query peer information
47
+ peers = broker.peers(asn=13335, full_feed=True)
48
+
49
+ # List collectors
50
+ collectors = broker.collectors(project="routeviews", active=True)
51
+ ```
52
+
53
+ ### BGPKIT IP Lookup
54
+
55
+ ```python
56
+ ip_info = bgpkit.IpLookup().query("1.1.1.1")
57
+ print(ip_info.country, ip_info.as_number, ip_info.as_name)
58
+ ```
59
+
60
+ ### BGPKIT ASN Lookup
61
+
62
+ ```python
63
+ result = bgpkit.AsnLookup().query(asn="13335", country="US")
64
+ for asn in result.data:
65
+ print(asn.asn, asn.name, asn.country)
66
+ ```
67
+
68
+ ### BGPKIT Community Lookup
69
+
70
+ ```python
71
+ entries = bgpkit.CommunityLookup().query(asn="13335", page_size=50)
72
+ for entry in entries:
73
+ print(entry.value, entry.description)
74
+
75
+ sources = bgpkit.CommunityLookup().sources()
76
+ ```
77
+
78
+ ### BGPKIT ROAS Lookup
79
+
80
+ ### BGPKIT ROAS Lookup
81
+
82
+ ```python
83
+ roas = bgpkit.Roas()
84
+ data = roas.query(asn=3333, date="2018-01-01")
85
+ for entry in data:
86
+ print(entry.prefix, entry.asn, entry.tal)
87
+
88
+ # Current ROAs only
89
+ current = roas.query(prefix="1.1.1.0/24", current=True)
90
+ ```
91
+
92
+ ## Build and Publish
93
+
94
+ Push a version tag to build and publish automatically via GitHub Actions:
95
+
96
+ ```
97
+ git tag v0.7.0
98
+ git push origin v0.7.0
99
+ ```
100
+
101
+ Manual release:
102
+
103
+ ```
104
+ python -m pip install --upgrade build twine
105
+ python -m build
106
+ twine upload --skip-existing dist/*
107
+ ```
108
+
109
+ PyPI Trusted Publishing is configured for automated releases. See `.github/workflows/release.yml`.
110
+
@@ -0,0 +1,6 @@
1
+ from .bgpkit_parser import Filter, Parser, RouteElem, RouteParser
2
+ from .bgpkit_broker import Broker, BrokerItem, CollectorItem, PeerItem
3
+ from .bgpkit_roas import Roas, RoasItem
4
+ from .bgpkit_ip import IpLookup, IpInfo
5
+ from .bgpkit_asn import AsnLookup, AsnInfo, AsnLookupResult
6
+ from .bgpkit_community import CommunityLookup, CommunityEntry, CommunitySource
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+
4
+ import requests
5
+
6
+
7
+ @dataclass
8
+ class Pagination:
9
+ next: Optional[str] = None
10
+ previous: Optional[str] = None
11
+
12
+
13
+ @dataclass
14
+ class AsnInfo:
15
+ asn: int
16
+ name: str
17
+ country: str = ""
18
+ org_name: str = ""
19
+ org_id: str = ""
20
+ description: str = ""
21
+ website: str = ""
22
+
23
+
24
+ @dataclass
25
+ class AsnLookupResult:
26
+ data: List[AsnInfo]
27
+ count: int
28
+ page: int
29
+ page_size: int
30
+ updated_at: str = ""
31
+ pagination: Optional[Pagination] = None
32
+
33
+
34
+ class AsnLookup:
35
+ """BGPKIT ASN information lookup (v3/utils/asn)."""
36
+
37
+ def __init__(self, api_url: str = "https://api.bgpkit.com/v3/utils"):
38
+ self.base_url = api_url.rstrip("/")
39
+
40
+ def query(
41
+ self,
42
+ asn: str = None,
43
+ country: str = None,
44
+ search: str = None,
45
+ page: int = 1,
46
+ page_size: int = 100,
47
+ ) -> AsnLookupResult:
48
+ params = {}
49
+ if asn:
50
+ params["asn"] = asn
51
+ if country:
52
+ params["country"] = country
53
+ if search:
54
+ params["search"] = search
55
+ if page:
56
+ params["page"] = page
57
+ if page_size:
58
+ params["page_size"] = min(page_size, 10000)
59
+
60
+ res = requests.get(f"{self.base_url}/asn", params=params).json()
61
+ data = [AsnInfo(**item) for item in res.get("data", [])]
62
+ pag = res.get("pagination", {})
63
+ return AsnLookupResult(
64
+ data=data,
65
+ count=res.get("count", 0),
66
+ page=res.get("page", page),
67
+ page_size=res.get("page_size", page_size),
68
+ updated_at=res.get("updatedAt", ""),
69
+ pagination=Pagination(**pag) if pag else None,
70
+ )
@@ -0,0 +1,176 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List, Optional
3
+
4
+ import requests as requests
5
+ import urllib3
6
+
7
+
8
+ def check_type(value: any, ty: type) -> bool:
9
+ try:
10
+ ty(value)
11
+ return True
12
+ except (ValueError, TypeError):
13
+ raise ValueError("invalid option input")
14
+
15
+
16
+ @dataclass
17
+ class BrokerItem:
18
+ ts_start: str
19
+ ts_end: str
20
+ collector_id: str
21
+ data_type: str
22
+ url: str
23
+ rough_size: int
24
+ exact_size: int
25
+
26
+
27
+ @dataclass
28
+ class PeerItem:
29
+ ip: str
30
+ asn: int
31
+ collector: str
32
+ full_feed: bool = False
33
+
34
+
35
+ @dataclass
36
+ class CollectorItem:
37
+ id: str
38
+ name: str
39
+ project: str
40
+ country: str
41
+ active: bool = True
42
+ latitude: Optional[float] = None
43
+ longitude: Optional[float] = None
44
+
45
+
46
+ @dataclass
47
+ class LatestResult:
48
+ """Result of the /v3/broker/latest endpoint."""
49
+ items: List[BrokerItem] = field(default_factory=list)
50
+
51
+
52
+ class Broker:
53
+ """BGPKIT Broker v3 API wrapper.
54
+
55
+ Provides access to MRT data file search, peer information,
56
+ collector metadata, and latest file discovery.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ api_url: str = "https://api.bgpkit.com/v3/broker",
62
+ page_size: int = 100,
63
+ verify: bool = True,
64
+ ):
65
+ self.base_url = api_url.rstrip("/")
66
+ self.page_size = int(page_size)
67
+ self.verify = verify
68
+ if not verify:
69
+ urllib3.disable_warnings()
70
+
71
+ def _paginate(self, endpoint: str, params: dict) -> dict:
72
+ """Fetch all pages of a paginated broker endpoint."""
73
+ page = 1
74
+ all_data = []
75
+
76
+ while True:
77
+ params["page"] = page
78
+ params["page_size"] = self.page_size
79
+ res = requests.get(
80
+ f"{self.base_url}/{endpoint}",
81
+ params=params,
82
+ verify=self.verify,
83
+ ).json()
84
+
85
+ if isinstance(res, dict):
86
+ data = res.get("data", [])
87
+ all_data.extend(data)
88
+ if len(data) < self.page_size:
89
+ break
90
+ elif isinstance(res, list):
91
+ all_data.extend(res)
92
+ break
93
+ else:
94
+ break
95
+
96
+ page += 1
97
+
98
+ result = res if isinstance(res, dict) else {"data": all_data}
99
+ result["data"] = all_data
100
+ return result
101
+
102
+ def query(
103
+ self,
104
+ ts_start: str = None,
105
+ ts_end: str = None,
106
+ collector_id: str = None,
107
+ project: str = None,
108
+ data_type: str = None,
109
+ ) -> List[BrokerItem]:
110
+ """Search for MRT data files matching the given criteria."""
111
+ params = {}
112
+ if ts_start:
113
+ params["ts_start"] = ts_start
114
+ if ts_end:
115
+ params["ts_end"] = ts_end
116
+ if collector_id:
117
+ params["collector_id"] = collector_id
118
+ if project:
119
+ check_type(project, str)
120
+ params["project"] = project
121
+ if data_type:
122
+ check_type(data_type, str)
123
+ params["data_type"] = data_type
124
+
125
+ result = self._paginate("search", params)
126
+ return [BrokerItem(**item) for item in result.get("data", [])]
127
+
128
+ def latest(self) -> List[BrokerItem]:
129
+ """Get latest MRT data files across projects."""
130
+ res = requests.get(
131
+ f"{self.base_url}/latest",
132
+ verify=self.verify,
133
+ ).json()
134
+ if isinstance(res, list):
135
+ return [BrokerItem(**item) for item in res]
136
+ data = res.get("data", [])
137
+ return [BrokerItem(**item) for item in data]
138
+
139
+ def peers(
140
+ self,
141
+ full_feed: bool = None,
142
+ ip: str = None,
143
+ asn: int = None,
144
+ collector: str = None,
145
+ ) -> List[PeerItem]:
146
+ """Query BGP peer information."""
147
+ params = {}
148
+ if full_feed is not None:
149
+ params["full_feed"] = str(full_feed).lower()
150
+ if ip:
151
+ params["ip"] = ip
152
+ if asn:
153
+ params["asn"] = asn
154
+ if collector:
155
+ params["collector"] = collector
156
+
157
+ result = self._paginate("peers", params)
158
+ return [PeerItem(**item) for item in result.get("data", [])]
159
+
160
+ def collectors(
161
+ self,
162
+ project: str = None,
163
+ country: str = None,
164
+ active: bool = None,
165
+ ) -> List[CollectorItem]:
166
+ """Query MRT collector information."""
167
+ params = {}
168
+ if project:
169
+ params["project"] = project
170
+ if country:
171
+ params["country"] = country
172
+ if active is not None:
173
+ params["active"] = str(active).lower()
174
+
175
+ result = self._paginate("collectors", params)
176
+ return [CollectorItem(**item) for item in result.get("data", [])]
@@ -0,0 +1,63 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+
4
+ import requests
5
+
6
+
7
+ @dataclass
8
+ class CommunityEntry:
9
+ asn: int
10
+ value: str
11
+ description: str
12
+ as_name: str = ""
13
+ country: str = ""
14
+ source: str = ""
15
+
16
+
17
+ @dataclass
18
+ class CommunitySource:
19
+ id: str
20
+ name: str
21
+ url: str = ""
22
+
23
+
24
+ class CommunityLookup:
25
+ """BGPKIT community lookup (v3/communities)."""
26
+
27
+ def __init__(self, api_url: str = "https://api.bgpkit.com/v3/communities"):
28
+ self.base_url = api_url.rstrip("/")
29
+
30
+ def query(
31
+ self,
32
+ asn: str = None,
33
+ value: str = None,
34
+ description: str = None,
35
+ as_name: str = None,
36
+ country: str = None,
37
+ page: int = 0,
38
+ page_size: int = 10,
39
+ ) -> List[CommunityEntry]:
40
+ params = {}
41
+ if asn:
42
+ params["asn"] = asn
43
+ if value:
44
+ params["value"] = value
45
+ if description:
46
+ params["description"] = description
47
+ if as_name:
48
+ params["as_name"] = as_name
49
+ if country:
50
+ params["country"] = country
51
+ params["page"] = str(page)
52
+ params["page_size"] = str(min(page_size, 1000))
53
+
54
+ res = requests.get(self.base_url, params=params).json()
55
+ if isinstance(res, list):
56
+ return [CommunityEntry(**item) for item in res]
57
+ return [CommunityEntry(**item) for item in res.get("data", [])]
58
+
59
+ def sources(self) -> List[CommunitySource]:
60
+ res = requests.get(f"{self.base_url}/sources").json()
61
+ if isinstance(res, list):
62
+ return [CommunitySource(**item) for item in res]
63
+ return [CommunitySource(**item) for item in res.get("data", [])]
@@ -0,0 +1,49 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ import requests
5
+
6
+
7
+ @dataclass
8
+ class AsnDetail:
9
+ asn: int
10
+ name: str
11
+ country: str
12
+ org_name: str = ""
13
+ org_id: str = ""
14
+
15
+
16
+ @dataclass
17
+ class IpInfo:
18
+ ip: str
19
+ country: str
20
+ asn: Optional[dict] = None
21
+
22
+ @property
23
+ def as_number(self) -> Optional[int]:
24
+ if self.asn:
25
+ return self.asn.get("asn")
26
+ return None
27
+
28
+ @property
29
+ def as_name(self) -> Optional[str]:
30
+ if self.asn:
31
+ return self.asn.get("name")
32
+ return None
33
+
34
+
35
+ class IpLookup:
36
+ """BGPKIT IP address lookup (v3/utils/ip)."""
37
+
38
+ def __init__(self, api_url: str = "https://api.bgpkit.com/v3/utils"):
39
+ self.base_url = api_url.rstrip("/")
40
+
41
+ def query(self, ip: str = None, simple: bool = False) -> IpInfo:
42
+ params = {}
43
+ if ip:
44
+ params["ip"] = ip
45
+ if simple:
46
+ params["simple"] = "true"
47
+
48
+ res = requests.get(f"{self.base_url}/ip", params=params).json()
49
+ return IpInfo(**res)
@@ -0,0 +1,3 @@
1
+ from pybgpkit_parser import Filter, Parser, RouteElem, RouteParser
2
+
3
+ __all__ = ["Parser", "RouteParser", "RouteElem", "Filter"]
@@ -0,0 +1,64 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+
4
+ import requests
5
+
6
+
7
+ @dataclass
8
+ class RoasItem:
9
+ prefix: str
10
+ asn: int
11
+ max_len: Optional[int] = None
12
+ tal: Optional[str] = None
13
+ current: bool = False
14
+ date_ranges: Optional[List[List[str]]] = None
15
+
16
+
17
+ class Roas:
18
+ """BGPKIT ROAS lookup (alpha API).
19
+
20
+ Queries the ROAS (Route Origination Authorization) database
21
+ for historical and current RPKI data.
22
+ """
23
+
24
+ def __init__(self, api_url: str = "https://alpha.api.bgpkit.com"):
25
+ self.base_url = api_url.rstrip("/")
26
+
27
+ def query(
28
+ self,
29
+ asn: int = None,
30
+ prefix: str = None,
31
+ date: str = None,
32
+ current: bool = None,
33
+ page: int = 1,
34
+ page_size: int = None,
35
+ ) -> List[RoasItem]:
36
+ """Query ROAS database.
37
+
38
+ Args:
39
+ asn: AS number to filter by.
40
+ prefix: IP prefix to filter by.
41
+ date: Date string (YYYY-MM-DD) for historical lookup.
42
+ current: If True, return only currently valid ROAs.
43
+ page: Page number (1-indexed).
44
+ page_size: Results per page. Defaults to 5 (alpha API limitation).
45
+
46
+ Returns:
47
+ List of RoasItem matching the query.
48
+ """
49
+ params = {}
50
+ if asn is not None:
51
+ params["asn"] = asn
52
+ if prefix:
53
+ params["prefix"] = prefix
54
+ if date:
55
+ params["date"] = date
56
+ if current is not None:
57
+ params["current"] = str(current).lower()
58
+ params["page"] = str(page)
59
+ params["page_size"] = str(page_size if page_size is not None else 5)
60
+
61
+ res = requests.get(f"{self.base_url}/roas", params=params).json()
62
+ if isinstance(res, list):
63
+ return [RoasItem(**item) for item in res]
64
+ return [RoasItem(**item) for item in res.get("data", [])]