Merge pull request #1 from kristoferssolo/tox

CI/CD
This commit is contained in:
Kristofers Solo 2024-09-12 14:31:35 +03:00 committed by GitHub
commit 725dd8e624
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 16 deletions

26
.github/workflows/publish.yaml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Publish Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

26
.github/workflows/tox.yaml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Run Tox
on:
pull_request:
branches: [main]
jobs:
test:
name: Test with ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run Tox
env:
TOXENV: py${{ matrix.python-version }}
run: tox

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ build/
dist/ dist/
wheels/ wheels/
*.egg-info *.egg-info
.tox/
# venv # venv
.venv .venv

View File

@ -1,10 +1,10 @@
[project] [project]
name = "bunyan-formatter" name = "bunyan-formatter"
version = "0.1.0" version = "1.0.0"
description = "Bunyan Formatter for Python" description = "Bunyan Formatter for Python"
dependencies = [] dependencies = []
readme = "README.md" readme = "README.md"
requires-python = ">= 3.8" requires-python = ">=3.9"
authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }] authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }]
[build-system] [build-system]
@ -13,7 +13,13 @@ build-backend = "hatchling.build"
[tool.rye] [tool.rye]
managed = true managed = true
dev-dependencies = ["mypy~=1.11", "ruff~=0.6", "pre-commit~=3.8", "pytest~=8.3"] dev-dependencies = [
"mypy~=1.11",
"ruff~=0.6",
"pre-commit~=3.8",
"pytest~=8.3",
"tox~=4.18",
]
[tool.hatch.metadata] [tool.hatch.metadata]
allow-direct-references = true allow-direct-references = true
@ -25,7 +31,7 @@ packages = ["src/bunyan_formatter"]
show-fixes = true show-fixes = true
line-length = 120 line-length = 120
indent-width = 4 indent-width = 4
target-version = "py38" target-version = "py312"
[tool.ruff.lint] [tool.ruff.lint]
extend-select = [ extend-select = [

View File

@ -10,11 +10,18 @@
# universal: false # universal: false
-e file:. -e file:.
cachetools==5.5.0
# via tox
cfgv==3.4.0 cfgv==3.4.0
# via pre-commit # via pre-commit
chardet==5.2.0
# via tox
colorama==0.4.6
# via tox
distlib==0.3.8 distlib==0.3.8
# via virtualenv # via virtualenv
filelock==3.16.0 filelock==3.16.0
# via tox
# via virtualenv # via virtualenv
identify==2.6.0 identify==2.6.0
# via pre-commit # via pre-commit
@ -26,17 +33,25 @@ mypy-extensions==1.0.0
nodeenv==1.9.1 nodeenv==1.9.1
# via pre-commit # via pre-commit
packaging==24.1 packaging==24.1
# via pyproject-api
# via pytest # via pytest
# via tox
platformdirs==4.3.2 platformdirs==4.3.2
# via tox
# via virtualenv # via virtualenv
pluggy==1.5.0 pluggy==1.5.0
# via pytest # via pytest
# via tox
pre-commit==3.8.0 pre-commit==3.8.0
pyproject-api==1.7.1
# via tox
pytest==8.3.3 pytest==8.3.3
pyyaml==6.0.2 pyyaml==6.0.2
# via pre-commit # via pre-commit
ruff==0.6.4 ruff==0.6.4
tox==4.18.1
typing-extensions==4.12.2 typing-extensions==4.12.2
# via mypy # via mypy
virtualenv==20.26.4 virtualenv==20.26.4
# via pre-commit # via pre-commit
# via tox

View File

@ -1,25 +1,28 @@
import json import json
import logging import logging
from logging import LogRecord
from pathlib import Path from pathlib import Path
from typing import Optional
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch from unittest.mock import Mock, patch
from bunyan_formatter import BunyanFormatter from bunyan_formatter import BunyanFormatter
class TestBunyanFormatter(TestCase): class TestBunyanFormatter(TestCase):
def setUp(self): def setUp(self) -> None:
self.project_name = "test_project" self.project_name = "test_project"
self.project_root = Path("/path/to/project") self.project_root = Path("/path/to/project")
self.formatter = BunyanFormatter(self.project_name, self.project_root) self.formatter = BunyanFormatter(self.project_name, self.project_root)
def create_log_record(self, level, msg, pathname): def create_log_record(self, level: int, msg: str, pathname: str) -> LogRecord:
return logging.LogRecord( return LogRecord(name="test_logger", level=level, pathname=pathname, lineno=42, msg=msg, args=(), exc_info=None)
name="test_logger", level=level, pathname=pathname, lineno=42, msg=msg, args=(), exc_info=None
)
@patch("socket.gethostname") @patch("socket.gethostname")
def test_format_basic(self, mock_gethostname): def test_format_basic(self, mock_gethostname: Optional[Mock]) -> None:
if mock_gethostname is None:
raise ValueError("mock_gethostname should not be None")
mock_gethostname.return_value = "test_host" mock_gethostname.return_value = "test_host"
record = self.create_log_record(logging.INFO, "Test message", "/path/to/project/test.py") record = self.create_log_record(logging.INFO, "Test message", "/path/to/project/test.py")
@ -36,25 +39,27 @@ class TestBunyanFormatter(TestCase):
assert log_entry["line"] == 42 assert log_entry["line"] == 42
assert log_entry["file"] == "test.py" assert log_entry["file"] == "test.py"
def test_format_different_levels(self): def test_format_different_levels(self) -> None:
levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
expected_levels = [20, 30, 40, 50, 60] expected_levels = [20, 30, 40, 50, 60]
for level, expected in zip(levels, expected_levels): for level, expected in zip(levels, expected_levels, strict=False):
record = self.create_log_record(level, f"Test {logging.getLevelName(level)}", "/path/to/project/test.py") record = self.create_log_record(level, f"Test {logging.getLevelName(level)}", "/path/to/project/test.py")
formatted = self.formatter.format(record) formatted = self.formatter.format(record)
log_entry = json.loads(formatted) log_entry = json.loads(formatted)
assert log_entry["level"] == expected assert log_entry["level"] == expected
assert log_entry["levelname"] == logging.getLevelName(level) assert log_entry["levelname"] == logging.getLevelName(level)
def test_format_file_outside_project(self): def test_format_file_outside_project(self) -> None:
record = self.create_log_record(logging.INFO, "Test message", "/path/outside/project/test.py") record = self.create_log_record(logging.INFO, "Test message", "/path/outside/project/test.py")
formatted = self.formatter.format(record) formatted = self.formatter.format(record)
log_entry = json.loads(formatted) log_entry = json.loads(formatted)
assert log_entry["file"] == "/path/outside/project/test.py" assert log_entry["file"] == "/path/outside/project/test.py"
@patch("socket.gethostname") @patch("socket.gethostname")
def test_format_hostname_consistency(self, mock_gethostname): def test_format_hostname_consistency(self, mock_gethostname: Optional[Mock]) -> None:
if mock_gethostname is None:
raise ValueError("mock_gethostname should not be None")
mock_gethostname.return_value = "test_host" mock_gethostname.return_value = "test_host"
record1 = self.create_log_record(logging.INFO, "Message 1", "/path/to/project/test1.py") record1 = self.create_log_record(logging.INFO, "Message 1", "/path/to/project/test1.py")
record2 = self.create_log_record(logging.INFO, "Message 2", "/path/to/project/test2.py") record2 = self.create_log_record(logging.INFO, "Message 2", "/path/to/project/test2.py")
@ -67,7 +72,7 @@ class TestBunyanFormatter(TestCase):
assert log_entry1["hostname"] == log_entry2["hostname"] assert log_entry1["hostname"] == log_entry2["hostname"]
def test_format_time(self): def test_format_time(self) -> None:
record = self.create_log_record(logging.INFO, "Test message", "/path/to/project/test.py") record = self.create_log_record(logging.INFO, "Test message", "/path/to/project/test.py")
formatted = self.formatter.format(record) formatted = self.formatter.format(record)
log_entry = json.loads(formatted) log_entry = json.loads(formatted)

28
tox.ini Normal file
View File

@ -0,0 +1,28 @@
[tox]
envlist = py39, py310, py311, py312, format, lint, type, test
skipsdist = True
[testenv]
deps =
hatchling
-rrequirements-dev.lock
[testenv:format]
description = Run ruff to format the code
skip_install = true
deps = ruff
commands = ruff format
[testenv:lint]
description = Run ruff to lint the code
skip_install = true
deps = ruff
commands = ruff check --fix
[testenv:type]
description = Run mypy to check type annotations
commands = mypy .
[testenv:test]
description = Run pytest to execute the tests
commands = pytest