Skip to content
Draft
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
11 changes: 11 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
on:
pull_request:
branches-ignore:
- revamp
push:
branches: [main]
jobs:
pytest:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -13,6 +16,7 @@ jobs:
- name: Run tests in Docker container
run: docker run --rm appu /bin/sh -c 'pip install -r dev-requirements.txt; python -m pytest'
test:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -32,6 +36,7 @@ jobs:
with:
path-to-profile: profile.cov
check-formatting:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -40,6 +45,7 @@ jobs:
run: |
gofmt -s -e -d -l . | tee /tmp/gofmt.output && [ $(cat /tmp/gofmt.output | wc -l) -eq 0 ]
check-smells:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -55,6 +61,7 @@ jobs:
run: |
go vet ./...
check-complexity:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -70,6 +77,7 @@ jobs:
run: |
gocyclo -over 15 .
check-style:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -85,6 +93,7 @@ jobs:
run: |
golint ./...
check-ineffectual-assignments:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -103,6 +112,7 @@ jobs:
run: |
ineffassign ./...
check-spelling:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -118,6 +128,7 @@ jobs:
run: |
misspell -error .
staticcheck:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
4 changes: 4 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[settings]

[settings.python]
uv_venv_auto = true
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.4
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

**A**utomatic **P**odcast **PU**blisher, aka appu, is a toolkit for podcast edition and publishing.

## Newer APPU 2 version

This is the new version of appu, it's a work in progress.

### Pre-requisites

- Make sure you have `uv` [installed in your system](https://docs.astral.sh/uv/getting-started/installation/).
Use your favorite package manager to install it.

#### Automation of `venv` management (optional)

You can use `mise` to transparently manage your venvs. Installation instructions are [here](https://mise.jdx.dev).
With `mise` installed, you just need to trust the source directory, by running `mise trust` from within the cloned repository.

### Install deps

Run `uv sync` to install the python dependencies.

### Install pre-commit hook

Run `uv run pre-commit install` to install the pre-commit hook. (The first time you commit, it will take some time.)

### Run the app

Run `uv run appu` to run the app, or if you have done the right thing before, you might just be able to run `appu` directly with no parameters.

## Rationale

While running the [Entre Dev y Ops podcast](https://www.entredevyops.es), the authors found interesting to start building a set of tools to make this easier. We hope this might help anyone else.
Expand Down
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[project]
name = "appu2"
version = "0.1.0"
description = "Automatic Podcast PUblisher, aka appu, is a toolkit for podcast edition and publishing"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"loguru>=0.7.3",
"pydantic>=2.10.6",
"pydantic-settings>=2.7.1",
"typer>=0.15.1",
]

[project.scripts]
appu = "appu2.cli:app"

[tool.uv]
package = true

[dependency-groups]
dev = ["pre-commit>=4.1.0", "ruff>=0.9.4"]

[tool.ruff]
# Excludes the old appu python code from the linting
exclude = ["appu"]

[tool.ruff.lint]
extend-select = [
# E and F are enabled by default
'B', # flake8-bugbear
'C4', # flake8-comprehensions
'C90', # mccabe
'I', # isort
'N', # pep8-naming
'Q', # flake8-quotes
'RUF100', # ruff (unused noqa)
'S', # flake8-bandit
'W', # pycodestyle
]

[tool.ruff.lint.isort]
combine-as-imports = true
lines-after-imports = 2
29 changes: 29 additions & 0 deletions src/appu2/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import typer

from appu2.logging import LogLevel, setup_logging

from .config import ROOT_PROJECT_PATH, settings

app = typer.Typer(help="appu CLI", no_args_is_help=True)


LOG_LEVEL_OPTION = typer.Option(
LogLevel.INFO,
"--log-level",
"-l",
case_sensitive=False,
help="Set the logging level",
)


@app.callback()
def callback(log_level: LogLevel | None = LOG_LEVEL_OPTION) -> None:
"""Initialize logging and other global settings."""
setup_logging(level=log_level)


@app.command(name="test", help="Test Command")
def app_test() -> None:
print(settings.debug)
print("Hello World!")
print(ROOT_PROJECT_PATH)
25 changes: 25 additions & 0 deletions src/appu2/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pathlib import Path

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

__project_name__ = "appu"

ROOT_PROJECT_PATH = Path(__file__).parent.parent.parent


class Settings(BaseSettings):
# Debug mode
debug: bool = Field(default=False)

# Meta Behavior
model_config = SettingsConfigDict(
env_file=ROOT_PROJECT_PATH / ".env",
env_file_encoding="utf-8",
case_sensitive=False,
# Keep this here, in case we eventually use docker secrets
# secrets_dir="/run/secrets",
)


settings = Settings()
89 changes: 89 additions & 0 deletions src/appu2/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import logging
import sys
from enum import Enum
from pathlib import Path

from loguru import logger


class LogLevel(str, Enum):
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
CRITICAL = "CRITICAL"


class InterceptHandler(logging.Handler):
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno

# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1

# Extract module information
module_path = (
record.name
) # This will get the full logger name (e.g., 'appu.XXXX.XXXX')

# Format the message with module information inline
logger.opt(depth=depth, exception=record.exc_info).log(
level,
f"[{module_path}] {record.getMessage()}",
)


def setup_logging(
level: str | int = LogLevel.INFO,
log_file: str | Path | None = None,
rotation: str = "10 MB",
retention: str = "1 week",
) -> None:
"""Configure logging for the application.

Args:
level: Minimum logging level threshold
log_file: Optional path to log file. If None, logs only to stderr
rotation: When to rotate the log file
retention: How long to keep log files

"""
# Remove default handler
logger.remove()

# Define format for the logs
format_string = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{name}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
)

# Add stderr handler with custom format
logger.add(sys.stderr, level=level, format=format_string, colorize=True)

# Add file handler if log_file is specified
if log_file:
logger.add(
str(log_file),
level=level,
format=format_string,
rotation=rotation,
retention=retention,
compression="zip",
)

# Intercept standard logging
logging.basicConfig(handlers=[InterceptHandler()], level=0, force=True)

# Optional: Capture warnings from warnings module
logging.captureWarnings(True)

logger.info("Logging configured successfully")
Loading