Skip to content
This repository was archived by the owner on Dec 11, 2024. It is now read-only.

Commit 027e6ea

Browse files
authored
Support general arithmetic expressions (#30)
* updated configurations * fixed typo * added more info in error message * added support for general arithmetic expression * updated console calculator * switch from if-else to match-case * fix and skip mypy problems * pre-commit autoupdate * updated pyproject formatting * fixed failure tests for updated types * support negative numbers * refactor to reduce code complexity * added new test * added new test for wrong sub-parser use * formatted stub files with isort and black * updated readme * pre-commit autoupdate * updated sphinx docs * address PTC-W0063 detected by DeepSource * address enum str mixin formatting breaking change Ref. python/cpython#100458. * rename function * updated docstring * split infix to postfix conversion * skipped few lines from coverage * changed module docstring * removed unnecessary check * avoided list index out of range * improved error messages * added more tests * skip PY-R1000 detected by DeepSource * fixed typo * moved coverage skip to cover conditional block * skipped coverage for safety block * updated codecov configuration
1 parent ea42944 commit 027e6ea

25 files changed

Lines changed: 787 additions & 118 deletions

.pre-commit-config.yaml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ repos:
44
hooks:
55
- id: check-ast
66
- id: check-case-conflict
7+
- id: check-executables-have-shebangs
78
- id: check-json
89
- id: check-merge-conflict
10+
- id: check-shebang-scripts-are-executable
11+
- id: check-symlinks
912
- id: check-toml
1013
- id: check-yaml
14+
args:
15+
- --allow-multiple-documents
16+
- id: detect-private-key
1117
- id: end-of-file-fixer
1218
- id: mixed-line-ending
1319
- id: name-tests-test
@@ -92,14 +98,14 @@ repos:
9298
stages:
9399
- manual
94100
- repo: https://github.com/RobertCraigie/pyright-python
95-
rev: v1.1.333
101+
rev: v1.1.334
96102
hooks:
97103
- id: pyright
98104
pass_filenames: false
99105
stages:
100106
- manual
101107
- repo: https://github.com/astral-sh/ruff-pre-commit
102-
rev: v0.1.3
108+
rev: v0.1.4
103109
hooks:
104110
- id: ruff
105111
args:
@@ -146,7 +152,7 @@ repos:
146152
- src
147153
pass_filenames: false
148154
- repo: https://github.com/tox-dev/pyproject-fmt
149-
rev: 1.3.0
155+
rev: 1.4.1
150156
hooks:
151157
- id: pyproject-fmt
152158
- repo: https://github.com/abravalheri/validate-pyproject
@@ -157,6 +163,12 @@ repos:
157163
rev: v2.2.6
158164
hooks:
159165
- id: codespell
166+
additional_dependencies:
167+
- tomli
168+
args:
169+
- --write-changes
170+
stages:
171+
- manual
160172
default_language_version:
161173
python: python3.10
162174
fail_fast: false

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
- [x] ~~Host Documentation~~
3131
- [x] ~~Hypotheses Testing~~
3232
- [x] ~~Pydantic Validation~~
33-
- [ ] Support Expressions
33+
- [x] ~~Support Expressions~~
3434
- [ ] Bump Workflow
3535

3636
## Tools Used

codecov.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ codecov:
22
require_ci_to_pass: true
33
coverage:
44
status:
5+
patch:
6+
default:
7+
target: 80%
58
project:
69
default:
710
target: 80%

docs/source/package_name_to_import_with.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Submodules
1717

1818
package_name_to_import_with.data_using_module
1919
package_name_to_import_with.garbage_collection_module
20+
package_name_to_import_with.simplify
2021

2122
Module contents
2223
---------------
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package\_name\_to\_import\_with.simplify module
2+
===============================================
3+
4+
.. automodule:: package_name_to_import_with.simplify
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

pyproject.toml

Lines changed: 43 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ ignore-fully-untyped = true
217217
extend-immutable-calls = [
218218
"fastapi.Depends",
219219
"fastapi.Query",
220+
"pydantic.Field",
220221
]
221222

222223
[tool.ruff.flake8-type-checking]
@@ -229,7 +230,9 @@ runtime-evaluated-base-classes = [
229230
]
230231

231232
[tool.ruff.pep8-naming]
232-
classmethod-decorators = ["pydantic.field_validator"]
233+
classmethod-decorators = [
234+
"pydantic.field_validator",
235+
]
233236

234237
[tool.ruff.per-file-ignores]
235238
"**/__init__.py" = [
@@ -238,9 +241,6 @@ classmethod-decorators = ["pydantic.field_validator"]
238241
"**/test_*.py" = [
239242
"S101",
240243
]
241-
"docs/source/conf.py" = [
242-
"INP001",
243-
]
244244

245245
[tool.ruff.pycodestyle]
246246
max-doc-length = 99
@@ -261,9 +261,45 @@ atomic = true
261261
float_to_top = true
262262
line_length = 99
263263
remove_redundant_aliases = true
264-
src_paths = "src"
264+
src_paths = [
265+
"src",
266+
]
265267
py_version = 310
266268

269+
[tool.pylint.main]
270+
extension-pkg-allow-list = [
271+
"pydantic",
272+
]
273+
fail-under = 8.5
274+
jobs = 0
275+
recursive = true
276+
277+
[tool.pylint.basic]
278+
include-naming-hint = true
279+
280+
[tool.pylint.format]
281+
max-line-length = 99
282+
283+
[tool.pylint.logging]
284+
logging-format-style = "new"
285+
286+
[tool.pylint."messages control"]
287+
enable = [
288+
"all",
289+
]
290+
disable = [
291+
"logging-fstring-interpolation",
292+
]
293+
294+
[tool.pylint.reports]
295+
output-format = "colorized"
296+
297+
[tool.docformatter]
298+
in-place = true
299+
recursive = true
300+
wrap-summaries = 99
301+
wrap-descriptions = 99
302+
267303
[tool.pytest.ini_options]
268304
addopts = "--junit-xml=pytest_junit_report.xml --doctest-modules --doctest-ignore-import-errors --doctest-continue-on-failure"
269305
console_output_style = "count"
@@ -280,7 +316,7 @@ omit = [
280316
]
281317

282318
[tool.coverage.report]
283-
fail_under = 75
319+
fail_under = 85
284320
include = [
285321
"src/**/*.py",
286322
]
@@ -323,47 +359,13 @@ ignore-init-module-imports = true
323359
remove-duplicate-keys = true
324360
remove-unused-variables = true
325361

326-
[tool.docformatter]
327-
in-place = true
328-
recursive = true
329-
wrap-summaries = 99
330-
wrap-descriptions = 99
331-
332362
[tool.interrogate]
333-
fail-under = 75
363+
fail-under = 85
334364
ignore-init-method = true
335365

336366
[tool.pydocstyle]
337367
convention = "numpy"
338368

339-
[tool.pylint.main]
340-
extension-pkg-allow-list = [
341-
"pydantic",
342-
]
343-
fail-under = 7.5
344-
jobs = 0
345-
recursive = true
346-
347-
[tool.pylint.basic]
348-
include-naming-hint = true
349-
350-
[tool.pylint.format]
351-
max-line-length = 99
352-
353-
[tool.pylint.logging]
354-
logging-format-style = "new"
355-
356-
[tool.pylint."messages control"]
357-
enable = [
358-
"all",
359-
]
360-
disable = [
361-
"logging-fstring-interpolation",
362-
]
363-
364-
[tool.pylint.reports]
365-
output-format = "colorized"
366-
367369
[tool.pyright]
368370
include = [
369371
"src",

src/module_that_can_be_invoked_from_cli.py

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Calculate arithmetic expressions from command line."""
22
import argparse
3+
import enum
34
import sys
45
import typing
56

@@ -8,24 +9,71 @@
89
import package_name_to_import_with
910

1011

12+
@enum.unique
13+
class CalculatorType(str, enum.Enum):
14+
"""Define supported calculator types."""
15+
16+
BINARY = "binary"
17+
GENERAL = "general"
18+
19+
20+
class BinaryInputs(pydantic.BaseModel):
21+
"""Define arguments for binary calculator."""
22+
23+
calculator_type: typing.Literal[CalculatorType.BINARY]
24+
first_number: float
25+
operator: package_name_to_import_with.calculator_sub_package.ArithmeticOperator
26+
second_number: float
27+
28+
29+
class GeneralInputs(pydantic.BaseModel):
30+
"""Define arguments of general calculator."""
31+
32+
calculator_type: typing.Literal[CalculatorType.GENERAL]
33+
expression: str
34+
35+
36+
class UserInputs(pydantic.BaseModel):
37+
"""Define sub-commands and arguments of CLI calculator."""
38+
39+
inputs: BinaryInputs | GeneralInputs = pydantic.Field(discriminator="calculator_type")
40+
41+
1142
@pydantic.validate_call(validate_return=True)
12-
def capture_user_inputs() -> dict[str, typing.Any]:
43+
def capture_user_inputs() -> UserInputs:
1344
"""Capture user inputs for arithmetic expression.
1445
1546
Returns
1647
-------
17-
dict[str, typing.Any]
48+
UserInputs
1849
captured user inputs
1950
"""
2051
parser = argparse.ArgumentParser(description="calculator for console", add_help=True)
2152

22-
parser.add_argument("first_number", help="first number")
23-
parser.add_argument("operator", help="arithmetic operator")
24-
parser.add_argument("second_number", help="second number")
53+
sub_parsers = parser.add_subparsers(
54+
dest="calculator_type", help="types of arithmetic expressions"
55+
)
56+
57+
binary_parser = sub_parsers.add_parser(
58+
CalculatorType.BINARY.value, help="basic binary operations"
59+
)
60+
general_parser = sub_parsers.add_parser(
61+
CalculatorType.GENERAL.value, help="standard simplification problems"
62+
)
63+
64+
binary_parser.add_argument("first_number", type=float, help="first number")
65+
binary_parser.add_argument(
66+
"operator",
67+
type=package_name_to_import_with.calculator_sub_package.ArithmeticOperator,
68+
help="arithmetic operator",
69+
)
70+
binary_parser.add_argument("second_number", type=float, help="second number")
71+
72+
general_parser.add_argument("expression", type=str, help="infix expression")
2573

2674
parsed_arguments, _ = parser.parse_known_args()
2775

28-
return vars(parsed_arguments)
76+
return UserInputs.model_validate({"inputs": vars(parsed_arguments)})
2977

3078

3179
@pydantic.validate_call(validate_return=True)
@@ -34,9 +82,19 @@ def console_calculator() -> None:
3482
user_inputs = capture_user_inputs()
3583

3684
try:
37-
operation_result = package_name_to_import_with.calculate_results(
38-
user_inputs["first_number"], user_inputs["operator"], user_inputs["second_number"]
39-
)
85+
match user_inputs.inputs.calculator_type:
86+
case CalculatorType.BINARY:
87+
operation_result = package_name_to_import_with.calculate_results(
88+
user_inputs.inputs.first_number, # type: ignore[union-attr]
89+
user_inputs.inputs.operator, # type: ignore[union-attr]
90+
user_inputs.inputs.second_number, # type: ignore[union-attr]
91+
)
92+
case CalculatorType.GENERAL:
93+
operation_result = package_name_to_import_with.solve_simplification(
94+
user_inputs.inputs.expression # type: ignore[union-attr]
95+
)
96+
case _: # pragma: no cover
97+
operation_result = None
4098
except Exception as error: # noqa: BLE001 # pylint: disable=broad-except
4199
sys.stderr.write(f"Error: {error}")
42100
else:

src/package_name_to_import_with/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .calculator_sub_package import calculate_results
33
from .data_using_module import METADATA
44
from .garbage_collection_module import define_garbage_collection_decorator
5+
from .simplify import solve_simplification
56

6-
__all__ = ["calculate_results", "define_garbage_collection_decorator"]
7+
__all__ = ["calculate_results", "define_garbage_collection_decorator", "solve_simplification"]
78
__version__: str = METADATA.Version

src/package_name_to_import_with/calculator_sub_package/tests/conftest.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Define common inputs for unit tests."""
2-
32
import pytest
43

54
from package_name_to_import_with.calculator_sub_package.wrapper_module import ArithmeticOperator
@@ -81,16 +80,12 @@ def expected_result(
8180
ValueError
8281
if ``operator`` not one of ``+``, ``-``, ``*``, ``/``
8382
"""
84-
if ArithmeticOperator(operator) == ArithmeticOperator.ADDITION:
85-
return first_number + second_number
86-
87-
if ArithmeticOperator(operator) == ArithmeticOperator.SUBTRACTION:
88-
return first_number - second_number
89-
90-
if ArithmeticOperator(operator) == ArithmeticOperator.MULTIPLICATION:
91-
return first_number * second_number
92-
93-
if ArithmeticOperator(operator) == ArithmeticOperator.DIVISION:
94-
return first_number / second_number
95-
96-
raise ValueError("Unexpected value of operation")
83+
match operator:
84+
case ArithmeticOperator.ADDITION:
85+
return first_number + second_number
86+
case ArithmeticOperator.SUBTRACTION:
87+
return first_number - second_number
88+
case ArithmeticOperator.MULTIPLICATION:
89+
return first_number * second_number
90+
case ArithmeticOperator.DIVISION:
91+
return first_number / second_number

src/package_name_to_import_with/calculator_sub_package/tests/test_wrappers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Define unit tests for user level functions."""
1+
"""Define unit tests for basic binary operations."""
22
import math
33
import typing
44

0 commit comments

Comments
 (0)