Metadata-Version: 2.4
Name: openfigi-client
Version: 0.4.2
Summary: Python client for the OpenFIGI API.
Author-email: ljnsn <info@ljnsn.com>
License: MIT License
        
        Copyright (c) [2024] [ljnsn]
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: api,bloomberg,figi,openfigi,python
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.2
Requires-Dist: msgspec>=0.18.6
Description-Content-Type: text/markdown

# openfigi-client

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Black](https://img.shields.io/badge/code%20style-black-black)](https://black.readthedocs.io/en/stable/)
[![Version](https://img.shields.io/pypi/v/openfigi-client)](https://pypi.python.org/pypi/openfigi-client)
[![Python Versions](https://img.shields.io/pypi/pyversions/openfigi-client.svg)](https://pypi.python.org/pypi/openfigi-client)

Python wrapper for the [OpenFIGI API](https://www.openfigi.com/api) v3.

## Table of contents

- [About OpenFIGI](#about-openfigi)
- [Installation](#installation)
- [API key](#api-key)
- [Mapping](#mapping)
- [Filtering](#filtering)
- [Troubleshooting](#troubleshooting)

## About OpenFIGI

- The **F**inancial **I**nstrument **G**lobal **I**dentifier (FIGI) is a
  universal system for identifying instruments globally and across all asset
  classes
- OpenFIGI is an application programming interface that provides automated
  access to mapping various symbols with their corresponding FIGI. It is
  available at <https://www.openfigi.com/>
- [openfigi_client](https://github.com/tlouarn/openfigi_client) is a thin Python
  wrapper to access OpenFIGI

The API contains 3 endpoints:

| endpoint | description                                          |
| -------- | ---------------------------------------------------- |
| /mapping | Map third-party identifiers to FIGIs                 |
| /filter  | Filter for FIGIs using keywords and optional filters |
| /search  | Search for FIGIs using keywords and optional filters |

_Note: given that the _/search_ endpoint is strictly superseded by the _/filter_
endpoint, we choose not to include it in the wrapper._

## Installation

**openfigi_client** is published on
[PyPI](https://pypi.org/project/openfigi_client/). To install it, simply run:

```commandline
pip install openfigi_client
```

## API key

The API can be used with or without API key. Getting an API key is free and
loosens the [rate limits](https://www.openfigi.com/api#rate-limit).

When instantiating the wrapper, the API key is optional:

```python
from openfigi_client import OpenFigiSync

client = OpenFigiSync()
client = OpenFigiSync(api_key="XXXXXXXXXX")
```

## Async

In addition to the synchronous client, there is an equivalent asynchronous
client available. All examples below work equally, simply import
`OpenFigiAsync` instead and `await` the calls.

## Mapping

The `map()` method takes a list of `MappingJob` as argument and returns a list
of `MappingJobResult`. The result of the request at index `i` in the list of
mapping jobs is located at index `i` in the list of results.

```python
from openfigi_client import OpenFigiSync, MappingJob

mapping_job = MappingJob(id_type="TICKER", id_value="IBM", exch_code="US")
mapping_jobs = [mapping_job]
results = OpenFigiSync().map(mapping_jobs)

>>> results
[
    MappingJobResultFigiList(
        data = [
            FigiResult(
                  figi='BBG000BLNNH6', 
                  security_type='Common Stock', 
                  market_sector='Equity', 
                  ticker='IBM', 
                  name='INTL BUSINESS MACHINES CORP', 
                  exch_code='US', 
                  share_class_figi='BBG001S5S399', 
                  composite_figi='BBG000BLNNH6', 
                  security_type2='Common Stock', 
                  security_description='IBM', 
                  metadata=None
            )
        ]
    )
]
```

A `MappingJobResult` can either be a `MappingJobResultFigiList`, a
`MappingJobResultFigiNotFound` or a `MappingJobResultError`.

The `MappingJob` object has 2 required properties which are `id_type` and
`id_value`. The other properties are optional but subject to specific rules in
case they are provided. These rules are modeled and checked using **Pydantic**.

Below is the full list of properties for `MappingJob`:

| property                  | required | type | example                                  |
| ------------------------- | -------- | ---- | ---------------------------------------- |
| id_type                   | X        | str  | `"TICKER"`                               |
| id_value                  | X        | str  | `"IBM"`                                  |
| exch_code                 |          | str  | `"UN"`                                   |
| mic_code                  |          | str  | `"XNYS"`                                 |
| currency                  |          | str  | `"USD"`                                  |
| market_sec_des            |          | str  | `"Equity"`                               |
| security_type             |          | str  | `"Common Stock"`                         |
| security_type_2           |          | str  | `"Common Stock"`                         |
| include_unlisted_equities |          | bool |                                          |
| option_type               |          | str  | `"Call"`                                 |
| strike                    |          | list | `[100, 200]`                             |
| contract_size             |          | list | `[0, 100]`                               |
| coupon                    |          | list | `[0, 2.5]`                               |
| expiration                |          | list | `[date(2023, 6, 1), date(2023, 12, 31)]` |
| maturity                  |          | list | `[date(2023, 6, 1), date(2023, 12, 31)]` |
| state_code                |          | str  | `"AZ"`                                   |

Some of the properties in the `MappingJob` are "enum-like". For each of these
properties, it is possible to retrieve the current list of accepted values via
specific methods:

| property        | method                   | examples |
| --------------- | ------------------------ | -------- |
| id_type         | `get_id_types()`         |          |
| exch_code       | `get_exch_codes()`       |          |
| mic_code        | `get_mic_codes()`        |          |
| currency        | `get_currencies()`       |          |
| market_sec_des  | `get_market_sec_des()`   |          |
| security_type   | `get_security_types()`   |          |
| security_type_2 | `get_security_types_2()` |          |
| state_code      | `get_state_codes()`      |          |

For example, to retrieve the current values for `id_type`:

```python
from openfigi_client import OpenFigiSync

id_types = OpenFigiSync().get_id_types()
```

## Filtering

The `filter()` method takes a `Filter` object as argument and returns a list of
`FigiResult`.

- The `Filter` object is very similar to the `MappingJob` object
- The only difference are that the `id_type` and `id_value` are replaced by a
  single `query` property
- All the "enum-like" properties are the same and the list of accepted values is
  the same
- The maximum number of results is limited to 15,000

| property                  | required | type | example                                  |
| ------------------------- | -------- | ---- | ---------------------------------------- |
| query                     | X        | str  | `"SJIM"`                                 |
| exch_code                 |          | str  | `"UN"`                                   |
| mic_code                  |          | str  | `"XNYS"`                                 |
| currency                  |          | str  | `"USD"`                                  |
| market_sec_des            |          | str  | `"Equity"`                               |
| security_type             |          | str  | `"Common Stock"`                         |
| security_type_2           |          | str  | `"Common Stock"`                         |
| include_unlisted_equities |          | bool |                                          |
| option_type               |          | str  | `"Call"`                                 |
| strike                    |          | list | `[100, 200]`                             |
| contract_size             |          | list | `[0, 100]`                               |
| coupon                    |          | list | `[0, 2.5]`                               |
| expiration                |          | list | `[date(2023, 6, 1), date(2023, 12, 31)]` |
| maturity                  |          | list | `[date(2023, 6, 1), date(2023, 12, 31)]` |
| state_code                |          | str  | `"AZ"`                                   |

Example

```python
from openfigi_client import OpenFigiSync, Filter

query = Filter(query="SJIM")
results = OpenFigiSync().filter(query)
```

In order to know the total number of matches for a given query before starting
to request them, it is possible to use the `get_total_number_of_matches()`
method:

```python
from openfigi_client import OpenFigiSync, Filter

query = Filter(query="SJIM")
number_of_results = OpenFigiSync().get_total_number_of_matches(query)

>>> number_of_results
36
```

## Troubleshooting

Several kinds of errors can occur.

- `ValidationError`: the `MappingJob` and `Filter` objects are modelled using
  **msgspec** and therefore need to be properly instantiated. If an error
  occurs, a `msgspec.ValidationError` will be raised.

- `HTTPError`: in case the status code of the HTTP response is not 200, an
  HTTPError exception will be raised. Note: in case a particular symbol is not
  found, the API will still respond with a status 200 and a `MappingNotFound`
  object. HTTP errors only occur if there is a real error like a malformed
  `MappingJob` request (which should not happen since all `MappingJob` objects
  are checked by **Pydantic** prior to being sent), a rate limitation or an
  internal server error.

Here is how to check for `ValidationError` in case the mapping jobs are
instantiated programmatically:

```python
from msgspec import ValidationError

from openfigi_client import MappingJob

tickers = ["IBM", "XRX", "TSLA", None, "MSFT"]

mapping_jobs = []
for ticker in tickers:
    try:
        mapping_job = MappingJob(id_type="TICKER", id_value=ticker, exch_code="US")
        mapping_jobs.append(mapping_job)
    except ValidationError:
        print(f"Error when trying to build a MappingJob with {ticker=}")
        # Do something
        continue
```

And here is how to check for `HTTPError` in case exceptions need to be handled:

```python
from openfigi_client import OpenFigiSync, MappingJob
from openfigi_client.exceptions import HTTPError

mapping_jobs = [MappingJob(id_type="TICKER", id_value="IBM", exch_code="US")]

try:
    results = OpenFigiSync().map(mapping_jobs)
except HTTPError as e:
    print(f"{e}")
    # Do something
```
