Skip to content

Commit

Permalink
Merge branch 'main' into rustcurve
Browse files Browse the repository at this point in the history
# Conflicts:
#	python/rateslib/curves/curves.py
  • Loading branch information
attack68 committed Dec 15, 2024
2 parents 77522e4 + c03e16a commit 3f7eb7d
Show file tree
Hide file tree
Showing 26 changed files with 1,215 additions and 929 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ubuntu-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ jobs:
- name: Python Ruff formatting
run: |
ruff format
- name: Python typing with Mypy
run: |
mypy --config-file pyproject.toml
- name: Test with pytest and display Coverage
run: |
coverage run -m --source=rateslib pytest
Expand Down
12 changes: 10 additions & 2 deletions docs/source/i_developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Clone the repository and navigate to the directory.
**2) Setup Python and Rust**

It is recommended to **install Python 3.12 and Rust 1.80**.
It is recommended to **install Python 3.13 and Rust 1.80**.

Create and activate a virtual Python environment, as below
(or the equivalent for Windows).
Expand Down Expand Up @@ -52,7 +52,7 @@ After installing everything test that everything is successful.
**5) Making changes**

You can now edit and make code changes and submit them for addition to
the package. The continuous integration (CI) checks on github perform 7 tasks:
the package. The continuous integration (CI) checks on github perform 8 tasks:

- **Rust Checks**

Expand Down Expand Up @@ -107,6 +107,14 @@ the package. The continuous integration (CI) checks on github perform 7 tasks:
(venv) rateslib/>$ ruff format
- ``mypy``: type checking the Python code to ensure type consistency. You should locally
run the following and fix any issues before committing (note ensure you are using the
latest supported Python and mypy versions, inline with the github server):

.. code-block::
(venv) rateslib/>$ mypy
- ``coverage run -m pytest``: this runs all the pytests and measures the coverage
report of the codebase which should remain > 96%. You can this locally also
before committing by using:
Expand Down
11 changes: 11 additions & 0 deletions docs/source/i_whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ email contact, see `rateslib <https://rateslib.com>`_.
Replicable functionality is achieved by importing and using the internal method
:meth:`rateslib.fx.FXForwards._rate_with_path`.
(`537 <https://github.com/attack68/rateslib/pull/537>`_)
* - Refactor
- :red:`Minor Breaking Change!` :meth:`FXForwards.update <rateslib.fx.FXForwards.update>`
has dropped the ``fx_curves`` argument and amended the ``fx_rates`` argument to
provide a safer architecture for mutability of objects after market data changes.
(`544 <https://github.com/attack68/rateslib/pull/544>`_)
* - Refactor
- :red:`Minor Breaking Change!` :meth:`Curve.to_json <rateslib.curves.Curve.to_json>`
has refactored its JSON format to include the Rust calendar serialization implementations
introduced in v1.3.0. This should not be noticeable on round trips, i.e. using
``from_json`` on the output from ``to_json``.
(`552 <https://github.com/attack68/rateslib/pull/552>`_)

1.6.0 (30th November 2024)
****************************
Expand Down
16 changes: 11 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ dev = [
"coverage>=7.6.1,<8.0",
# style/linting
"ruff>=0.6.3,<1.0",
"mypy>=1.13,<2.0",
"pandas-stubs>2.0,<3.0",
# doc building
"sphinx>=8.0,<9.0",
"sphinx-automodapi>=0.16.0,<1.0",
Expand Down Expand Up @@ -137,11 +139,15 @@ ignore = [
"python/tests/*" = ["F401", "B", "N", "S", "ANN"]

[tool.mypy]
files = [
"python/rateslib/calendars/**/*.py",
"python/rateslib/fx/**/*.py",
"python/rateslib/dual/**/*.py",
"python/rateslib/scheduling.py"
packages = [
"rateslib"
]
exclude = [
"/instruments",
"fx_volatility.py",
"legs.py",
"periods.py",
"solver.py",
]
strict = true

Expand Down
6 changes: 3 additions & 3 deletions python/rateslib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Let users know if they're missing any of our hard dependencies
_hard_dependencies = ("pandas", "matplotlib", "numpy")
_missing_dependencies = []
_missing_dependencies: list[str] = []

for _dependency in _hard_dependencies:
try:
Expand Down Expand Up @@ -31,7 +31,7 @@ class default_context(ContextDecorator):
... pass
"""

def __init__(self, *args) -> None:
def __init__(self, *args) -> None: # type: ignore[no-untyped-def]
if len(args) % 2 != 0 or len(args) < 2:
raise ValueError("Need to invoke as option_context(pat, val, [(pat, val), ...]).")

Expand All @@ -43,7 +43,7 @@ def __enter__(self) -> None:
for pat, val in self.ops:
setattr(defaults, pat, val)

def __exit__(self, *args) -> None:
def __exit__(self, *args) -> None: # type: ignore[no-untyped-def]
if self.undo:
for pat, val in self.undo:
setattr(defaults, pat, val)
Expand Down
16 changes: 9 additions & 7 deletions python/rateslib/_spec_loader.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import os
from typing import Any

import pandas as pd
from packaging import version
from pandas import DataFrame

DEVELOPMENT = True
if DEVELOPMENT:
Expand All @@ -12,18 +14,18 @@
# So when packaging output the INSTRUMENT_SPEC dict and paste into the non-development
# section.

def _append_kwargs_name(df):
def _append_kwargs_name(df: DataFrame) -> DataFrame:
"""combine the columns leg and kwargs to produce library consistent kwargs for dicts"""
prefix = df["leg"]
prefix = prefix.where(prefix == "leg2", "")
prefix = prefix.replace("leg2", "leg2_")
df["kwargs_name"] = prefix + df["kwarg"]
return df.set_index("kwargs_name")

def _parse_bool(df):
def _parse_bool(df: DataFrame) -> DataFrame:
"""parse data input as bools to return True and False dtypes."""

def _map_true_false(v):
def _map_true_false(v: str) -> bool | None:
try:
if v.upper() == "TRUE":
return True
Expand All @@ -39,7 +41,7 @@ def _map_true_false(v):
# TODO (low): clean this up when setting a minimum pandas version at 2.1.0
df[df["dtype"] == "bool"] = df[df["dtype"] == "bool"].map(_map_true_false)
else:
df[df["dtype"] == "bool"] = df[df["dtype"] == "bool"].applymap(_map_true_false)
df[df["dtype"] == "bool"] = df[df["dtype"] == "bool"].applymap(_map_true_false) # type: ignore[operator]
return df

path = "data/__instrument_spec.csv"
Expand All @@ -57,19 +59,19 @@ def _map_true_false(v):
"int": int,
}

def _map_dtype(v):
def _map_dtype(v: str) -> Any:
try:
return DTYPE_MAP[v]
except KeyError:
return v

def _map_str_int(v):
def _map_str_int(v: Any) -> Any:
try:
return int(v)
except ValueError:
return v

def _get_kwargs(spec):
def _get_kwargs(spec: str) -> dict[str, Any]:
"""From the legs DataFrame extract the relevant column and ensure dtypes are suitable."""
# get values that are not null
s = df_legs[spec]
Expand Down
4 changes: 2 additions & 2 deletions python/rateslib/calendars/dcfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,8 @@ def _dcf_bus252(
"ACTACTICMA_STUB365F": 1 / 365.25,
"ACTACTISMA": 1.0 / 365.25,
"ACTACTBOND": 1.0 / 365.25,
"1": None,
"1+": None,
"1": 1.0 / 365.25,
"1+": 1.0 / 365.25,
"BUS252": 1.0 / 252,
}

Expand Down
36 changes: 4 additions & 32 deletions python/rateslib/calendars/rs.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,47 +123,19 @@ def get_calendar(
tgt_and_nyc_cal.holidays[300:312]
"""
_: CalTypes = _get_calendar_with_kind(calendar=calendar, named=named)[0]
return _


def _get_calendar_with_kind(
calendar: CalInput,
named: bool = True,
) -> tuple[CalTypes, str]:
"""
Returns a calendar object either from an available set or a user defined input.
Parameters
----------
calendar : str, Cal, UnionCal, NamedCal
If `str`, then the calendar is returned from pre-calculated values.
If a specific user defined calendar this is returned without modification.
named : bool
If `True` will return a :class:`~rateslib.calendars.NamedCal` object, which is more
compactly serialized, otherwise will parse an input string and return a
:class:`~rateslib.calendars.Cal` or :class:`~rateslib.calendars.UnionCal` directly.
Returns
-------
tuple[NamedCal | Cal | UnionCal, str]
"""
# TODO: rename calendars or make a more generalist statement about their names.
# these object categorisations do not seem to make sense.
if isinstance(calendar, str):
if named:
try:
return NamedCal(calendar), "object"
return NamedCal(calendar)
except ValueError:
# try parsing with Python only
pass
# parse the string in Python and return Rust Cal/UnionCal objects directly
return _parse_str_calendar(calendar), "named"
return _parse_str_calendar(calendar)
elif isinstance(calendar, NoInput):
return defaults.calendars["all"], "null"
return defaults.calendars["all"]
else: # calendar is a Calendar object type
return calendar, "custom"
return calendar


def _parse_str_calendar(calendar: str) -> CalTypes:
Expand Down
Loading

0 comments on commit 3f7eb7d

Please sign in to comment.