Skip to content

base

Classes:

Name Description
BrokerAPIException

BrokerAPIException represents an exception condition related to Broker API calls

BrokerAPI

Base class for broker REST API implementations.

BrokerAPIException

BrokerAPIException(code=None, message=None)

Bases: Exception

BrokerAPIException represents an exception condition related to Broker API calls

BrokerAPIException represents an exception condition related to Broker API calls

-code: (string) error code -message: (string) error message

Example programs

examples/stock_brokers/api/base/BrokerAPIException/example_1_raising_and_reading_an_error.py
"""Raises a `BrokerAPIException` and reads the code and message it carries.

`BrokerAPIException` is what every broker class raises, through its own subclass, when a broker refuses a request. It holds the broker's error `code` and a `message`, both optional, and passes both to `Exception` as well, so they also appear in `args` and in the exception's text.

This program raises one with a Kite-style code and message, as `ZerodhaAPI` does for an expired token, and one with neither, and prints what a handler can read from each. It needs no data store, no network and no broker.

Notice that the code and message are plain attributes, and that an exception raised without them still has both, set to None.

Run it from the project root:

    python examples/stock_brokers/api/base/BrokerAPIException/example_1_raising_and_reading_an_error.py
"""

from stock_brokers.api.base import (
    BrokerAPIException,
)


class RaisingAndReadingAnErrorExample:
    """Raises two exceptions and prints what each holds.

    Attributes:
        errors (list): The exceptions to raise, one with a code and message and one without.
    """

    def __init__(self):
        """Builds the exceptions to raise.

        Returns:
            None: This method returns nothing.
        """
        self.errors = [
            BrokerAPIException(code='TokenException', message='Incorrect `api_key` or `access_token`.'),
            BrokerAPIException(),
        ]

    def run(self):
        """Raises each exception, catches it and prints its fields.

        Returns:
            None: This method returns nothing.
        """
        for error_to_raise in self.errors:
            try:
                raise error_to_raise
            except BrokerAPIException as error:
                print(f'code: {error.code!r}')
                print(f'message: {error.message!r}')
                print(f'args: {error.args!r}')
                print(f'text: {error}')
                print()


if __name__ == '__main__':
    RaisingAndReadingAnErrorExample().run()

It prints:

code: 'TokenException'
message: 'Incorrect `api_key` or `access_token`.'
args: ('TokenException', 'Incorrect `api_key` or `access_token`.')
text: ('TokenException', 'Incorrect `api_key` or `access_token`.')

code: None
message: None
args: (None, None)
text: (None, None)
examples/stock_brokers/api/base/BrokerAPIException/example_2_one_handler_for_every_broker.py
"""Handles the errors of several brokers with one `except BrokerAPIException` clause.

Each broker module defines its own exception class, such as `DhanAPIException` or `ZerodhaAPIException`, and every one of them derives from `BrokerAPIException`. Code that works across brokers, such as a script that polls every broker's funds, can therefore catch the base class once and still read the broker's `code` and `message`, and use the exception's class to say which broker refused.

This program raises three brokers' exceptions with the codes and messages those brokers send for an expired token, catches them all with one handler, and prints what the handler sees. It needs no data store, no network and no broker.

Notice that every exception is caught by the same clause, and that each keeps its broker's own code: Dhan's `errorType`, Kite's `error_type` and Noren's `stat`.

Run it from the project root:

    python examples/stock_brokers/api/base/BrokerAPIException/example_2_one_handler_for_every_broker.py
"""

from stock_brokers.api.base import (
    BrokerAPIException,
)
from stock_brokers.api.dhan import (
    DhanAPIException,
)
from stock_brokers.api.flattrade import (
    FlattradeAPIException,
)
from stock_brokers.api.zerodha import (
    ZerodhaAPIException,
)


class RefusingBroker:
    """A stand-in for one broker's API object whose every request is refused with that broker's exception.

    Attributes:
        broker_name (str): The broker's name.
        refusal (BrokerAPIException): The exception every request raises.
    """

    def __init__(self, broker_name, refusal):
        """Holds the broker's name and the refusal.

        Args:
            broker_name (str): The broker's name.
            refusal (BrokerAPIException): The exception every request raises.

        Returns:
            None: This method returns nothing.
        """
        self.broker_name = broker_name
        self.refusal = refusal

    def get(self, url):
        """Refuses the request.

        Args:
            url (str): The URL requested.

        Returns:
            dict: Never returns.

        Raises:
            BrokerAPIException: Always, the broker's own subclass of it.
        """
        raise self.refusal


class OneHandlerExample:
    """Asks every broker for its funds and handles every refusal in one place.

    Attributes:
        brokers (list): The stand-in brokers to ask.
    """

    def __init__(self):
        """Builds the refusing brokers.

        Returns:
            None: This method returns nothing.
        """
        self.brokers = [
            RefusingBroker('dhan', DhanAPIException(code='Invalid_Authentication', message='Client ID or user generated access token is invalid or expired.')),
            RefusingBroker('zerodha', ZerodhaAPIException(code='TokenException', message='Incorrect `api_key` or `access_token`.')),
            RefusingBroker('flattrade', FlattradeAPIException(code='Not_Ok', message='Session Expired :  Invalid Session Key')),
        ]

    def run(self):
        """Sends one request to each broker and prints each refusal.

        Returns:
            None: This method returns nothing.
        """
        for broker in self.brokers:
            try:
                broker.get(url='/funds')
            except BrokerAPIException as error:
                print(f'{broker.broker_name}: {type(error).__name__} code={error.code} message={error.message}')


if __name__ == '__main__':
    OneHandlerExample().run()

It prints:

dhan: DhanAPIException code=Invalid_Authentication message=Client ID or user generated access token is invalid or expired.
zerodha: ZerodhaAPIException code=TokenException message=Incorrect `api_key` or `access_token`.
flattrade: FlattradeAPIException code=Not_Ok message=Session Expired :  Invalid Session Key
Source code in stock_brokers/api/base.py
def __init__(self, code=None, message=None):
    """
    BrokerAPIException represents an exception condition related to Broker  API calls

    -`code`: (string) error code
    -`message`: (string) error message
    """
    super(BrokerAPIException, self).__init__(code, message)
    self.code = code
    self.message = message

BrokerAPI

BrokerAPI(broker_name)

Base class for broker REST API implementations.

Initializes the BrokerAPI instance.

Parameters:

Name Type Description Default
broker_name str

The name of the broker.

required

Example programs

examples/stock_brokers/api/base/BrokerAPI/example_1_writing_a_broker_class.py
"""Writes a small broker class on `BrokerAPI` and sends one request with each HTTP verb through it.

A broker module subclasses `BrokerAPI` and supplies two things: a constructor that calls `BrokerAPI.__init__` and then logs in when the stored token no longer works, and a `_request` that sends one request with the broker's credentials. `BrokerAPI` supplies the rest: `get`, `post`, `put`, `patch` and `delete` each pass their arguments to `_request` with the matching method, and `__init__` loads the broker's settings and stored login.

The broker here, `PaperBrokerAPI`, is made up. Its `_request` answers from memory instead of the network, keeping a small list of watchlists, and it reads the token in force through `_current_login` as the real brokers do. Its constructor skips the login, because there is nothing to log in to.

`BrokerAPI.__init__` itself reads the settings and the stored login from MongoDB and copies the settings into the Redis `settings` hash. The program runs that real constructor, with `get_cache` and `get_mongo_db` replaced for the length of the construction by small stand-ins, so no data store is touched.

Notice that the constructor copied the settings into Redis but not the login: only a login writes the Redis `last_login` hash, so that a constructor can never put an older token back over a newer one.

Run it from the project root:

    python examples/stock_brokers/api/base/BrokerAPI/example_1_writing_a_broker_class.py
"""

import unittest.mock

from stock_brokers.api import base as broker_api_base
from stock_brokers.api.base import (
    BrokerAPI,
)


class StandInCollection:
    """A stand-in for one MongoDB collection, holding documents in a list.

    Attributes:
        documents (list): The stored documents.
    """

    def __init__(self, documents):
        """Holds the documents.

        Args:
            documents (list): The documents the collection starts with.

        Returns:
            None: This method returns nothing.
        """
        self.documents = documents

    def find_one(self, query, projection=None):
        """Finds the first document whose fields match every field of the query.

        Args:
            query (dict): The fields to match.
            projection (dict | None): Fields to leave out, which the stand-in honours only for `_id`.

        Returns:
            dict | None: A copy of the document, or None when nothing matches.
        """
        for document in self.documents:
            matches = True
            for field, value in query.items():
                if document.get(field) != value:
                    matches = False
            if matches:
                found = dict(document)
                found.pop('_id', None)
                return found
        return None


class StandInMongo:
    """A stand-in for the MongoDB database, holding the `settings` and `last_login` collections.

    Attributes:
        collections (dict): Each collection name mapped to its `StandInCollection`.
    """

    def __init__(self, collections):
        """Holds the collections.

        Args:
            collections (dict): Each collection name mapped to its `StandInCollection`.

        Returns:
            None: This method returns nothing.
        """
        self.collections = collections

    def __getitem__(self, name):
        """Returns one collection, as `database[name]` does.

        Args:
            name (str): The collection name.

        Returns:
            StandInCollection: The collection.
        """
        return self.collections[name]


class StandInRedis:
    """A dictionary-backed stand-in for the Redis client's hash commands.

    Attributes:
        hashes (dict): Each hash name mapped to a dictionary of its fields.
    """

    def __init__(self):
        """Starts with nothing stored.

        Returns:
            None: This method returns nothing.
        """
        self.hashes = {}

    def hget(self, name, key):
        """Reads one field of a hash.

        Args:
            name (str): The hash name.
            key (str): The field name.

        Returns:
            str | None: The stored value, or None when there is none.
        """
        fields = self.hashes.get(name, {})
        return fields.get(key)

    def hset(self, name, key, value):
        """Writes one field of a hash.

        Args:
            name (str): The hash name.
            key (str): The field name.
            value (str): The value to store.

        Returns:
            int: Always 1, the number of fields Redis reports written.
        """
        if name not in self.hashes:
            self.hashes[name] = {}
        self.hashes[name][key] = value
        return 1

    def hsetnx(self, name, key, value):
        """Writes one field of a hash only when it is empty.

        Args:
            name (str): The hash name.
            key (str): The field name.
            value (str): The value to store.

        Returns:
            int: 1 when the field was written, 0 when it already held a value.
        """
        if self.hget(name, key) is not None:
            return 0
        return self.hset(name, key, value)


class PaperBrokerAPI(BrokerAPI):
    """A made-up broker that keeps watchlists in memory, written the way a real broker module is.

    Attributes:
        watchlists (dict): Each watchlist id mapped to its name and symbols.
    """

    def __init__(self):
        """Loads the settings and stored login through `BrokerAPI.__init__`, and skips the login a real broker would make.

        Returns:
            None: This method returns nothing.
        """
        super().__init__(broker_name='paper')
        self.watchlists = {}

    def _request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
        """Answers one request from memory, in the shape every broker's `_request` returns.

        Args:
            method (str): The HTTP method.
            url (str): The URL requested.
            params (dict | None): Query parameters, unused here.
            data (Any): A form body, unused here.
            headers (dict | None): Headers, unused here.
            cookies (dict | None): Cookies, unused here.
            files (dict | None): Files, unused here.
            auth (Any): Authentication, unused here.
            timeout (float | None): A timeout, unused here.
            allow_redirects (bool | None): Whether to follow redirects, unused here.
            proxies (dict | None): Proxies, unused here.
            hooks (dict | None): Request hooks, unused here.
            stream (bool | None): Whether to stream, unused here.
            verify (bool | None): Whether to check certificates, unused here.
            cert (Any): A client certificate, unused here.
            json (dict | None): A JSON body, holding the watchlist's fields.
            verbose (bool): Whether to log the request, unused here.

        Returns:
            dict: `status`, `code` and `data`, where `data` holds the watchlists and the token the request would have carried.
        """
        login = self._current_login()
        watchlist_id = url.rsplit('/', 1)[-1]
        if method == 'POST' or method == 'PUT':
            self.watchlists[watchlist_id] = dict(json)
        elif method == 'PATCH':
            self.watchlists[watchlist_id].update(json)
        elif method == 'DELETE':
            del self.watchlists[watchlist_id]
        return {
            'status': 'success',
            'code': 200,
            'data': {
                'token': login['access_token'],
                'watchlists': dict(self.watchlists),
            },
        }


class WritingABrokerClassExample:
    """Builds `PaperBrokerAPI` through the real `BrokerAPI` constructor and calls every verb once.

    Attributes:
        cache (StandInRedis): The stand-in for Redis.
        mongo_db (StandInMongo): The stand-in for MongoDB, holding the settings and the stored login.
    """

    def __init__(self):
        """Builds the stand-in data stores.

        Returns:
            None: This method returns nothing.
        """
        self.cache = StandInRedis()
        self.mongo_db = StandInMongo({
            'settings': StandInCollection([
                {
                    '_id': 'settings-1',
                    'broker_name': 'paper',
                    'client_id': 'PAPER01',
                },
            ]),
            'last_login': StandInCollection([
                {
                    '_id': 'login-1',
                    'broker_name': 'paper',
                    'access_token': 'paper-token',
                    'last_login': '2026-09-30 07:00:05',
                },
            ]),
        })

    def show(self, verb, answer):
        """Prints one answer on one line.

        Args:
            verb (str): The method that was called.
            answer (dict): What it returned.

        Returns:
            None: This method returns nothing.
        """
        print(f'{verb}: status={answer["status"]} token={answer["data"]["token"]} watchlists={answer["data"]["watchlists"]}')

    def run(self):
        """Builds the broker object, sends one request per verb and prints each answer.

        Returns:
            None: This method returns nothing.
        """
        with unittest.mock.patch.object(broker_api_base, 'get_cache', return_value=self.cache):
            with unittest.mock.patch.object(broker_api_base, 'get_mongo_db', return_value=self.mongo_db):
                api = PaperBrokerAPI()
        print(f'Redis settings hash after construction: {self.cache.hashes.get("settings")}')
        print(f'Redis last_login hash after construction: {self.cache.hashes.get("last_login")}')
        url = 'https://paper.example/watchlists/banks'
        new_watchlist = {
            'name': 'Banks',
            'symbols': [
                'NSE:HDFCBANK',
            ],
        }
        replacement = {
            'name': 'Banks',
            'symbols': [
                'NSE:HDFCBANK',
                'NSE:ICICIBANK',
            ],
        }
        rename = {
            'name': 'Private banks',
        }
        self.show('post', api.post(url=url, json=new_watchlist))
        self.show('get', api.get(url=url))
        self.show('put', api.put(url=url, json=replacement))
        self.show('patch', api.patch(url=url, json=rename))
        self.show('delete', api.delete(url=url))

if __name__ == '__main__':
    WritingABrokerClassExample().run()

It prints:

Redis settings hash after construction: {'paper': '{"broker_name": "paper", "client_id": "PAPER01"}'}
Redis last_login hash after construction: None
post: status=success token=paper-token watchlists={'banks': {'name': 'Banks', 'symbols': ['NSE:HDFCBANK']}}
get: status=success token=paper-token watchlists={'banks': {'name': 'Banks', 'symbols': ['NSE:HDFCBANK']}}
put: status=success token=paper-token watchlists={'banks': {'name': 'Banks', 'symbols': ['NSE:HDFCBANK', 'NSE:ICICIBANK']}}
patch: status=success token=paper-token watchlists={'banks': {'name': 'Private banks', 'symbols': ['NSE:HDFCBANK', 'NSE:ICICIBANK']}}
delete: status=success token=paper-token watchlists={}
examples/stock_brokers/api/base/BrokerAPI/example_2_choosing_the_newer_login.py
"""Shows which login `BrokerAPI` uses when MongoDB, Redis and the object itself disagree.

Before every request, a broker class calls `BrokerAPI._current_login` to choose the token to send. Redis's `last_login` hash is where every process publishes a new login, so it normally wins. When Redis holds nothing, the login is read from MongoDB and written into Redis only if the field is still empty, so it can fill the cache but never overwrite a login that lands in between. And when the object's own login is newer than Redis's by its `last_login` time, which happens when this object has just logged in but its write to Redis failed, the object keeps its own.

The program walks one object through those three cases with `get`. `ClockedBrokerAPI` is a made-up broker whose `_request` only reports the token it would send. Its constructor is the real `BrokerAPI.__init__`, run with `get_cache` and `get_mongo_db` replaced by small stand-ins for the length of the construction, so no data store is touched.

Notice that the second request picks up another process's newer token with no restart, and that the third keeps the object's own login because it is newer than the one in Redis.

Run it from the project root:

    python examples/stock_brokers/api/base/BrokerAPI/example_2_choosing_the_newer_login.py
"""

import json
import unittest.mock

from stock_brokers.api import base as broker_api_base
from stock_brokers.api.base import (
    BrokerAPI,
)


class StandInCollection:
    """A stand-in for one MongoDB collection, holding documents in a list.

    Attributes:
        documents (list): The stored documents.
    """

    def __init__(self, documents):
        """Holds the documents.

        Args:
            documents (list): The documents the collection starts with.

        Returns:
            None: This method returns nothing.
        """
        self.documents = documents

    def find_one(self, query, projection=None):
        """Finds the first document whose fields match every field of the query.

        Args:
            query (dict): The fields to match.
            projection (dict | None): Fields to leave out, which the stand-in honours only for `_id`.

        Returns:
            dict | None: A copy of the document, or None when nothing matches.
        """
        for document in self.documents:
            matches = True
            for field, value in query.items():
                if document.get(field) != value:
                    matches = False
            if matches:
                found = dict(document)
                found.pop('_id', None)
                return found
        return None


class StandInMongo:
    """A stand-in for the MongoDB database, holding the `settings` and `last_login` collections.

    Attributes:
        collections (dict): Each collection name mapped to its `StandInCollection`.
    """

    def __init__(self, collections):
        """Holds the collections.

        Args:
            collections (dict): Each collection name mapped to its `StandInCollection`.

        Returns:
            None: This method returns nothing.
        """
        self.collections = collections

    def __getitem__(self, name):
        """Returns one collection, as `database[name]` does.

        Args:
            name (str): The collection name.

        Returns:
            StandInCollection: The collection.
        """
        return self.collections[name]


class StandInRedis:
    """A dictionary-backed stand-in for the Redis client's hash commands.

    Attributes:
        hashes (dict): Each hash name mapped to a dictionary of its fields.
    """

    def __init__(self):
        """Starts with nothing stored.

        Returns:
            None: This method returns nothing.
        """
        self.hashes = {}

    def hget(self, name, key):
        """Reads one field of a hash.

        Args:
            name (str): The hash name.
            key (str): The field name.

        Returns:
            str | None: The stored value, or None when there is none.
        """
        fields = self.hashes.get(name, {})
        return fields.get(key)

    def hset(self, name, key, value):
        """Writes one field of a hash.

        Args:
            name (str): The hash name.
            key (str): The field name.
            value (str): The value to store.

        Returns:
            int: Always 1, the number of fields Redis reports written.
        """
        if name not in self.hashes:
            self.hashes[name] = {}
        self.hashes[name][key] = value
        return 1

    def hsetnx(self, name, key, value):
        """Writes one field of a hash only when it is empty.

        Args:
            name (str): The hash name.
            key (str): The field name.
            value (str): The value to store.

        Returns:
            int: 1 when the field was written, 0 when it already held a value.
        """
        if self.hget(name, key) is not None:
            return 0
        return self.hset(name, key, value)


class ClockedBrokerAPI(BrokerAPI):
    """A made-up broker whose requests only report the token they would carry."""

    def __init__(self):
        """Loads the settings and stored login through `BrokerAPI.__init__`, and skips the login a real broker would make.

        Returns:
            None: This method returns nothing.
        """
        super().__init__(broker_name='clocked')

    def record_login_whose_redis_write_failed(self, access_token, moment):
        """Keeps a login this object has just made, as a real login does when its write to MongoDB succeeds and its write to Redis fails.

        Args:
            access_token (str): The new token.
            moment (str): When the login was made, as `%Y-%m-%d %H:%M:%S`.

        Returns:
            None: This method returns nothing.
        """
        self._last_login = {
            'broker_name': 'clocked',
            'access_token': access_token,
            'last_login': moment,
        }

    def _request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
        """Reports the login a request would carry, without sending anything.

        Args:
            method (str): The HTTP method.
            url (str): The URL requested.
            params (dict | None): Query parameters, unused here.
            data (Any): A form body, unused here.
            headers (dict | None): Headers, unused here.
            cookies (dict | None): Cookies, unused here.
            files (dict | None): Files, unused here.
            auth (Any): Authentication, unused here.
            timeout (float | None): A timeout, unused here.
            allow_redirects (bool | None): Whether to follow redirects, unused here.
            proxies (dict | None): Proxies, unused here.
            hooks (dict | None): Request hooks, unused here.
            stream (bool | None): Whether to stream, unused here.
            verify (bool | None): Whether to check certificates, unused here.
            cert (Any): A client certificate, unused here.
            json (dict | None): A JSON body, unused here.
            verbose (bool): Whether to log the request, unused here.

        Returns:
            dict: `status`, `code` and `data`, where `data` is the login the request would carry.
        """
        login = self._current_login()
        return {
            'status': 'success',
            'code': 200,
            'data': login,
        }


class ChoosingTheNewerLoginExample:
    """Sends three requests through one object while the stored logins change around it.

    Attributes:
        cache (StandInRedis): The stand-in for Redis, empty at first.
        mongo_db (StandInMongo): The stand-in for MongoDB, holding the settings and the morning login.
        url (str): The URL every request names.
    """

    def __init__(self):
        """Builds the stand-in data stores.

        Returns:
            None: This method returns nothing.
        """
        self.cache = StandInRedis()
        self.mongo_db = StandInMongo({
            'settings': StandInCollection([
                {
                    'broker_name': 'clocked',
                    'client_id': 'CLOCK01',
                },
            ]),
            'last_login': StandInCollection([
                {
                    'broker_name': 'clocked',
                    'access_token': 'morning-token',
                    'last_login': '2026-09-30 07:00:05',
                },
            ]),
        })
        self.url = 'https://clocked.example/user/profile'

    def show(self, case, answer):
        """Prints which login a request carried and what Redis holds afterwards.

        Args:
            case (str): What the request shows.
            answer (dict): What the request returned.

        Returns:
            None: This method returns nothing.
        """
        login = answer['data']
        print(case)
        print(f'    sent {login["access_token"]} (logged in at {login["last_login"]})')
        print(f'    Redis last_login now: {self.cache.hget("last_login", "clocked")}')

    def run(self):
        """Builds the object and sends the three requests.

        Returns:
            None: This method returns nothing.
        """
        with unittest.mock.patch.object(broker_api_base, 'get_cache', return_value=self.cache):
            with unittest.mock.patch.object(broker_api_base, 'get_mongo_db', return_value=self.mongo_db):
                api = ClockedBrokerAPI()
        self.show('1. Redis is empty, so the login is read from MongoDB and cached:', api.get(url=self.url))
        newer_login = {
            'broker_name': 'clocked',
            'access_token': 'noon-token',
            'last_login': '2026-09-30 12:30:41',
        }
        self.cache.hset('last_login', 'clocked', json.dumps(newer_login))
        self.show('2. Another process logged in at 12:30:41 and wrote Redis:', api.get(url=self.url))
        api.record_login_whose_redis_write_failed('evening-token', '2026-09-30 18:02:10')
        self.show('3. This object logged in at 18:02:10 but could not write Redis:', api.get(url=self.url))


if __name__ == '__main__':
    ChoosingTheNewerLoginExample().run()

It prints:

1. Redis is empty, so the login is read from MongoDB and cached:
    sent morning-token (logged in at 2026-09-30 07:00:05)
    Redis last_login now: {"broker_name": "clocked", "access_token": "morning-token", "last_login": "2026-09-30 07:00:05"}
2. Another process logged in at 12:30:41 and wrote Redis:
    sent noon-token (logged in at 2026-09-30 12:30:41)
    Redis last_login now: {"broker_name": "clocked", "access_token": "noon-token", "last_login": "2026-09-30 12:30:41"}
3. This object logged in at 18:02:10 but could not write Redis:
    sent evening-token (logged in at 2026-09-30 18:02:10)
    Redis last_login now: {"broker_name": "clocked", "access_token": "noon-token", "last_login": "2026-09-30 12:30:41"}

Methods:

Name Description
get

HTTP GET API call to Broker's API server.

post

HTTP POST API call to Broker's API server.

put

HTTP PUT API call to Broker's API server.

patch

HTTP PATCH API call to Broker's API server.

delete

HTTP DELETE API call to Broker's API server.

Source code in stock_brokers/api/base.py
def __init__(self, broker_name):
    """
    Initializes the BrokerAPI instance.

    Args:
        broker_name (str): The name of the broker.
    """
    self._broker_name = broker_name
    self._cache = get_cache()
    self._mongo_db = get_mongo_db()
    self._logger = get_logger(broker_name)

    self._settings = self._mongo_db['settings'].find_one({'broker_name': broker_name}, {'_id': 0})
    self._last_login = self._mongo_db['last_login'].find_one({'broker_name': broker_name}, {'_id': 0})
    self._cache.hset('settings', broker_name, json.dumps(self._settings))

_current_login

_current_login()

The login in force right now, read on every request rather than once at construction.

Any process that logs a broker in writes the new login to MongoDB and then to the Redis last_login hash. Reading that hash here, on every request, means a token another process has just obtained is used by this object's very next request, rather than a token loaded at construction being sent long after it has been replaced.

Redis is the one place a token is read from, and a login the only thing that writes it. Constructors do not copy MongoDB into it, because that would race a login: a process that read MongoDB just before another logged in, and wrote Redis just after, would put the old token back for everyone.

The newer of this object's login and Redis's is used, by their last_login time. The shared login normally wins. This object's own wins only when it is genuinely newer - a login this object has just made, or one whose write to Redis failed while its write to MongoDB, which this object was built from, succeeded. When either time cannot be read, Redis is trusted.

When Redis holds nothing for the broker, the login is read from MongoDB and written into Redis only if that field is still empty, so it can fill the cache but never overwrite a login that lands in between. When neither store can be read, the object keeps what it has.

Returns:

Type Description
dict | None

The last_login document now in force, also left in self._last_login.

Source code in stock_brokers/api/base.py
def _current_login(self):
    """
    The login in force right now, read on every request rather than once at construction.

    Any process that logs a broker in writes the new login to MongoDB and then to the Redis
    `last_login` hash. Reading that hash here, on every request, means a token another process
    has just obtained is used by this object's very next request, rather than a token loaded at
    construction being sent long after it has been replaced.

    Redis is the one place a token is read from, and a login the only thing that writes it.
    Constructors do not copy MongoDB into it, because that would race a login: a process that
    read MongoDB just before another logged in, and wrote Redis just after, would put the old
    token back for everyone.

    The newer of this object's login and Redis's is used, by their `last_login` time. The
    shared login normally wins. This object's own wins only when it is genuinely newer - a
    login this object has just made, or one whose write to Redis failed while its write to
    MongoDB, which this object was built from, succeeded. When either time cannot be read,
    Redis is trusted.

    When Redis holds nothing for the broker, the login is read from MongoDB and written into
    Redis only if that field is still empty, so it can fill the cache but never overwrite a
    login that lands in between. When neither store can be read, the object keeps what it has.

    Returns:
        dict | None: The `last_login` document now in force, also left in `self._last_login`.
    """
    try:
        raw = self._cache.hget('last_login', self._broker_name)
        shared = json.loads(raw) if raw else None
    except Exception:
        shared = None

    if not shared:
        try:
            shared = self._mongo_db['last_login'].find_one({'broker_name': self._broker_name}, {'_id': 0})
        except Exception:
            shared = None
        if shared:
            try:
                self._cache.hsetnx('last_login', self._broker_name, json.dumps(shared))
            except Exception:
                pass

    if shared:
        own_moment = self._login_moment(self._last_login)
        shared_moment = self._login_moment(shared)
        if own_moment is None or shared_moment is None or shared_moment >= own_moment:
            self._last_login = shared
    return self._last_login

_login_moment staticmethod

_login_moment(document)

When a login document was made, or None when that cannot be read.

Brokers stamp last_login with local time, some with microseconds and some without.

Parameters:

Name Type Description Default
document dict | None

A last_login document.

required

Returns:

Type Description
datetime | None

The moment of the login.

Source code in stock_brokers/api/base.py
@staticmethod
def _login_moment(document):
    """
    When a login document was made, or None when that cannot be read.

    Brokers stamp `last_login` with local time, some with microseconds and some without.

    Args:
        document (dict | None): A `last_login` document.

    Returns:
        datetime | None: The moment of the login.
    """
    stamp = (document or {}).get('last_login')
    for form in ('%Y-%m-%d %H:%M:%S.%f', '%Y-%m-%d %H:%M:%S'):
        try:
            return datetime.strptime(str(stamp), form)
        except (TypeError, ValueError):
            continue
    return None

_request

_request(method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

Private generic REST API request to Broker API server. This is specialized into GET, POST, PUT, PATCH and DELETE by public methods.

  • method: HTTP method to use for the request.
  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.
Source code in stock_brokers/api/base.py
def _request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    Private generic REST API request to Broker API server.
    This is specialized into GET, POST, PUT, PATCH and DELETE by public methods.

    - `method`: HTTP method to use for the request.
    - `url`: URL of the API endpoint.   
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    pass

get

get(url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

HTTP GET API call to Broker's API server. This queries the Broker's API server for some information. Most responses are JSON encoded. Only a few are CSV encoded.

  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.

Example usage

From example_1_writing_a_broker_class.py
fields = self.hashes.get(name, {})
From example_1_writing_a_broker_class.py
return fields.get(key)
From example_1_writing_a_broker_class.py
self.show('get', api.get(url=url))
Source code in stock_brokers/api/base.py
def get(self, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    HTTP GET  API call to Broker's API server.
    This queries the Broker's API server for some information. Most responses are JSON encoded. Only a few are CSV encoded.

    - `url`: URL of the API endpoint.
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    data = self._request(method="GET", url=url, params=params, data=data, headers=headers, cookies=cookies, files=files, auth=auth, timeout=timeout, allow_redirects=allow_redirects, proxies=proxies, hooks=hooks, stream=stream, verify=verify, cert=cert, json=json)
    return data

post

post(url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

HTTP POST API call to Broker's API server. This carries out some action using Broker's API server such as placing an order. Responses are JSON encoded.

  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.

Example usage

From example_1_writing_a_broker_class.py
self.show('post', api.post(url=url, json=new_watchlist))
Source code in stock_brokers/api/base.py
def post(self, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    HTTP POST  API call to Broker's API server.
    This carries out some action using Broker's API server such as placing an order. Responses are JSON encoded.

    - `url`: URL of the API endpoint.
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    data = self._request(method="POST", url=url, params=params, data=data, headers=headers, cookies=cookies, files=files, auth=auth, timeout=timeout, allow_redirects=allow_redirects, proxies=proxies, hooks=hooks, stream=stream, verify=verify, cert=cert, json=json)
    return data

put

put(url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

HTTP PUT API call to Broker's API server. This modifies something using Broker's API server such as modifying an order. Responses are JSON encoded.

  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.

Example usage

From example_1_writing_a_broker_class.py
self.show('put', api.put(url=url, json=replacement))
Source code in stock_brokers/api/base.py
def put(self, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    HTTP PUT  API call to Broker's API server.
    This modifies something using Broker's API server such as modifying an order. Responses are JSON encoded.

    - `url`: URL of the API endpoint.
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    data = self._request(method="PUT", url=url, params=params, data=data, headers=headers, cookies=cookies, files=files, auth=auth, timeout=timeout, allow_redirects=allow_redirects, proxies=proxies, hooks=hooks, stream=stream, verify=verify, cert=cert, json=json)
    return data

patch

patch(url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

HTTP PATCH API call to Broker's API server. This updates something using Broker's API server such as modifying an order. Responses are JSON encoded.

  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.

Example usage

From example_1_writing_a_broker_class.py
self.show('patch', api.patch(url=url, json=rename))
Source code in stock_brokers/api/base.py
def patch(self, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    HTTP PATCH  API call to Broker's API server.
    This updates something using Broker's API server such as modifying an order. Responses are JSON encoded.

    - `url`: URL of the API endpoint.
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    data = self._request(method="PATCH", url=url, params=params, data=data, headers=headers, cookies=cookies, files=files, auth=auth, timeout=timeout, allow_redirects=allow_redirects, proxies=proxies, hooks=hooks, stream=stream, verify=verify, cert=cert, json=json)
    return data

delete

delete(url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False)

HTTP DELETE API call to Broker's API server. This deletes something using Broker's API server such as cancelling an order or invalidating an access token. Responses are JSON encoded.

  • url: URL of the API endpoint.
  • params: Dictionary of parameters to be sent as part of the request.
  • data: Dictionary, bytes, or file-like object to send in the body of the request.
  • headers: Dictionary of HTTP headers to be sent with the request.
  • cookies: Dictionary of cookies to be sent with the request.
  • files: Dictionary of files to be sent with the request.
  • auth: Authentication tuple or object to be sent with the request.
  • timeout: Timeout value for the request.
  • allow_redirects: Boolean value indicating whether redirects should be allowed.
  • proxies: Dictionary of proxy settings for the request.
  • hooks: Dictionary of hooks to be called during the request lifecycle.
  • stream: Boolean value indicating whether response content should be streamed.
  • verify: Boolean value indicating whether SSL certificate verification should be performed.
  • cert: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
  • json: JSON data to send in the body of the request.
  • verbose: If set to True, the request and response are logged.

Example usage

From example_1_writing_a_broker_class.py
self.show('delete', api.delete(url=url))
Source code in stock_brokers/api/base.py
def delete(self, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=None, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None, verbose=False):
    """
    HTTP DELETE  API call to Broker's API server.
    This deletes something using Broker's API server such as cancelling an order or invalidating an access token. Responses are JSON encoded.

    - `url`: URL of the API endpoint.
    - `params`: Dictionary of parameters to be sent as part of the request.
    - `data`: Dictionary, bytes, or file-like object to send in the body of the request.
    - `headers`: Dictionary of HTTP headers to be sent with the request.
    - `cookies`: Dictionary of cookies to be sent with the request.
    - `files`: Dictionary of files to be sent with the request.
    - `auth`: Authentication tuple or object to be sent with the request.
    - `timeout`: Timeout value for the request.
    - `allow_redirects`: Boolean value indicating whether redirects should be allowed.
    - `proxies`: Dictionary of proxy settings for the request.
    - `hooks`: Dictionary of hooks to be called during the request lifecycle.
    - `stream`: Boolean value indicating whether response content should be streamed.
    - `verify`: Boolean value indicating whether SSL certificate verification should be performed.
    - `cert`: Certificate file or tuple containing certificate and key files for client-side SSL certificate verification.
    - `json`: JSON data to send in the body of the request.
    - `verbose`: If set to True, the request and response are logged.
    """
    data = self._request(method="DELETE", url=url, params=params, data=data, headers=headers, cookies=cookies, files=files, auth=auth, timeout=timeout, allow_redirects=allow_redirects, proxies=proxies, hooks=hooks, stream=stream, verify=verify, cert=cert, json=json)
    return data