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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- name: Checkout code
Expand Down
39 changes: 37 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
### ⚠️ BREAKING CHANGES

- **Minimum Python version increased to 3.10**
- Python 3.8 and 3.9 are no longer supported (Python 3.8 reached EOL in October 2024)
- Added Python 3.13 support
- CI now tests on Python 3.10, 3.11, 3.12, and 3.13

### Changed

- Updated minimum Python requirement from 3.8 to 3.10
- Updated all tool configurations (black, ruff, mypy) to target Python 3.10
- Updated documentation to reflect Python 3.10+ requirement

### Migration Guide

Users on Python 3.8 or 3.9 should:

1. **Upgrade Python** to 3.10 or higher:
```bash
# Using conda/mamba
conda install python=3.10 # or 3.11, 3.12, 3.13

# Or download from python.org
# https://www.python.org/downloads/
```

2. **Reinstall package**:
```bash
pip install --upgrade eddypro-batch-processor
```

3. **Test your workflows**:
```bash
eddypro-batch --version
eddypro-batch validate --config config/config.yaml
```

- Nothing yet
**Note**: Version 0.2.x will continue to support Python 3.8+ for critical security fixes only.

## [0.2.0] - 2025-10-02

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ A Python CLI tool for automated EddyPro processing with scenario support, perfor

### Requirements

- Python 3.8 or higher (Python 3.12+ recommended for development)
- Python 3.10 or higher (Python 3.12+ recommended for development)
- [EddyPro](https://www.licor.com/env/products/eddy_covariance/eddypro.html) installed and accessible
- Python packages: `pyyaml`, `psutil`, `plotly` (optional for charts)

Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ The architecture provides several extension points for future enhancements:
### Core Dependencies

- **PyYAML**: Configuration file parsing
- **Python 3.8+**: Language runtime
- **Python 3.10+**: Language runtime

### Optional Dependencies

Expand Down
2 changes: 1 addition & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This document provides guidelines for contributing to the EddyPro Batch Processo

### Prerequisites

- Python 3.8 or higher (Python 3.12+ recommended for development)
- Python 3.10 or higher (Python 3.12+ recommended for development)
- Git
- Virtual environment (recommended)

Expand Down
13 changes: 6 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@ build-backend = "hatchling.build"

[project]
name = "eddypro-batch-processor"
version = "0.2.0"
version = "0.3.0"
description = "Automated EddyPro processing with scenario support and performance monitoring"
authors = [{name = "Rasmus Jensen", email = "raje@ecos.au.dk"}]
readme = "README.md"
license = {text = "GNU GPLv3"}
requires-python = ">=3.8"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Atmospheric Science",
]
dependencies = [
Expand Down Expand Up @@ -62,12 +61,12 @@ packages = ["src/eddypro_batch_processor"]

[tool.black]
line-length = 88
target-version = ["py38"]
target-version = ["py310"]
skip-string-normalization = false

[tool.ruff]
line-length = 88
target-version = "py38"
target-version = "py310"

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "TRY", "PL"]
Expand All @@ -88,7 +87,7 @@ ignore = [
known-first-party = ["eddypro_batch_processor"]

[tool.mypy]
python_version = "3.8"
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false # Gradual typing as per instructions
Expand Down
4 changes: 1 addition & 3 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
Currently under development - refactoring from monolithic script to modular package.
"""

from typing import List

# Re-export key functions will be implemented during Milestone 2-3
# For now, keeping this minimal to avoid import errors during transition

__all__: List[str] = [
__all__: list[str] = [
# Will be populated as modules are refactored
]
24 changes: 12 additions & 12 deletions src/eddypro_batch_processor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any

import yaml

Expand All @@ -25,12 +25,12 @@
class EddyProBatchProcessor:
"""Main class for EddyPro batch processing operations."""

def __init__(self, config_path: Optional[Path] = None):
def __init__(self, config_path: Path | None = None):
"""Initialize the processor with optional config path."""
self.config_path = config_path or Path("config/config.yaml")
self.config: Dict[str, Any] = {}
self.config: dict[str, Any] = {}

def load_config(self, config_path: Optional[Path] = None) -> Dict[str, Any]:
def load_config(self, config_path: Path | None = None) -> dict[str, Any]:
"""Load the YAML configuration file.

This function attempts to read and parse a YAML configuration file
Expand All @@ -54,7 +54,7 @@ def load_config(self, config_path: Optional[Path] = None) -> Dict[str, Any]:

try:
with self.config_path.open("r") as file:
config: Dict[str, Any] = yaml.safe_load(file)
config: dict[str, Any] = yaml.safe_load(file)
logging.info(
f"Configuration loaded successfully from {self.config_path}"
)
Expand All @@ -67,7 +67,7 @@ def load_config(self, config_path: Optional[Path] = None) -> Dict[str, Any]:
logging.exception("Error parsing the configuration file")
sys.exit(1)

def validate_config(self, config: Optional[Dict[str, Any]] = None) -> None:
def validate_config(self, config: dict[str, Any] | None = None) -> None:
"""
Validate the essential configuration parameters.

Expand Down Expand Up @@ -112,7 +112,7 @@ def validate_config(self, config: Optional[Dict[str, Any]] = None) -> None:

# Validate metrics_interval_seconds
metrics_interval = config.get("metrics_interval_seconds")
if not isinstance(metrics_interval, (int, float)) or metrics_interval <= 0:
if not isinstance(metrics_interval, int | float) or metrics_interval <= 0:
logging.error(
"Invalid 'metrics_interval_seconds' value. "
"It must be a positive number."
Expand All @@ -135,7 +135,7 @@ def run_subprocess_with_monitoring(
working_dir: Path,
stream_output: bool = True,
metrics_interval: float = 0.5,
output_dir: Optional[Path] = None,
output_dir: Path | None = None,
scenario_suffix: str = "",
) -> int:
"""
Expand Down Expand Up @@ -318,7 +318,7 @@ def validate_config(config: dict) -> None:


def generate_run_report(
config: Dict[str, Any],
config: dict[str, Any],
site_id: str,
years_processed: list,
output_base_dir: Path,
Expand Down Expand Up @@ -428,7 +428,7 @@ def run_single_scenario(
stream_output: bool,
metrics_interval: float,
dry_run: bool = False,
) -> Dict[str, Any]:
) -> dict[str, Any]:
"""
Execute a single scenario with patched parameters.

Expand Down Expand Up @@ -541,14 +541,14 @@ def run_single_scenario(


def run_scenario_batch(
scenario_list: List[Scenario],
scenario_list: list[Scenario],
template_path: Path,
output_base_dir: Path,
eddypro_executable: Path,
stream_output: bool,
metrics_interval: float,
dry_run: bool = False,
) -> List[Dict[str, Any]]:
) -> list[dict[str, Any]]:
"""
Execute a batch of scenarios sequentially.

Expand Down
16 changes: 8 additions & 8 deletions src/eddypro_batch_processor/ini_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import configparser
import logging
from pathlib import Path
from typing import Any, Dict, Optional, Set
from typing import Any

logger = logging.getLogger(__name__)

# Parameter validation rules
PARAMETER_VALIDATION: Dict[str, Dict[str, Any]] = {
PARAMETER_VALIDATION: dict[str, dict[str, Any]] = {
"rot_meth": {
"section": "RawProcess_Settings",
"allowed_values": {1, 3},
Expand Down Expand Up @@ -75,7 +75,7 @@ def validate_parameter(param_name: str, value: Any) -> int:

# Check if value is in allowed set
validation_info = PARAMETER_VALIDATION[param_name]
allowed_values: Set[int] = validation_info["allowed_values"]
allowed_values: set[int] = validation_info["allowed_values"]
if int_value not in allowed_values:
allowed = sorted(allowed_values)
description = validation_info["description"]
Expand All @@ -87,7 +87,7 @@ def validate_parameter(param_name: str, value: Any) -> int:
return int_value


def validate_parameters(parameters: Dict[str, Any]) -> Dict[str, int]:
def validate_parameters(parameters: dict[str, Any]) -> dict[str, int]:
"""
Validate a dictionary of parameters.

Expand Down Expand Up @@ -138,7 +138,7 @@ def read_ini_template(template_path: Path) -> configparser.ConfigParser:


def patch_ini_parameters(
config: configparser.ConfigParser, parameters: Dict[str, int]
config: configparser.ConfigParser, parameters: dict[str, int]
) -> None:
"""
Patch INI configuration with validated parameters.
Expand Down Expand Up @@ -190,7 +190,7 @@ def write_ini_file(config: configparser.ConfigParser, output_path: Path) -> None


def create_patched_ini(
template_path: Path, output_path: Path, parameters: Optional[Dict[str, Any]] = None
template_path: Path, output_path: Path, parameters: dict[str, Any] | None = None
) -> None:
"""
Create a patched INI file from template with parameter overrides.
Expand Down Expand Up @@ -222,7 +222,7 @@ def create_patched_ini(
logger.info(f"Created patched INI file: {output_path}")


def get_parameter_info() -> Dict[str, Dict[str, Any]]:
def get_parameter_info() -> dict[str, dict[str, Any]]:
"""
Get information about all supported parameters.

Expand All @@ -232,7 +232,7 @@ def get_parameter_info() -> Dict[str, Dict[str, Any]]:
return PARAMETER_VALIDATION.copy()


def generate_scenario_suffix(parameters: Dict[str, int]) -> str:
def generate_scenario_suffix(parameters: dict[str, int]) -> str:
"""
Generate a deterministic suffix for scenario identification.

Expand Down
Loading
Loading