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 .devcontainer/post_create_command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cd /opt/dagster/app
# install requirements-dev.txt if it exists
if [ -f requirements-dev.txt ]; then
echo "Installing requirements-dev.txt..."
pip install -r requirements-dev.txt
pip install -c constraints.txt -r requirements-dev.txt
else
echo "requirements-dev.txt not found, skipping..."
fi
Expand Down
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.git
.venv
venv
**/__pycache__
**/*.pyc
.env
.env.*
.sesskey
**/node_modules
**/.docusaurus
**/build
dist
*.egg-info
tmpdata
models
dagster_home/history
dagster_home/storage
dagster_home/logs
dagster_home/schedules
12 changes: 6 additions & 6 deletions .github/workflows/deploy-docs-site.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ jobs:
run:
working-directory: docs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v7
with:
node-version: "20"
node-version: "24"

- name: Install dependencies
run: yarn install --frozen-lockfile
run: npm ci

- name: Build website
run: yarn build
run: npm run build

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ jobs:
runs-on: ubuntu-latest
steps:
# in case of PR, check out the PR's head branch
- uses: actions/checkout@v3
- uses: actions/checkout@v7
if: github.event_name == 'pull_request_target'
with:
ref: ${{ github.event.pull_request.head.sha }}

# in case of push, check out the main branch
- uses: actions/checkout@v3
- uses: actions/checkout@v7
if: github.event_name != 'pull_request_target'

- uses: actions/setup-python@v4
- uses: actions/setup-python@v7
with:
python-version: "3.11"
python-version: "3.12"
cache: "pip"
cache-dependency-path: "**/requirements*.txt"
- uses: pre-commit/action@v3.0.0
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python 3.11
uses: actions/setup-python@v4
- name: Set up Python 3.12
uses: actions/setup-python@v7
with:
python-version: "3.11"
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
if [ -f requirements.txt ]; then pip install -c constraints.txt -r requirements.txt -r requirements-dashboard.txt; fi
if [ -f requirements-dev.txt ]; then pip install -c constraints.txt -r requirements-dev.txt; fi

- name: Test with pytest and generate coverage
run: |
Expand Down Expand Up @@ -95,7 +95,7 @@ jobs:

- name: Comment PR with coverage
if: github.event_name == 'pull_request'
uses: actions/github-script@v6
uses: actions/github-script@v9
with:
script: |
const coverage = '${{ steps.coverage.outputs.percentage }}';
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: check-ast
- id: check-case-conflict
Expand All @@ -16,8 +16,8 @@ repos:
args: ["--markdown-linebreak-ext=md"]
# isort removed - ruff handles import sorting now
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
rev: v0.16.6
hooks:
- id: ruff
- id: ruff-check
args: ["--fix"]
- id: ruff-format
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ services:
command: dagster code-server start -h 0.0.0.0 -p 4000 -f anomstack/main.py
ports:
- "4000:4000"

dagster_webserver:
image: anomstack_dagster:latest
depends_on:
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,4 @@ The `metrics/examples/` directory contains ready-to-use examples:

When adding new metrics, follow existing patterns in examples and ensure proper `.yaml` configuration with required fields like `metric_batch`, `db`, and cron schedules.

Use `make run-example EXAMPLE=<name>` to test individual examples or `make list-examples` to see all available examples.
Use `make run-example EXAMPLE=<name>` to test individual examples or `make list-examples` to see all available examples.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ We want your work to be readable by others; therefore, we encourage you to note
The following are considered to be bad and may be requested to be improved:

```python
x = x + 2 # increased by 2
x = x + 2 # increased by 2
```

This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code.
Expand Down Expand Up @@ -118,7 +118,7 @@ We want your work to be readable by others; therefore, we encourage you to note
The use of the Python built-in `input()` function is __not__ encouraged:

```python
input('Enter your input:')
input("Enter your input:")
# Or even worse...
input = eval(input("Enter your input: "))
```
Expand Down
2 changes: 1 addition & 1 deletion DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ make docker
- `anomstack_metrics_duckdb:/data` (DuckDB data)
- `./dagster_home:/opt/dagster/dagster_home` (Dagster storage - includes SQLite)

### 2. Dagster Daemon (`anomstack_daemon`)
### 2. Dagster Daemon (`anomstack_daemon`)
- **Image**: Built locally from `docker/Dockerfile.dagster`
- **Purpose**: Background process for scheduling and run execution
- **Restart Policy**: `on-failure`
Expand Down
46 changes: 46 additions & 0 deletions LOCAL_DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Local development

Use Python 3.12 and [uv](https://docs.astral.sh/uv/). From the repository root:

```sh
make setup-local
make stack
```

The dashboard runs at http://localhost:8080 and Dagster at http://localhost:3000.
Keep the command running; Ctrl-C stops both services and their child processes.
The first start seeds 72 hours of synthetic metrics, scores, and alerts. Later starts
reuse the database. Runtime files live in ignored `tmpdata/local-stack/`.

This setup uses the `python_ingest_simple` example, local DuckDB, and separate
Dagster history. It does not load the repository's `.env`; email and Slack alert
methods are empty. Schedules start stopped. Start jobs manually in Dagster as needed.
The example configuration is refreshed from the repository on each start.

Dashboard code is loaded from the working tree. Restart `make stack` after changes.
For the existing environment and full metric configuration, the older `make local`
and dashboard targets remain available.

## Validation and dependency updates

```sh
make test-local
cd docs
npm ci
npm run build
```

`make test-local` uses the CI test mode, which skips live third-party API checks.
To run those as well, use `.venv/bin/python -m pytest tests/`; some require credentials.

The Python runtime and development dependency set is recorded in `constraints.txt` and used by local
setup, CI, and Docker builds. To resolve newer compatible versions:

```sh
make update-dependencies
make setup-local
make test-local
```

Review compatibility and smoke-test both services before releasing. The docs site
uses npm and `package-lock.json` as its dependency lock. Use Node.js 24 LTS.
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include requirements.txt
recursive-include anomstack *.sql *.jinja *.jinja2 *.yaml
recursive-include dashboard/static *
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dev:
# DOCKER OPERATIONS
# =============================================================================

.PHONY: docker docker-dev docker-smart docker-build docker-dev-build docker-clean
.PHONY: docker docker-dev docker-smart docker-build docker-dev-build docker-clean
.PHONY: docker-logs docker-logs-code docker-logs-dagit docker-logs-daemon docker-logs-dashboard
.PHONY: docker-shell-code docker-shell-dagit docker-shell-dashboard docker-restart-dashboard docker-restart-code docker-restart reload-config enable-auto-reload enable-config-watcher
.PHONY: docker-stop docker-down docker-rm docker-prune
Expand All @@ -51,7 +51,7 @@ docker-dev:

# build docker images locally
docker-build:
docker build -f docker/Dockerfile.dagster_consolidated -t anomstack_consolidated_image .
docker build -f docker/Dockerfile.dagster -t anomstack_consolidated_image .
docker build -f docker/Dockerfile.anomstack_dashboard -t anomstack_dashboard_image .

# build docker images for development
Expand Down Expand Up @@ -515,3 +515,18 @@ docker-dev-env:
# stop docker containers
docker-stop:
docker compose -f docker-compose.yaml -f docker-compose.dev.yaml down

# Reproducible Python 3.12 development environment (requires uv).
.PHONY: setup-local stack test-local update-dependencies
setup-local:
uv venv --python 3.12 --allow-existing .venv
uv pip install --python .venv/bin/python -c constraints.txt -r requirements.txt -r requirements-dashboard.txt -r requirements-dev.txt

stack:
.venv/bin/python scripts/development/local_stack.py

test-local:
CI=true .venv/bin/python -m pytest tests/

update-dependencies:
uv pip compile requirements.txt requirements-dashboard.txt requirements-dev.txt --no-emit-package anomstack --universal --upgrade --python-version 3.12 -o constraints.txt
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
> Returning to the project? See [Local development](LOCAL_DEVELOPMENT.md) for reproducible setup and a local stack with sample data.

# Anomstack

[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/andrewm4894/anomstack)
Expand Down
34 changes: 34 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Anomstack 0.0.20

Maintenance release to refresh dependencies and restore a repeatable local development workflow.

## Changes

- Add `make setup-local`, `make stack`, and `make test-local` for Python 3.12 development.
- Start an isolated dashboard and Dagster instance with 72 hours of synthetic DuckDB data. Production maintenance jobs are excluded, dotenv loading is disabled, and example schedules start stopped.
- Resolve and lock compatible runtime and development dependencies across platforms in `constraints.txt`; use the same constraints in local setup, CI, and Docker builds.
- Update Dagster to 1.13.21, PyOD to 3.6.5, scikit-learn to 1.9.0, NumPy to 2.5.2, FastHTML to 0.14.13, MonsterUI to 1.0.47, and Plotly to 7.0.0. Replace PyOD's moving Git reference with its stable package and remove the obsolete `dagit` dependency.
- Match the browser's Plotly JavaScript version to the installed Python package and fix negative pagination labels for small metric batches.
- Update Docusaurus to 3.10.2 and React to 19.2.8, standardize docs on npm, and refresh transitive dependencies. Use Node.js 24 LTS for docs.
- Update GitHub Actions and pre-commit hooks, apply repository formatting, fix recursive pytest collection, and make LLM tests independent of local provider settings.
- Unify package and dashboard version reporting, repair source-distribution packaging, and correct the Docker build target.

## Upgrade

Run `make setup-local`, then `make stack`. See [Local development](LOCAL_DEVELOPMENT.md).
Existing trained model artifacts should be retrained with the upgraded NumPy/scikit-learn/PyOD stack before scoring production metrics.

## Validation

- Python CI-mode suite: 209 passed, 13 skipped (live external API tests and an existing skip).
- Full repository pre-commit checks passed.
- Real local ingest, PCA/KNN training for five metrics, and scoring completed successfully.
- Dashboard health, metric charts, and Dagster code-location loading verified locally.
- Python source archive and wheel built successfully.
- Docs production build passed; npm audit is down to 18 high findings, all originating from the unpatched `image-size` dependency.
- Linux ARM dashboard Docker image built successfully.

## Known limitations

- CoinDesk's live example endpoint returned HTTP 401 without API credentials. The CI-mode test suite skips live external API checks; those integrations have not all been verified against live services.
- npm reports upstream `image-size` parser denial-of-service advisories, with no patched release available at the time of this update. This dependency belongs to the docs image-processing/build toolchain. Only build trusted documentation assets. See [GHSA-w3rx-r6r6-pgpr](https://github.com/advisories/GHSA-w3rx-r6r6-pgpr) and [GHSA-5p2g-fcmc-qvqq](https://github.com/advisories/GHSA-5p2g-fcmc-qvqq).
1 change: 1 addition & 0 deletions anomstack/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.0.20"
2 changes: 1 addition & 1 deletion anomstack/alerts/asciiart.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ def make_alert_message(
if i <= 1:
message += "\n" + line
else:
message += "\n" + f"t={0-i+2}".ljust(6, " ") + line
message += "\n" + f"t={0 - i + 2}".ljust(6, " ") + line
message += f"""
<pre><code>{message}</code></pre>
"""
Expand Down
2 changes: 1 addition & 1 deletion anomstack/alerts/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def send_alert_slack(
slack_token = os.environ.get("ANOMSTACK_SLACK_BOT_TOKEN")
if not slack_token:
raise ValueError(
("Slack bot token not found in environment variable " "ANOMSTACK_SLACK_BOT_TOKEN")
("Slack bot token not found in environment variable ANOMSTACK_SLACK_BOT_TOKEN")
)

client = WebClient(token=slack_token)
Expand Down
12 changes: 2 additions & 10 deletions anomstack/df/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,15 @@ The typical data processing flow uses these utilities in sequence:
from anomstack.df.wrangle import clean_metric_data

# Clean and standardize metric dataframe
df_clean = clean_metric_data(
df_raw,
timestamp_col='ts',
value_col='metric_value'
)
df_clean = clean_metric_data(df_raw, timestamp_col="ts", value_col="metric_value")
```

### Time Series Resampling
```python
from anomstack.df.resample import resample_metrics

# Aggregate to hourly frequency
df_hourly = resample_metrics(
df_clean,
freq='1H',
agg_method='mean'
)
df_hourly = resample_metrics(df_clean, freq="1H", agg_method="mean")
```

### Data Validation
Expand Down
2 changes: 1 addition & 1 deletion anomstack/df/wrangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def wrangle_df(df: pd.DataFrame, rounding: int = 4) -> pd.DataFrame:
# if we have any nan metric_values then drop them and log how many
# nan rows we dropped
if df["metric_value"].isnull().sum() > 0:
logger.warning(f"dropping {df['metric_value'].isnull().sum()} nan " "metric_value rows")
logger.warning(f"dropping {df['metric_value'].isnull().sum()} nan metric_value rows")
df = df[~df["metric_value"].isnull()]

# round metric_value
Expand Down
5 changes: 3 additions & 2 deletions anomstack/external/aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ Example lifecycle policy:
Enable debug logging:
```python
import logging
logging.getLogger('boto3').setLevel(logging.DEBUG)
logging.getLogger('botocore').setLevel(logging.DEBUG)

logging.getLogger("boto3").setLevel(logging.DEBUG)
logging.getLogger("botocore").setLevel(logging.DEBUG)
```

### Performance Monitoring
Expand Down
Loading
Loading