Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 30 additions & 156 deletions .github/workflows/linttest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,179 +8,53 @@ on:

jobs:
# This will run bandit and produce a security report if there are any failures
bandit:
linting:
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
pyver: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- name: Checkout
uses: actions/checkout@v3.1.0

- name: Security check - Bandit
uses: joshvote/bandit-report-artifacts@v0.0.6
with:
project_path: .
ignore_failure: false
config_file: pyproject.toml

- name: Security check report artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
name: Security report
path: output/security_report.txt
overwrite: true

# This will run the latest flake8 with python 3.11 and report on any errors
flake8_py311:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0

uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4.2.0
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Setup flake8 annotations
uses: rbialon/flake8-annotations@v1
python-version: ${{ matrix.pyver }}
- name: Install linters
run: pip install -e .[all]

- name: Lint with flake8
if: always()
run: |
pip install flake8
flake8 . --count --statistics
- name: Lint (ruff)
run: ruff check .
- name: Format check
run: ruff format --check .
- name: Typing (ty)
run: ty check .
- name: Security scan (bandit)
run: bandit -c pyproject.toml -r src/

# This will run the latest black to see if there are any code formatting errors
black_formatting:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable

# This will run the latest mypy with python3.11 to see if there are any type errors
mypy_py311:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0

- name: Setup Python
uses: actions/setup-python@v4.2.0
with:
python-version: "3.11"

- name: Install Dependencies
run: |
pip install .[all]

- name: Add mypy annotator
uses: pr-annotators/mypy-pr-annotator@v1.0.0

- name: Run mypy
run: |
mypy src/
continue-on-error: true
strategy:
fail-fast: false
matrix:
pyver: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

# This will run the pytest with python3.9
pytest_py39:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v4.2.0
uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: ${{ matrix.pyver }}

- name: Install Dependencies
run: |
pip install .[all]

- name: Run Pytest
run: |
pytest --junit-xml=.test_report.xml

- name: Upload Results
uses: test-summary/action@v1
with:
paths: .test_report.xml
if: always()

# This will run the pytest with python3.10
pytest_py310:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0

- name: Setup Python
uses: actions/setup-python@v4.2.0
with:
python-version: "3.10"

- name: Install Dependencies
run: |
pip install .[all]

- name: Run Pytest
run: |
pytest --junit-xml=.test_report.xml

- name: Upload Results
uses: test-summary/action@v1
with:
paths: .test_report.xml
if: always()

# This will run the pytest with python3.11
pytest_py311:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0

- name: Setup Python
uses: actions/setup-python@v4.2.0
with:
python-version: "3.11"

- name: Install Dependencies
run: |
pip install .[all]

- name: Run Pytest
run: |
pytest --junit-xml=.test_report.xml

- name: Upload Results
uses: test-summary/action@v1
with:
paths: .test_report.xml
if: always()


# This will run the pytest with python3.12
pytest_py312:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0

- name: Setup Python
uses: actions/setup-python@v4.2.0
with:
python-version: "3.12"

- name: Install Dependencies
run: |
pip install .[all]

- name: Run Pytest
run: |
pytest --junit-xml=.test_report.xml

- name: Upload Results
uses: test-summary/action@v1
with:
paths: .test_report.xml
if: always()
run: pytest
37 changes: 20 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
[tool.black]
line-length = 120

[tool.pytest.ini_options]
pythonpath = ["src/"]
testpaths = "tests"

[tool.isort]
profile = "black"

[tool.bandit]
exclude_dirs = ["tests"]
skips = ["B101"]

[tool.mypy]
exclude = ["tests"]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
namespace_packages = true
warn_redundant_casts = true
warn_unused_ignores = true
[tool.ruff]
line-length = 120

[tool.ruff.lint]
select = ["E", "W", "F", "I", "S", "C90", "B", "UP", "ANN", "N"]
ignore = ["E721", "B017", "UP035", "E731", "UP045"]

[tool.ruff.lint.mccabe]
max-complexity = 15

[tool.ruff.lint.per-file-ignores]
"src/**" = ["ANN401"]
"**/asserts/**" = ["S101", "B011"]
"tests/**" = ["S", "ANN", "N"]

[tool.ty.src]
include = ["src", "tests"]

[tool.ty.rules]
invalid-type-form = "ignore"

[build-system]
requires = ["setuptools >= 40.9.0", "wheel"]
Expand Down Expand Up @@ -60,7 +63,7 @@ Homepage = "https://github.com/bsgip/assertical"

[project.optional-dependencies]
all = ["assertical[dev,fastapi,pandas,pydantic,postgres,xml]"]
dev = ["bandit", "flake8", "mypy", "black", "coverage"]
dev = ["bandit", "ruff", "ty", "coverage"]
fastapi = ["fastapi[standard]", "asgi_lifespan", "uvicorn"]
pandas = ["pandas", "pandas_stubs", "numpy"]
pydantic = ["pydantic"]
Expand Down
16 changes: 8 additions & 8 deletions src/assertical/asserts/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ def print_val(v: Any) -> str:
query = " & ".join([f"`{k}`=={print_val(v)}" for k, v in col_values.items()])
try:
count = len(df.query(query))
except Exception:
raise AssertionError(f"Column(s) don't exist. col_values: {col_values}")
except Exception as exc:
raise AssertionError(f"Column(s) don't exist. col_values: {col_values}") from exc

if expected_min_count is not None:
assert (
count >= expected_min_count
), f"Expected at least {expected_min_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
assert count >= expected_min_count, (
f"Expected at least {expected_min_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
)

if expected_max_count is not None:
assert (
count <= expected_max_count
), f"Expected at most {expected_max_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
assert count <= expected_max_count, (
f"Expected at most {expected_max_count} match(es) for {query}\n{df[list(col_values.keys())].to_string()}"
)
6 changes: 3 additions & 3 deletions src/assertical/asserts/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ def assert_fuzzy_datetime_match(
actual_time = datetime.fromtimestamp(float(actual_time))

delta_seconds = expected_time.timestamp() - actual_time.timestamp()
assert (
abs(delta_seconds) <= fuzziness_seconds
), f"Expected {expected_time} to be within {fuzziness_seconds} of {actual_time} but it was {delta_seconds}"
assert abs(delta_seconds) <= fuzziness_seconds, (
f"Expected {expected_time} to be within {fuzziness_seconds} of {actual_time} but it was {delta_seconds}"
)


def assert_nowish(expected_time: Union[int, float, datetime], fuzziness_seconds: int = 20) -> None:
Expand Down
24 changes: 12 additions & 12 deletions src/assertical/asserts/type.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ def assert_list_type(expected_element_type: type, obj: Any, count: Optional[int]

if count is specified - an additional assert will be made on the count of elements in obj"""
assert obj is not None
assert (
isinstance(obj, list) or get_origin(type(obj)) == list
), f"Expected a list type for obj but got {type(obj)} instead"
assert isinstance(obj, list) or get_origin(type(obj)) == list, (
f"Expected a list type for obj but got {type(obj)} instead"
)
assert_iterable_type(expected_element_type, obj, count=count)


Expand All @@ -17,9 +17,9 @@ def assert_set_type(expected_element_type: type, obj: Any, count: Optional[int]

if count is specified - an additional assert will be made on the count of elements in obj"""
assert obj is not None
assert (
isinstance(obj, set) or get_origin(type(obj)) == set
), f"Expected a set type for obj but got {type(obj)} instead"
assert isinstance(obj, set) or get_origin(type(obj)) == set, (
f"Expected a set type for obj but got {type(obj)} instead"
)
assert_iterable_type(expected_element_type, obj, count=count)


Expand All @@ -28,9 +28,9 @@ def assert_dict_type(expected_key_type: type, expected_value_type: type, obj: An

if count is specified - an additional assert will be made on the count of elements in obj"""
assert obj is not None
assert (
isinstance(obj, dict) or get_origin(type(obj)) == dict
), f"Expected a dict type for obj but got {type(obj)} instead"
assert isinstance(obj, dict) or get_origin(type(obj)) == dict, (
f"Expected a dict type for obj but got {type(obj)} instead"
)
assert_iterable_type(expected_key_type, obj.keys(), count=count)
assert_iterable_type(expected_value_type, obj.values(), count=count)

Expand All @@ -49,9 +49,9 @@ def assert_iterable_type(expected_element_type: type, obj: Any, count: Optional[
enumerated_item_count = 0
for i, val in enumerate(obj):
enumerated_item_count += 1
assert isinstance(
val, expected_element_type
), f"obj[{i}]: Element has type {type(val)} instead of {expected_element_type}"
assert isinstance(val, expected_element_type), (
f"obj[{i}]: Element has type {type(val)} instead of {expected_element_type}"
)

if count is not None:
assert enumerated_item_count == count
Loading
Loading