Skip to content

Commit 06f5d48

Browse files
authored
Merge pull request #79 from numpy/ci/move-to-ruff
CI/STY: Move to Ruff
2 parents f3ab7e4 + dccd7b6 commit 06f5d48

File tree

5 files changed

+82
-77
lines changed

5 files changed

+82
-77
lines changed

.github/workflows/lint.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ jobs:
4040
run: |
4141
python -m pip install --upgrade pip poetry
4242
poetry env use ${{ matrix.python-version }}
43-
poetry install --with=test --with=lint
43+
poetry install --with=lint
4444
45-
- name: Lint with flake8
45+
- name: Lint with Ruff
4646
run: |
4747
set -euo pipefail
48-
# stop the build if there are Python syntax errors or undefined names
49-
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
50-
# The GitHub editor is 127 chars wide
51-
poetry run flake8 . --ignore=F401,F403,W503,E226 --count --max-complexity=10 --max-line-length=127 --statistics
48+
# Tell us what version we are using
49+
poetry run ruff version
50+
# Check the source file, ignore type annotations (ANN) for now.
51+
poetry run ruff check numpy_financial/ --ignore F403,Q000,PLR0913,ERA001,TRY003,EM101,EM102,RET505,D203,D213,ANN --select ALL

numpy_financial/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
"""__init__ file.
2+
3+
This file allows us to import the public functions from NumPy-Financial and
4+
tells us the version we are using.
5+
"""
16

27
__version__ = "1.1.0.dev0"
38

numpy_financial/_financial.py

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Some simple financial calculations
1+
"""Some simple financial calculations.
22
33
patterned after spreadsheet computations.
44
@@ -10,15 +10,14 @@
1010
Functions support the :class:`decimal.Decimal` type unless
1111
otherwise stated.
1212
"""
13-
from __future__ import absolute_import, division, print_function
1413

1514
from decimal import Decimal
1615

1716
import numpy as np
1817

1918
__all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate',
2019
'irr', 'npv', 'mirr',
21-
'NoRealSolutionException', 'IterationsExceededException']
20+
'NoRealSolutionError', 'IterationsExceededError']
2221

2322
_when_to_num = {'end': 0, 'begin': 1,
2423
'e': 0, 'b': 1,
@@ -28,14 +27,12 @@
2827
'finish': 0}
2928

3029

31-
class NoRealSolutionException(Exception):
32-
""" No real solution to the problem. """
33-
pass
30+
class NoRealSolutionError(Exception):
31+
"""No real solution to the problem."""
3432

3533

36-
class IterationsExceededException(Exception):
37-
""" Maximum number of iterations reached. """
38-
pass
34+
class IterationsExceededError(Exception):
35+
"""Maximum number of iterations reached."""
3936

4037

4138
def _convert_when(when):
@@ -50,8 +47,7 @@ def _convert_when(when):
5047

5148

5249
def fv(rate, nper, pmt, pv, when='end'):
53-
"""
54-
Compute the future value.
50+
"""Compute the future value.
5551
5652
Given:
5753
* a present value, `pv`
@@ -143,11 +139,11 @@ def fv(rate, nper, pmt, pv, when='end'):
143139
fv_array[zero] = -(pv[zero] + pmt[zero] * nper[zero])
144140

145141
rate_nonzero = rate[nonzero]
146-
temp = (1 + rate_nonzero)**nper[nonzero]
142+
temp = (1 + rate_nonzero) ** nper[nonzero]
147143
fv_array[nonzero] = (
148-
- pv[nonzero] * temp
149-
- pmt[nonzero] * (1 + rate_nonzero * when[nonzero]) / rate_nonzero
150-
* (temp - 1)
144+
- pv[nonzero] * temp
145+
- pmt[nonzero] * (1 + rate_nonzero * when[nonzero]) / rate_nonzero
146+
* (temp - 1)
151147
)
152148

153149
if np.ndim(fv_array) == 0:
@@ -158,8 +154,7 @@ def fv(rate, nper, pmt, pv, when='end'):
158154

159155

160156
def pmt(rate, nper, pv, fv=0, when='end'):
161-
"""
162-
Compute the payment against loan principal plus interest.
157+
"""Compute the payment against loan principal plus interest.
163158
164159
Given:
165160
* a present value, `pv` (e.g., an amount borrowed)
@@ -244,17 +239,16 @@ def pmt(rate, nper, pv, fv=0, when='end'):
244239
"""
245240
when = _convert_when(when)
246241
(rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when])
247-
temp = (1 + rate)**nper
242+
temp = (1 + rate) ** nper
248243
mask = (rate == 0)
249244
masked_rate = np.where(mask, 1, rate)
250245
fact = np.where(mask != 0, nper,
251-
(1 + masked_rate*when)*(temp - 1)/masked_rate)
252-
return -(fv + pv*temp) / fact
246+
(1 + masked_rate * when) * (temp - 1) / masked_rate)
247+
return -(fv + pv * temp) / fact
253248

254249

255250
def nper(rate, pmt, pv, fv=0, when='end'):
256-
"""
257-
Compute the number of periodic payments.
251+
"""Compute the number of periodic payments.
258252
259253
:class:`decimal.Decimal` type is not supported.
260254
@@ -321,8 +315,8 @@ def nper(rate, pmt, pv, fv=0, when='end'):
321315
nonzero_rate = rate[nonzero]
322316
z = pmt[nonzero] * (1 + nonzero_rate * when[nonzero]) / nonzero_rate
323317
nper_array[nonzero] = (
324-
np.log((-fv[nonzero] + z) / (pv[nonzero] + z))
325-
/ np.log(1 + nonzero_rate)
318+
np.log((-fv[nonzero] + z) / (pv[nonzero] + z))
319+
/ np.log(1 + nonzero_rate)
326320
)
327321

328322
return nper_array
@@ -332,13 +326,11 @@ def _value_like(arr, value):
332326
entry = arr.item(0)
333327
if isinstance(entry, Decimal):
334328
return Decimal(value)
335-
else:
336-
return np.array(value, dtype=arr.dtype).item(0)
329+
return np.array(value, dtype=arr.dtype).item(0)
337330

338331

339332
def ipmt(rate, per, nper, pv, fv=0, when='end'):
340-
"""
341-
Compute the interest portion of a payment.
333+
"""Compute the interest portion of a payment.
342334
343335
Parameters
344336
----------
@@ -439,7 +431,7 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
439431
# If paying at the beginning we need to discount by one period.
440432
per_gt_1_and_begin = (when == 1) & (per > 1)
441433
ipmt_array[per_gt_1_and_begin] = (
442-
ipmt_array[per_gt_1_and_begin] / (1 + rate[per_gt_1_and_begin])
434+
ipmt_array[per_gt_1_and_begin] / (1 + rate[per_gt_1_and_begin])
443435
)
444436

445437
if np.ndim(ipmt_array) == 0:
@@ -450,7 +442,8 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
450442

451443

452444
def _rbl(rate, per, pmt, pv, when):
453-
"""
445+
"""Remaining balance on loan.
446+
454447
This function is here to simply have a different name for the 'fv'
455448
function to not interfere with the 'fv' keyword argument within the 'ipmt'
456449
function. It is the 'remaining balance on loan' which might be useful as
@@ -460,8 +453,7 @@ def _rbl(rate, per, pmt, pv, when):
460453

461454

462455
def ppmt(rate, per, nper, pv, fv=0, when='end'):
463-
"""
464-
Compute the payment against loan principal.
456+
"""Compute the payment against loan principle.
465457
466458
Parameters
467459
----------
@@ -489,8 +481,7 @@ def ppmt(rate, per, nper, pv, fv=0, when='end'):
489481

490482

491483
def pv(rate, nper, pmt, fv=0, when='end'):
492-
"""
493-
Compute the present value.
484+
"""Compute the present value.
494485
495486
Given:
496487
* a future value, `fv`
@@ -579,9 +570,10 @@ def pv(rate, nper, pmt, fv=0, when='end'):
579570
"""
580571
when = _convert_when(when)
581572
(rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when])
582-
temp = (1+rate)**nper
583-
fact = np.where(rate == 0, nper, (1+rate*when)*(temp-1)/rate)
584-
return -(fv + pmt*fact)/temp
573+
temp = (1 + rate) ** nper
574+
fact = np.where(rate == 0, nper, (1 + rate * when) * (temp - 1) / rate)
575+
return -(fv + pmt * fact) / temp
576+
585577

586578
# Computed with Sage
587579
# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x -
@@ -592,13 +584,13 @@ def pv(rate, nper, pmt, fv=0, when='end'):
592584
def _g_div_gp(r, n, p, x, y, w):
593585
# Evaluate g(r_n)/g'(r_n), where g =
594586
# fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1)
595-
t1 = (r+1)**n
596-
t2 = (r+1)**(n-1)
597-
g = y + t1*x + p*(t1 - 1) * (r*w + 1) / r
598-
gp = (n*t2*x
599-
- p*(t1 - 1) * (r*w + 1) / (r**2)
600-
+ n*p*t2 * (r*w + 1) / r
601-
+ p*(t1 - 1) * w/r)
587+
t1 = (r + 1) ** n
588+
t2 = (r + 1) ** (n - 1)
589+
g = y + t1 * x + p * (t1 - 1) * (r * w + 1) / r
590+
gp = (n * t2 * x
591+
- p * (t1 - 1) * (r * w + 1) / (r ** 2)
592+
+ n * p * t2 * (r * w + 1) / r
593+
+ p * (t1 - 1) * w / r)
602594
return g / gp
603595

604596

@@ -609,9 +601,18 @@ def _g_div_gp(r, n, p, x, y, w):
609601
# where
610602
# g(r) is the formula
611603
# g'(r) is the derivative with respect to r.
612-
def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100, *, raise_exceptions=False):
613-
"""
614-
Compute the rate of interest per period.
604+
def rate(
605+
nper,
606+
pmt,
607+
pv,
608+
fv,
609+
when='end',
610+
guess=None,
611+
tol=None,
612+
maxiter=100,
613+
*,
614+
raise_exceptions=False):
615+
"""Compute the rate of interest per period.
615616
616617
Parameters
617618
----------
@@ -675,29 +676,28 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100, *, ra
675676
close = False
676677
while (iterator < maxiter) and not np.all(close):
677678
rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when)
678-
diff = abs(rnp1-rn)
679+
diff = abs(rnp1 - rn)
679680
close = diff < tol
680681
iterator += 1
681682
rn = rnp1
682683

683684
if not np.all(close):
684685
if np.isscalar(rn):
685686
if raise_exceptions:
686-
raise IterationsExceededException('Maximum number of iterations exceeded.')
687+
raise IterationsExceededError('Maximum number of iterations exceeded.')
687688
return default_type(np.nan)
688689
else:
689690
# Return nan's in array of the same shape as rn
690691
# where the solution is not close to tol.
691692
if raise_exceptions:
692-
raise IterationsExceededException(f'Maximum number of iterations exceeded in '
693-
f'{len(close)-close.sum()} rate(s).')
693+
raise IterationsExceededError(f'Maximum iterations exceeded in '
694+
f'{len(close) - close.sum()} rate(s).')
694695
rn[~close] = np.nan
695696
return rn
696697

697698

698699
def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
699-
"""
700-
Return the Internal Rate of Return (IRR).
700+
r"""Return the Internal Rate of Return (IRR).
701701
702702
This is the "average" periodically compounded rate of return
703703
that gives a net present value of 0.0; for a more complete explanation,
@@ -781,8 +781,8 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
781781
same_sign = np.all(values > 0) if values[0] > 0 else np.all(values < 0)
782782
if same_sign:
783783
if raise_exceptions:
784-
raise NoRealSolutionException('No real solution exists for IRR since all '
785-
'cashflows are of the same sign.')
784+
raise NoRealSolutionError('No real solution exists for IRR since all '
785+
'cashflows are of the same sign.')
786786
return np.nan
787787

788788
# If no value is passed for `guess`, then make a heuristic estimate
@@ -820,14 +820,13 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
820820
g -= delta
821821

822822
if raise_exceptions:
823-
raise IterationsExceededException('Maximum number of iterations exceeded.')
823+
raise IterationsExceededError('Maximum number of iterations exceeded.')
824824

825825
return np.nan
826826

827827

828828
def npv(rate, values):
829-
"""
830-
Returns the NPV (Net Present Value) of a cash flow series.
829+
r"""Return the NPV (Net Present Value) of a cash flow series.
831830
832831
Parameters
833832
----------
@@ -905,8 +904,7 @@ def npv(rate, values):
905904

906905

907906
def mirr(values, finance_rate, reinvest_rate, *, raise_exceptions=False):
908-
"""
909-
Modified internal rate of return.
907+
r"""Return the modified internal rate of return.
910908
911909
Parameters
912910
----------
@@ -943,9 +941,9 @@ def mirr(values, finance_rate, reinvest_rate, *, raise_exceptions=False):
943941
neg = values < 0
944942
if not (pos.any() and neg.any()):
945943
if raise_exceptions:
946-
raise NoRealSolutionException('No real solution exists for MIRR since'
947-
' all cashflows are of the same sign.')
944+
raise NoRealSolutionError('No real solution exists for MIRR since'
945+
' all cashflows are of the same sign.')
948946
return np.nan
949-
numer = np.abs(npv(reinvest_rate, values*pos))
950-
denom = np.abs(npv(finance_rate, values*neg))
951-
return (numer/denom)**(1/(n - 1))*(1 + reinvest_rate) - 1
947+
numer = np.abs(npv(reinvest_rate, values * pos))
948+
denom = np.abs(npv(finance_rate, values * neg))
949+
return (numer / denom) ** (1 / (n - 1)) * (1 + reinvest_rate) - 1

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,7 @@ numpydoc = "^1.5"
5252
pydata-sphinx-theme = "^0.14.3"
5353

5454

55+
5556
[tool.poetry.group.lint.dependencies]
56-
flake8 = "*"
57+
ruff = "^0.1.6"
58+

tests/test_financial.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_rate_maximum_iterations_exception_scalar(self):
140140
# Test that if the maximum number of iterations is reached,
141141
# then npf.rate returns IterationsExceededException
142142
# when raise_exceptions is set to True.
143-
assert_raises(npf.IterationsExceededException, npf.rate, Decimal(12.0),
143+
assert_raises(npf.IterationsExceededError, npf.rate, Decimal(12.0),
144144
Decimal(400.0), Decimal(10000.0), Decimal(5000.0),
145145
raise_exceptions=True)
146146

@@ -152,7 +152,7 @@ def test_rate_maximum_iterations_exception_array(self):
152152
pmt = 0
153153
pv = [-593.06, -4725.38, -662.05, -428.78, -13.65]
154154
fv = [214.07, 4509.97, 224.11, 686.29, -329.67]
155-
assert_raises(npf.IterationsExceededException, npf.rate, nper,
155+
assert_raises(npf.IterationsExceededError, npf.rate, nper,
156156
pmt, pv, fv,
157157
raise_exceptions=True)
158158

@@ -285,7 +285,7 @@ def test_mirr_no_real_solution_exception(self):
285285
# have the same sign, then npf.mirr returns NoRealSolutionException
286286
# when raise_exceptions is set to True.
287287
val = [39000, 30000, 21000, 37000, 46000]
288-
assert_raises(npf.NoRealSolutionException, npf.mirr, val, 0.10, 0.12, raise_exceptions=True)
288+
assert_raises(npf.NoRealSolutionError, npf.mirr, val, 0.10, 0.12, raise_exceptions=True)
289289

290290

291291
class TestNper:
@@ -717,12 +717,12 @@ def test_irr_no_real_solution_exception(self):
717717
# have the same sign, then npf.irr returns NoRealSolutionException
718718
# when raise_exceptions is set to True.
719719
cashflows = numpy.array([40000, 5000, 8000, 12000, 30000])
720-
assert_raises(npf.NoRealSolutionException, npf.irr, cashflows, raise_exceptions=True)
720+
assert_raises(npf.NoRealSolutionError, npf.irr, cashflows, raise_exceptions=True)
721721

722722
def test_irr_maximum_iterations_exception(self):
723723
# Test that if the maximum number of iterations is reached,
724724
# then npf.irr returns IterationsExceededException
725725
# when raise_exceptions is set to True.
726726
cashflows = numpy.array([-40000, 5000, 8000, 12000, 30000])
727-
assert_raises(npf.IterationsExceededException, npf.irr, cashflows,
727+
assert_raises(npf.IterationsExceededError, npf.irr, cashflows,
728728
maxiter=1, raise_exceptions=True)

0 commit comments

Comments
 (0)