mellowdrifter / python-bgpstuff.net

Apache License 2.0
3 stars 1 forks source link

Possible Enhancement: Restructure Client #6

Closed crankynetman closed 3 years ago

crankynetman commented 3 years ago

What do you think about restructuring the client to be a bit less verbose? What I mean by that is structuring things so that the methods of the client act in a (well, at least what I think is :joy: ) more pythonic argument/return fashion.

For example, instead of the current:

>>> q = bgpstuff.Response()
>>> q.ip = "4.2.2.1"
>>> q.getRoute()
>>> if q.status_code == 200 and q.exists:
...     print("The route for {} is {}".format(q.ip, q.route))
... 
The route for 4.2.2.1 is 4.0.0.0/9

We could do:

>>> client = bgpstuff.Client()
>>> ip = "4.2.2.1"
>>> client.get_route(ip)
'4.0.0.0/9'

I'm probably explaining myself poorly, so I created some rough (but working!) example code. If you are interested, I would be glad to flesh this out to implement all BGPStuff features.


import requests
import ipaddress
from ratelimit import limits, sleep_and_retry

class BGPStuffError(Exception):
    pass

class Client():
    """Class Client is an object with all the required methods to
    interact with the REST API portions of bgpstuff.net. The class
    should be reused as it has a built in rate-limiter as long as
    you use the same object

    Args:
        url (str): The BGPStuff instance to query against.    
    """
    def __init__(self, url="https://bgpstuff.net"):
        self.url = url
        self.session_headers = {
            'Content-Type': 'application/json'
            }
        self.session = self._get_session()

    def _get_session(self):
        '''Make a requests session object with the proper headers.
        '''
        session = requests.Session()
        session.headers.update(self.session_headers)

        return session

    @sleep_and_retry
    @limits(calls=30, period=60)
    def _bgpstuff_request(self, endpoint):
        '''Performs an arbitrary HTTP GET to BGPStuff.

        Args:
            endpoint (str): The REST endpoint to query

        Returns:
            value (dict): Deserialized JSON response from BGPStuff.
        '''     
        url = f"{self.url}/{endpoint}"

        r = self.session.get(url)

        try:        
            r.raise_for_status()
        except requests.exceptions.HTTPError as e:
            raise BGPStuffError(e)

        value = r.json()

        return value

    def _validate_ip(self, ip):
        '''Ensures that an IP address is valid

        Args:
            ip (str): The IP Address to validate (v4 or v6)

        Returns:
            None

        Raises:
            BGPStuffError: If address is invalid.
        '''     
        try:
            ipaddress.ip_address(ip)
        except ValueError as e:
            raise BGPStuffError("Invalid IP Address", e)

    def get_route(self, ip):
        '''Gets the route prefix for the IP and sets it to the 
        self.route attribute.

        Args:
            ip (str): 

        Returns:
            route (str):
        '''        
        self._validate_ip(ip)

        endpoint = "route"
        resp = self._bgpstuff_request(f"{endpoint}/{ip}")

        route = resp['Response']['Route']

        return route

    def get_origin(self, ip):
        pass

    def get_aspath(self, ip):
        pass

    def get_roa(self, ip):
        pass

    def get_as_name(self, asn):
        pass

    def get_sourced_prefixes(self, asn):
        pass

    def get_totals(self):
        pass

    def get_invalids(self):
        pass
mellowdrifter commented 3 years ago

Yes seems a good idea. I'm terrible with naming shit

crankynetman commented 3 years ago

It's definitely the hardest part for me as well :joy: . I just put in the PR (#9 ), it probably needs some TLC before merging, but I think it implements everything that the current version has. The only exception is everything under the __name__ block. I figured it might make sense to move that into a separate file that imports the client to demo the client, but am not sold yet.

mellowdrifter commented 3 years ago

Closing this off as it's mostly done. For now we're just updating