Skip to content

Commit

Permalink
Add LRU caching to properties to improve performance.
Browse files Browse the repository at this point in the history
  • Loading branch information
jadchaar committed Jan 9, 2022
1 parent c2b03c1 commit 27da135
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 28 deletions.
5 changes: 2 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repos:
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v2.30.1
rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py36-plus]
Expand All @@ -39,7 +39,6 @@ repos:
hooks:
- id: python-no-eval
- id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore
- id: python-check-mock-methods
- id: python-use-type-annotations
- id: rst-backticks
Expand All @@ -57,7 +56,7 @@ repos:
- id: flake8
additional_dependencies: [flake8-bugbear]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.930'
rev: 'v0.931'
hooks:
- id: mypy
additional_dependencies: [pandas-stubs, types-requests]
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.1.0 - 1/9/22

### New

- Added an LRU cache to all StockMapper and MutualFundMapper properties. This should result in performance improvements on repeated calls to the mapper properties.

## 2.0.2 - 12/29/21

### Internal
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Jad Chaar
Copyright (c) 2022 Jad Chaar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# -- Project information -----------------------------------------------------

project = "SEC CIK Mapper 🗺"
copyright = "2021, Jad Chaar"
copyright = "2022, Jad Chaar"
author = "Jad Chaar"

release = about["__version__"]
Expand Down
11 changes: 7 additions & 4 deletions sec_cik_mapper/BaseMapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import requests

from .retrievers import MutualFundRetriever, StockRetriever
from .types import CompanyData, FieldIndices, Fields, KeyToValueSet
from .types import CompanyData, FieldIndices, Fields, KeyToValueSet, cache


class BaseMapper:
Expand Down Expand Up @@ -86,7 +86,8 @@ def _form_kv_mapping(self, keys: pd.Series, values: pd.Series) -> Dict[str, str]
"""Form key-value mapping, ignoring blank keys and values."""
return {k: v for k, v in zip(keys, values) if k and v}

@property
@property # type: ignore
@cache
def cik_to_tickers(self) -> KeyToValueSet:
"""Get CIK to tickers mapping.
Expand All @@ -104,7 +105,8 @@ def cik_to_tickers(self) -> KeyToValueSet:
ticker_col = self.mapping_metadata["Ticker"]
return self._form_kv_set_mapping(cik_col, ticker_col)

@property
@property # type: ignore
@cache
def ticker_to_cik(self) -> Dict[str, str]:
"""Get ticker to CIK mapping.
Expand All @@ -122,7 +124,8 @@ def ticker_to_cik(self) -> Dict[str, str]:
ticker_col = self.mapping_metadata["Ticker"]
return self._form_kv_mapping(ticker_col, cik_col)

@property
@property # type: ignore
@cache
def raw_dataframe(self) -> pd.DataFrame:
"""Get raw pandas dataframe.
Expand Down
29 changes: 19 additions & 10 deletions sec_cik_mapper/MutualFundMapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .BaseMapper import BaseMapper
from .retrievers import MutualFundRetriever
from .types import KeyToValueSet
from .types import KeyToValueSet, cache


class MutualFundMapper(BaseMapper):
Expand All @@ -23,7 +23,8 @@ def __init__(self) -> None:
"""Constructor for the :class:`MutualFundMapper` class."""
super().__init__(MutualFundMapper._retriever)

@property
@property # type: ignore
@cache
def cik_to_series_ids(self) -> KeyToValueSet:
"""Get CIK to series ID mapping.
Expand All @@ -38,7 +39,8 @@ def cik_to_series_ids(self) -> KeyToValueSet:
series_id_col = self.mapping_metadata["Series ID"]
return self._form_kv_set_mapping(cik_col, series_id_col)

@property
@property # type: ignore
@cache
def ticker_to_series_id(self) -> Dict[str, str]:
"""Get ticker to series ID mapping.
Expand All @@ -53,7 +55,8 @@ def ticker_to_series_id(self) -> Dict[str, str]:
series_id_col = self.mapping_metadata["Series ID"]
return self._form_kv_mapping(ticker_col, series_id_col)

@property
@property # type: ignore
@cache
def series_id_to_cik(self) -> Dict[str, str]:
"""Get series ID to CIK mapping.
Expand All @@ -68,7 +71,8 @@ def series_id_to_cik(self) -> Dict[str, str]:
series_id_col = self.mapping_metadata["Series ID"]
return self._form_kv_mapping(series_id_col, cik_col)

@property
@property # type: ignore
@cache
def series_id_to_tickers(self) -> KeyToValueSet:
"""Get series ID to tickers mapping.
Expand All @@ -83,7 +87,8 @@ def series_id_to_tickers(self) -> KeyToValueSet:
series_id_col = self.mapping_metadata["Series ID"]
return self._form_kv_set_mapping(series_id_col, ticker_col)

@property
@property # type: ignore
@cache
def series_id_to_class_ids(self) -> KeyToValueSet:
"""Get series ID to class IDs mapping.
Expand All @@ -98,7 +103,8 @@ def series_id_to_class_ids(self) -> KeyToValueSet:
series_id_col = self.mapping_metadata["Series ID"]
return self._form_kv_set_mapping(series_id_col, class_id_col)

@property
@property # type: ignore
@cache
def ticker_to_class_id(self) -> Dict[str, str]:
"""Get ticker to class ID mapping.
Expand All @@ -113,7 +119,8 @@ def ticker_to_class_id(self) -> Dict[str, str]:
class_id_col = self.mapping_metadata["Class ID"]
return self._form_kv_mapping(ticker_col, class_id_col)

@property
@property # type: ignore
@cache
def cik_to_class_ids(self) -> KeyToValueSet:
"""Get CIK to class IDs mapping.
Expand All @@ -128,7 +135,8 @@ def cik_to_class_ids(self) -> KeyToValueSet:
class_id_col = self.mapping_metadata["Class ID"]
return self._form_kv_set_mapping(cik_col, class_id_col)

@property
@property # type: ignore
@cache
def class_id_to_cik(self) -> Dict[str, str]:
"""Get class ID to CIK mapping.
Expand All @@ -143,7 +151,8 @@ def class_id_to_cik(self) -> Dict[str, str]:
class_id_col = self.mapping_metadata["Class ID"]
return self._form_kv_mapping(class_id_col, cik_col)

@property
@property # type: ignore
@cache
def class_id_to_ticker(self) -> Dict[str, str]:
"""Get class ID to ticker mapping.
Expand Down
20 changes: 13 additions & 7 deletions sec_cik_mapper/StockMapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .BaseMapper import BaseMapper
from .retrievers import StockRetriever
from .types import KeyToValueSet
from .types import KeyToValueSet, cache


class StockMapper(BaseMapper):
Expand All @@ -23,7 +23,8 @@ def __init__(self) -> None:
"""Constructor for the :class:`StockMapper` class."""
super().__init__(StockMapper._retriever)

@property
@property # type: ignore
@cache
def cik_to_company_name(self) -> Dict[str, str]:
"""Get CIK to company name mapping.
Expand All @@ -38,7 +39,8 @@ def cik_to_company_name(self) -> Dict[str, str]:
company_name_col = self.mapping_metadata["Name"]
return self._form_kv_mapping(cik_col, company_name_col)

@property
@property # type: ignore
@cache
def ticker_to_company_name(self) -> Dict[str, str]:
"""Get ticker to company name mapping.
Expand All @@ -53,7 +55,8 @@ def ticker_to_company_name(self) -> Dict[str, str]:
company_name_col = self.mapping_metadata["Name"]
return self._form_kv_mapping(ticker_col, company_name_col)

@property
@property # type: ignore
@cache
def ticker_to_exchange(self) -> Dict[str, str]:
"""Get ticker to exchange mapping.
Expand All @@ -68,7 +71,8 @@ def ticker_to_exchange(self) -> Dict[str, str]:
exchange_col = self.mapping_metadata["Exchange"]
return self._form_kv_mapping(ticker_col, exchange_col)

@property
@property # type: ignore
@cache
def exchange_to_tickers(self) -> KeyToValueSet:
"""Get exchange to tickers mapping.
Expand All @@ -83,7 +87,8 @@ def exchange_to_tickers(self) -> KeyToValueSet:
exchange_col = self.mapping_metadata["Exchange"]
return self._form_kv_set_mapping(exchange_col, ticker_col)

@property
@property # type: ignore
@cache
def cik_to_exchange(self) -> Dict[str, str]:
"""Get CIK to exchange mapping.
Expand All @@ -98,7 +103,8 @@ def cik_to_exchange(self) -> Dict[str, str]:
exchange_col = self.mapping_metadata["Exchange"]
return self._form_kv_mapping(cik_col, exchange_col)

@property
@property # type: ignore
@cache
def exchange_to_ciks(self) -> KeyToValueSet:
"""Get exchange to CIKs mapping.
Expand Down
2 changes: 1 addition & 1 deletion sec_cik_mapper/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.0.2"
__version__ = "2.1.0"
9 changes: 8 additions & 1 deletion sec_cik_mapper/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Dict, List, Set, Union
from functools import lru_cache
from typing import Callable, Dict, List, Set, TypeVar, Union

from typing_extensions import Literal, TypedDict

Expand Down Expand Up @@ -28,3 +29,9 @@ class MutualFundFieldIndices(TypedDict):
CompanyData = List[List[Union[int, str]]]

KeyToValueSet = Dict[str, Set[str]]

T = TypeVar("T")


def cache(func: Callable[..., T]) -> T:
return lru_cache()(func) # type: ignore
18 changes: 18 additions & 0 deletions tests/test_mutual_fund_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,21 @@ def test_class_id_to_ticker(
ticker = vtsax_mutual_fund["ticker"]
assert class_id in class_id_to_ticker
assert class_id_to_ticker[class_id] == ticker


def test_caching(mutual_fund_mapper: MutualFundMapper):
# Clear cache
MutualFundMapper.ticker_to_cik.fget.cache_clear() # type: ignore
n = 1000

for _ in range(n):
mutual_fund_mapper.ticker_to_cik

# Verify cache hits and misses
cache_info = MutualFundMapper.ticker_to_cik.fget.cache_info() # type: ignore

expected_misses = 1
assert cache_info.misses == expected_misses

expected_hits = n - expected_misses
assert cache_info.hits == expected_hits
18 changes: 18 additions & 0 deletions tests/test_stock_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,21 @@ def test_save_metadata_to_csv(stock_mapper: StockMapper, tmp_path: Path):
assert tmp_csv_path.exists()
df = pd.read_csv(tmp_csv_path)
assert len(df) == len(stock_mapper.raw_dataframe)


def test_caching(stock_mapper: StockMapper):
# Clear cache
StockMapper.ticker_to_cik.fget.cache_clear() # type: ignore
n = 1000

for _ in range(n):
stock_mapper.ticker_to_cik

# Verify cache hits and misses
cache_info = StockMapper.ticker_to_cik.fget.cache_info() # type: ignore

expected_misses = 1
assert cache_info.misses == expected_misses

expected_hits = n - expected_misses
assert cache_info.hits == expected_hits

0 comments on commit 27da135

Please sign in to comment.