Skip to content

Commit

Permalink
DEP: drop support for Python 3.9 (#540)
Browse files Browse the repository at this point in the history
  • Loading branch information
attack68 authored Dec 8, 2024
1 parent 069258c commit f05f82b
Show file tree
Hide file tree
Showing 24 changed files with 102 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-minimum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows-minimum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9"]
python-version: ["3.10"]
env:
MPLBACKEND: Agg # https://github.com/orgs/community/discussions/26434

Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rateslib"
version = "1.6.0"
version = "1.7.0"
edition = "2021"
exclude = [".github/*", "benches/*", "notebooks/*", ".readthedocs.yaml", "docs/*", "PACKAGING.md"]

Expand Down Expand Up @@ -40,10 +40,10 @@ serde_json = "1.0"

[features]
# multiple-pymethods = ["pyo3/multiple-pymethods"]
abi3-py39 = ["pyo3/abi3-py39"]
abi3-py310 = ["pyo3/abi3-py310"]
pyo3-chrono = ["pyo3/chrono"]
pyo3-indexmap = ["pyo3/indexmap"]
default = ["abi3-py39", "pyo3-chrono", "pyo3-indexmap"]
default = ["abi3-py310", "pyo3-chrono", "pyo3-indexmap"]
# 'extension-module' has been added to 'features' of [tool.maturin] in pyproject.toml
#extension-module = ["pyo3/extension-module"]
#default = ["extension-module", "abi3-py39", "chrono"]
Expand Down
16 changes: 8 additions & 8 deletions docs/source/i_get_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,19 @@ earlier pre-rust 1.1.0 version.
* - Python
- 3.13
- 3.12
- 3.9
- 3.10 (Oct '21)
* - NumPy
- 2.1.2
- 1.26.1
- 1.21.5
- 2.1.3
- 1.26.4
- 1.21.5 (Dec '21)
* - Pandas
- 2.2.3
- 2.2.2
- 1.4.1
- 1.4.1 (Feb '22)
* - Matplotlib
- 3.9.2
- 3.9.2
- 3.5.1
- 3.9.3
- 3.9.3
- 3.5.1 (Dec '21)


Introduction to Rateslib
Expand Down
2 changes: 2 additions & 0 deletions docs/source/i_whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ email contact, see `rateslib <https://rateslib.com>`_.
(`532 <https://github.com/attack68/rateslib/pull/532>`_)
(`535 <https://github.com/attack68/rateslib/pull/535>`_)
(`536 <https://github.com/attack68/rateslib/pull/536>`_)
* - Dependencies
- Drop support for Python 3.9, only versions 3.10 - 3.13 now supported.
* - Refactor
- :red:`Minor Breaking Change!` :meth:`~rateslib.calendars.get_calendar` has dropped the
``kind`` argument being only useful internally.
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ features = ["pyo3/extension-module"]

[project]
name = "rateslib"
version = "1.6.0"
version = "1.7.0"
description = "A fixed income library for trading interest rates"
readme = "README.md"
authors = [{ name = "J H M Darbyshire"}]
Expand All @@ -26,7 +26,7 @@ dependencies = [
"matplotlib>=3.5.1,<4.0",
"pandas>=1.4.1,<3.0",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
Expand Down Expand Up @@ -91,7 +91,7 @@ exclude = [
line-length = 100
indent-width = 4
# Assume Python 3.12
target-version = "py39"
target-version = "py310"

[tool.ruff.format]
quote-style = "double"
Expand Down Expand Up @@ -151,4 +151,4 @@ src_paths = ["python"]

[tool.black]
line-length = 100
target-version = ['py39']
target-version = ['py310']
4 changes: 2 additions & 2 deletions python/rateslib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(self, *args) -> None:
if len(args) % 2 != 0 or len(args) < 2:
raise ValueError("Need to invoke as option_context(pat, val, [(pat, val), ...]).")

self.ops = list(zip(args[::2], args[1::2]))
self.ops = list(zip(args[::2], args[1::2], strict=False))

def __enter__(self) -> None:
self.undo = [(pat, getattr(defaults, pat, None)) for pat, _ in self.ops]
Expand Down Expand Up @@ -244,4 +244,4 @@ def __exit__(self, *args) -> None:
"FXBrokerFly",
]

__version__ = "1.6.0"
__version__ = "1.7.0"
2 changes: 1 addition & 1 deletion python/rateslib/calendars/dcfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import calendar as calendar_mod
import warnings
from collections.abc import Callable
from datetime import datetime
from typing import Callable

from rateslib.calendars.rs import CalInput, _get_modifier, _get_rollday, get_calendar
from rateslib.default import NoInput
Expand Down
8 changes: 3 additions & 5 deletions python/rateslib/calendars/rs.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from typing import Union

from rateslib import defaults
from rateslib.default import NoInput
from rateslib.rs import Cal, Modifier, NamedCal, RollDay, UnionCal

CalTypes = Union[Cal, UnionCal, NamedCal]
CalInput = Union[CalTypes, str, NoInput]
CalTypes = Cal | UnionCal | NamedCal
CalInput = CalTypes | str | NoInput


def _get_rollday(roll: Union[str, int, NoInput]) -> RollDay:
def _get_rollday(roll: str | int | NoInput) -> RollDay:
if isinstance(roll, str):
return {
"EOM": RollDay.EoM(),
Expand Down
12 changes: 9 additions & 3 deletions python/rateslib/curves/curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@

import json
import warnings
from collections.abc import Callable
from datetime import datetime, timedelta
from math import comb, floor
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any
from uuid import uuid4

import numpy as np
Expand Down Expand Up @@ -1194,7 +1195,12 @@ def plot(
y_ = [y] if not difference else []
for _, comparator in enumerate(comparators):
if difference:
y_.append([self._plot_diff(_x, tenor, _y, comparator) for _x, _y in zip(x, y)])
y_.append(
[
self._plot_diff(_x, tenor, _y, comparator)
for _x, _y in zip(x, y, strict=False)
]
)
else:
pm_ = comparator._plot_modifier(tenor)
y_.append([comparator._plot_rate(_x, tenor, pm_) for _x in x])
Expand Down Expand Up @@ -2209,7 +2215,7 @@ def __init__(

def _validate_curve_collection(self):
"""Perform checks to ensure CompositeCurve can exist"""
if type(self) is MultiCsaCurve and isinstance(self.curves[0], (LineCurve, IndexCurve)):
if type(self) is MultiCsaCurve and isinstance(self.curves[0], LineCurve | IndexCurve):
raise TypeError("Multi-CSA curves must be of type `Curve`.")

if type(self) is MultiCsaCurve and self.multi_csa_min_step > self.multi_csa_max_step:
Expand Down
17 changes: 8 additions & 9 deletions python/rateslib/dual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import math
from functools import partial
from statistics import NormalDist
from typing import Union

import numpy as np

Expand All @@ -13,8 +12,8 @@
Dual.__doc__ = "Dual number data type to perform first derivative automatic differentiation."
Dual2.__doc__ = "Dual number data type to perform second derivative automatic differentiation."

DualTypes = Union[float, Dual, Dual2, Variable]
Number = Union[float, Dual, Dual2]
DualTypes = float | Dual | Dual2 | Variable
Number = float | Dual | Dual2

# Licence: Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International
# Commercial use of this code, and/or copying and redistribution is prohibited.
Expand Down Expand Up @@ -71,7 +70,7 @@ def set_order_convert(
-------
float, Dual, Dual2
"""
if isinstance(val, (*FLOATS, *INTS)):
if isinstance(val, FLOATS | INTS):
_ = [] if tag is None else tag
if order == 0:
return float(val)
Expand Down Expand Up @@ -120,7 +119,7 @@ def gradient(
-------
float, ndarray, Dual2
"""
if not isinstance(dual, (Dual, Dual2, Variable)):
if not isinstance(dual, Dual | Dual2 | Variable):
raise TypeError("Can call `gradient` only on dual-type variables.")
if order == 1:
if isinstance(dual, Variable):
Expand Down Expand Up @@ -157,7 +156,7 @@ def dual_exp(x: DualTypes) -> Number:
-------
float, Dual, Dual2
"""
if isinstance(x, (Dual, Dual2, Variable)):
if isinstance(x, Dual | Dual2 | Variable):
return x.__exp__()
return math.exp(x)

Expand All @@ -177,7 +176,7 @@ def dual_log(x: DualTypes, base: int | None = None) -> Number:
-------
float, Dual, Dual2
"""
if isinstance(x, (Dual, Dual2, Variable)):
if isinstance(x, Dual | Dual2 | Variable):
val = x.__log__()
if base is None:
return val
Expand Down Expand Up @@ -216,7 +215,7 @@ def dual_norm_cdf(x: DualTypes) -> Number:
-------
float, Dual, Dual2
"""
if isinstance(x, (Dual, Dual2, Variable)):
if isinstance(x, Dual | Dual2 | Variable):
return x.__norm_cdf__()
else:
return NormalDist().cdf(x)
Expand All @@ -234,7 +233,7 @@ def dual_inv_norm_cdf(x: DualTypes) -> Number:
-------
float, Dual, Dual2
"""
if isinstance(x, (Dual, Dual2, Variable)):
if isinstance(x, Dual | Dual2 | Variable):
return x.__norm_inv_cdf__()
else:
return NormalDist().inv_cdf(x)
Expand Down
12 changes: 6 additions & 6 deletions python/rateslib/dual/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from rateslib.rs import Dual, Dual2

PRECISION = 1e-14
FLOATS = (float, np.float16, np.float32, np.float64, np.longdouble)
INTS = (int, np.int8, np.int16, np.int32, np.int32, np.int64)
FLOATS = float | np.float16 | np.float32 | np.float64 | np.longdouble
INTS = int | np.int8 | np.int16 | np.int32 | np.int32 | np.int64


class Variable:
Expand Down Expand Up @@ -108,7 +108,7 @@ def __add__(self, other: Dual | Dual2 | float | int | Variable) -> Dual | Dual2:
_1 = self._to_dual_type(defaults._global_ad_order)
_2 = other._to_dual_type(defaults._global_ad_order)
return _1.__add__(_2)
elif isinstance(other, (FLOATS, INTS)):
elif isinstance(other, FLOATS | INTS):
return Variable(self.real + float(other), vars=self.vars, dual=self.dual)
elif isinstance(other, Dual):
_ = Dual(self.real, vars=self.vars, dual=self.dual)
Expand All @@ -133,7 +133,7 @@ def __mul__(self, other: Dual | Dual2 | float | int | Variable) -> Dual | Dual2:
_1 = self._to_dual_type(defaults._global_ad_order)
_2 = other._to_dual_type(defaults._global_ad_order)
return _1.__mul__(_2)
elif isinstance(other, (FLOATS, INTS)):
elif isinstance(other, FLOATS | INTS):
return Variable(self.real * float(other), vars=self.vars, dual=self.dual * float(other))
elif isinstance(other, Dual):
_ = Dual(self.real, vars=self.vars, dual=self.dual)
Expand All @@ -152,7 +152,7 @@ def __truediv__(self, other: Dual | Dual2 | float | int | Variable) -> Dual | Du
_1 = self._to_dual_type(defaults._global_ad_order)
_2 = other._to_dual_type(defaults._global_ad_order)
return _1.__truediv__(_2)
elif isinstance(other, (FLOATS, INTS)):
elif isinstance(other, FLOATS | INTS):
return Variable(self.real / float(other), vars=self.vars, dual=self.dual / float(other))
elif isinstance(other, Dual):
_ = Dual(self.real, vars=self.vars, dual=self.dual)
Expand All @@ -167,7 +167,7 @@ def __rtruediv__(self, other: Dual | Dual2 | float | int | Variable) -> Dual | D
if isinstance(other, Variable):
# cannot reach this line
raise TypeError("Impossible line execution - please report issue.") # pragma: no cover
elif isinstance(other, (FLOATS, INTS)):
elif isinstance(other, FLOATS | INTS):
_1 = Variable(other, ())
return _1 / self
elif isinstance(other, Dual):
Expand Down
4 changes: 2 additions & 2 deletions python/rateslib/fx/fx_forwards.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ def positions(
)
"""
if isinstance(value, (float, int)):
if isinstance(value, float | int):
value = Dual(value, [], [])
base_: str = self.base if isinstance(base, NoInput) else base.lower()
_ = np.array(
Expand Down Expand Up @@ -961,7 +961,7 @@ def curve(
from the combination of curves and FX rates that are available within
the given :class:`FXForwards` instance.
"""
if isinstance(collateral, (list, tuple)):
if isinstance(collateral, list | tuple):
curves = []
for coll in collateral:
curves.append(self.curve(cashflow, coll, convention, modifier, calendar))
Expand Down
2 changes: 1 addition & 1 deletion python/rateslib/fx/fx_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ def positions(
fxr.positions(100, base="nok")
"""
if isinstance(value, (float, int)):
if isinstance(value, float | int):
value = Dual(value, [], [])
base_: str = self.base if isinstance(base, NoInput) else base.lower()
_ = np.array([0 if ccy != base_ else float(value) for ccy in self.currencies_list])
Expand Down
16 changes: 10 additions & 6 deletions python/rateslib/fx_volatility.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from datetime import datetime, timedelta
from datetime import datetime as dt
from typing import Union
from uuid import uuid4

import numpy as np
Expand Down Expand Up @@ -591,7 +590,7 @@ def plot(
/ 100.0
* (dual_inv_norm_cdf(_1) * _2 * self.t_expiry_sqrt * _2 / 100.0),
)
for (_1, _2) in zip(x, vols)
for (_1, _2) in zip(x, vols, strict=False)
]

if not difference:
Expand Down Expand Up @@ -715,7 +714,7 @@ def __init__(
self.delta_type = _validate_delta_type(delta_type)
self.smiles = [
FXDeltaVolSmile(
nodes=dict(zip(self.delta_indexes, node_values[i, :])),
nodes=dict(zip(self.delta_indexes, node_values[i, :], strict=False)),
expiry=expiry,
eval_date=self.eval_date,
delta_type=self.delta_type,
Expand Down Expand Up @@ -811,7 +810,9 @@ def get_smile(self, expiry: datetime):
vol2=vol1,
bounds_flag=1,
)
for k, vol1 in zip(self.delta_indexes, self.smiles[e_idx + 1].nodes.values())
for k, vol1 in zip(
self.delta_indexes, self.smiles[e_idx + 1].nodes.values(), strict=False
)
},
eval_date=self.eval_date,
expiry=expiry,
Expand All @@ -833,7 +834,9 @@ def get_smile(self, expiry: datetime):
vol2=vol1,
bounds_flag=-1,
)
for k, vol1 in zip(self.delta_indexes, self.smiles[0].nodes.values())
for k, vol1 in zip(
self.delta_indexes, self.smiles[0].nodes.values(), strict=False
)
},
eval_date=self.eval_date,
expiry=expiry,
Expand All @@ -857,6 +860,7 @@ def get_smile(self, expiry: datetime):
self.delta_indexes,
ls.nodes.values(),
rs.nodes.values(),
strict=False,
)
},
eval_date=self.eval_date,
Expand Down Expand Up @@ -1247,5 +1251,5 @@ def _delta_type_constants(delta_type, w, u):
return (-0.5, w, u)


FXVols = Union[FXDeltaVolSmile, FXDeltaVolSurface]
FXVols = FXDeltaVolSmile | FXDeltaVolSurface
FXVolObj = (FXDeltaVolSmile, FXDeltaVolSurface)
Loading

0 comments on commit f05f82b

Please sign in to comment.