Compare commits
7
Commits
9fc9cfb7d3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c57ea64c1 | ||
|
|
cf65198825 | ||
|
|
20928f4ab6 | ||
|
|
ffbeef5207 | ||
|
|
57c9f7554f | ||
|
|
09e1d5d4b0 | ||
|
|
2cd5930e2b |
@@ -31,15 +31,15 @@ jobs:
|
|||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
# Only log in when pushing (not on PRs from forks)
|
# Only log in when pushing (not on PRs from forks)
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
release_created: ${{ steps.release.outputs.release_created }}
|
release_created: ${{ steps.release.outputs.release_created }}
|
||||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||||
steps:
|
steps:
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@v5
|
||||||
id: release
|
id: release
|
||||||
with:
|
with:
|
||||||
release-type: python
|
release-type: python
|
||||||
@@ -36,13 +36,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Log in to GHCR
|
- name: Log in to GHCR
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker metadata
|
- name: Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ Note: Pre-commit will run these checks automatically on commit, so you usually d
|
|||||||
|
|
||||||
**Configuration issues:**
|
**Configuration issues:**
|
||||||
1. Check `.env` file syntax (should be `KEY=value`)
|
1. Check `.env` file syntax (should be `KEY=value`)
|
||||||
2. For lists, use JSON format: `STEWARD__TELEGRAM__ALLOWED_USER_IDS='[123, 456]'`
|
2. For lists, prefer CSV format: `STEWARD__TELEGRAM__ALLOWED_USER_IDS=123,456` (JSON lists are also supported)
|
||||||
3. New format variables use `STEWARD__SECTION__KEY` pattern
|
3. New format variables use `STEWARD__SECTION__KEY` pattern
|
||||||
4. Check `CONFIGURATION.md` for examples
|
4. Check `CONFIGURATION.md` for examples
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.4.2](https://github.com/djw4/steward/compare/v0.4.1...v0.4.2) (2026-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* accept CSV telegram ID config ([#27](https://github.com/djw4/steward/issues/27)) ([cf65198](https://github.com/djw4/steward/commit/cf651988251986caf4748ff30c388e6b3d9f6b5c))
|
||||||
|
|
||||||
## [0.4.1](https://github.com/djw4/steward/compare/v0.4.0...v0.4.1) (2026-07-26)
|
## [0.4.1](https://github.com/djw4/steward/compare/v0.4.0...v0.4.1) (2026-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -141,8 +141,8 @@ telegram:
|
|||||||
|
|
||||||
**Environment Variables:**
|
**Environment Variables:**
|
||||||
- `STEWARD__TELEGRAM__BOT_TOKEN` - Telegram bot token (REQUIRED)
|
- `STEWARD__TELEGRAM__BOT_TOKEN` - Telegram bot token (REQUIRED)
|
||||||
- `STEWARD__TELEGRAM__ALLOWED_USER_IDS` - JSON list: `[123,456,789]`
|
- `STEWARD__TELEGRAM__ALLOWED_USER_IDS` - Comma-separated or JSON list: `123,456,789`
|
||||||
- `STEWARD__TELEGRAM__GROUP_IDS` - JSON list: `[-1005308306472]`
|
- `STEWARD__TELEGRAM__GROUP_IDS` - Comma-separated or JSON list: `-1005308306472`
|
||||||
|
|
||||||
### OpenAI Section
|
### OpenAI Section
|
||||||
|
|
||||||
@@ -211,10 +211,10 @@ STEWARD__OPENAI__MODEL="gpt-4-turbo"
|
|||||||
STEWARD__ANALYSIS__CRON_HOUR="9"
|
STEWARD__ANALYSIS__CRON_HOUR="9"
|
||||||
```
|
```
|
||||||
|
|
||||||
For list values, use JSON format:
|
For list values, use comma-separated integers. JSON lists are also accepted for compatibility:
|
||||||
```bash
|
```bash
|
||||||
STEWARD__TELEGRAM__ALLOWED_USER_IDS='[123456789, 987654321]'
|
STEWARD__TELEGRAM__ALLOWED_USER_IDS=123456789,987654321
|
||||||
STEWARD__TELEGRAM__GROUP_IDS='[-1005308306472]'
|
STEWARD__TELEGRAM__GROUP_IDS=-1005308306472
|
||||||
```
|
```
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "steward"
|
name = "steward"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
description = "A long-running, AI-assisted personal operations platform"
|
description = "A long-running, AI-assisted personal operations platform"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
+40
-3
@@ -8,16 +8,50 @@ Supports configuration via:
|
|||||||
Environment variables take precedence over config file values.
|
Environment variables take precedence over config file values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from omegaconf import OmegaConf
|
from omegaconf import OmegaConf
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_int_list(value: Any) -> Any:
|
||||||
|
if isinstance(value, int):
|
||||||
|
return [value]
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
|
||||||
|
text = value.strip()
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if len(text) >= 2 and text[0] == text[-1] and text[0] in {"'", '"'}:
|
||||||
|
text = text[1:-1].strip()
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if text.startswith("["):
|
||||||
|
try:
|
||||||
|
parsed = json.loads(text)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if isinstance(parsed, list):
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
try:
|
||||||
|
return [int(item.strip().strip("'\"")) for item in text.split(",") if item.strip()]
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
"expected a comma-separated list of integers or a JSON integer list"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
class TelegramConfig(BaseModel):
|
class TelegramConfig(BaseModel):
|
||||||
"""Telegram bot configuration."""
|
"""Telegram bot configuration."""
|
||||||
|
|
||||||
@@ -25,6 +59,11 @@ class TelegramConfig(BaseModel):
|
|||||||
allowed_user_ids: list[int] = Field(default_factory=list, description="Allowed user IDs")
|
allowed_user_ids: list[int] = Field(default_factory=list, description="Allowed user IDs")
|
||||||
group_ids: list[int] = Field(default_factory=list, description="Allowed group/channel IDs")
|
group_ids: list[int] = Field(default_factory=list, description="Allowed group/channel IDs")
|
||||||
|
|
||||||
|
@field_validator("allowed_user_ids", "group_ids", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def parse_int_list(cls, value: Any) -> Any:
|
||||||
|
return _parse_int_list(value)
|
||||||
|
|
||||||
|
|
||||||
class OpenAIConfig(BaseModel):
|
class OpenAIConfig(BaseModel):
|
||||||
"""OpenAI/LLM configuration."""
|
"""OpenAI/LLM configuration."""
|
||||||
@@ -186,8 +225,6 @@ def _load_config_from_env() -> dict:
|
|||||||
# Try to parse value as JSON first (for lists, etc.)
|
# Try to parse value as JSON first (for lists, etc.)
|
||||||
if isinstance(cfg[section], dict):
|
if isinstance(cfg[section], dict):
|
||||||
try:
|
try:
|
||||||
import json
|
|
||||||
|
|
||||||
cfg[section][setting_key] = json.loads(value)
|
cfg[section][setting_key] = json.loads(value)
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
cfg[section][setting_key] = value
|
cfg[section][setting_key] = value
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""Tests for Steward configuration loading."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from steward import config as config_module
|
||||||
|
from steward.config import TelegramConfig, reload_settings
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_settings_singleton():
|
||||||
|
config_module._settings_instance = None
|
||||||
|
yield
|
||||||
|
config_module._settings_instance = None
|
||||||
|
|
||||||
|
|
||||||
|
def test_telegram_config_accepts_csv_id_lists():
|
||||||
|
config = TelegramConfig(
|
||||||
|
allowed_user_ids="8861600138, 123456789",
|
||||||
|
group_ids="-5308306472,-1005308306472",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.allowed_user_ids == [8861600138, 123456789]
|
||||||
|
assert config.group_ids == [-5308306472, -1005308306472]
|
||||||
|
|
||||||
|
|
||||||
|
def test_telegram_config_preserves_list_id_values():
|
||||||
|
config = TelegramConfig(
|
||||||
|
allowed_user_ids=[8861600138, 123456789],
|
||||||
|
group_ids=[-5308306472, -1005308306472],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.allowed_user_ids == [8861600138, 123456789]
|
||||||
|
assert config.group_ids == [-5308306472, -1005308306472]
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_accepts_csv_id_lists_from_env(monkeypatch):
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__ALLOWED_USER_IDS", "8861600138,123456789")
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__GROUP_IDS", "-5308306472,-1005308306472")
|
||||||
|
|
||||||
|
settings = reload_settings()
|
||||||
|
|
||||||
|
assert settings.telegram_allowed_user_ids == [8861600138, 123456789]
|
||||||
|
assert settings.telegram_group_ids == [-5308306472, -1005308306472]
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_accepts_single_id_values_from_env(monkeypatch):
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__ALLOWED_USER_IDS", "8861600138")
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__GROUP_IDS", "-5308306472")
|
||||||
|
|
||||||
|
settings = reload_settings()
|
||||||
|
|
||||||
|
assert settings.telegram_allowed_user_ids == [8861600138]
|
||||||
|
assert settings.telegram_group_ids == [-5308306472]
|
||||||
|
|
||||||
|
|
||||||
|
def test_settings_accepts_quoted_json_id_lists_from_env(monkeypatch):
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__ALLOWED_USER_IDS", "'[8861600138]'")
|
||||||
|
monkeypatch.setenv("STEWARD__TELEGRAM__GROUP_IDS", "'[-5308306472]'")
|
||||||
|
|
||||||
|
settings = reload_settings()
|
||||||
|
|
||||||
|
assert settings.telegram_allowed_user_ids == [8861600138]
|
||||||
|
assert settings.telegram_group_ids == [-5308306472]
|
||||||
Reference in New Issue
Block a user