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
72 changes: 72 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: sync
run: uv sync --all-extras
- name: ruff check
run: uv run ruff check .
- name: ruff format
run: uv run ruff format --check .

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: sync
run: uv sync --all-extras
- name: pyright
run: uv run pyright measure

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: set up python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: sync
run: uv sync --all-extras --python ${{ matrix.python-version }}
- name: pytest
run: uv run --python ${{ matrix.python-version }} pytest

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: build
run: uv build --out-dir dist
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
if-no-files-found: error
retention-days: 7
95 changes: 95 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Release

on:
push:
branches: [main, master]
paths:
- pyproject.toml
workflow_dispatch:

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
# PyPI trusted publishing (recommended). Configure at:
# https://pypi.org/manage/account/publishing/
# Project name: measure Workflow: release.yaml Environment: pypi
environment:
name: pypi
url: https://pypi.org/p/measure
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: jdx/mise-action@v2
with:
experimental: true

- name: version info
id: ver
run: |
set -euo pipefail
local_version=$(uv version --short)
published_version=$(uvx get_pypi_latest_version measure 2>/dev/null || echo "none")
echo "local=$local_version" >> "$GITHUB_OUTPUT"
echo "published=$published_version" >> "$GITHUB_OUTPUT"
echo "measure: local=$local_version published=$published_version"

- name: skip if already published
if: steps.ver.outputs.local == steps.ver.outputs.published
run: echo "::notice::measure ${{ steps.ver.outputs.local }} already on PyPI — skipping"

- name: build
if: steps.ver.outputs.local != steps.ver.outputs.published
run: uv build --out-dir dist

- name: publish (trusted publishing via OIDC)
if: steps.ver.outputs.local != steps.ver.outputs.published
run: uv publish dist/*

- name: verify
if: steps.ver.outputs.local != steps.ver.outputs.published
run: |
set -euo pipefail
for i in $(seq 1 60); do
got=$(uvx get_pypi_latest_version measure 2>/dev/null || echo "")
if [ "$got" = "${{ steps.ver.outputs.local }}" ]; then
echo "::notice::published measure ${{ steps.ver.outputs.local }}"
exit 0
fi
sleep 2
done
echo "::warning::did not see measure ${{ steps.ver.outputs.local }} on PyPI after 120s"

- name: tag release
if: steps.ver.outputs.local != steps.ver.outputs.published
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="v${{ steps.ver.outputs.local }}"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "tag $tag already exists, skipping"
exit 0
fi
git tag "$tag"
git push origin "$tag"

- name: create github release
if: steps.ver.outputs.local != steps.ver.outputs.published
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="v${{ steps.ver.outputs.local }}"
gh release create "$tag" \
--title "$tag" \
--generate-notes \
dist/* || echo "::warning::release creation failed"
54 changes: 47 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,direnv

### direnv ###
.direnv
.envrc

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
Expand All @@ -9,7 +15,6 @@ __pycache__/

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
Expand All @@ -21,13 +26,14 @@ lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

Expand All @@ -38,24 +44,58 @@ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Sphinx documentation
docs/_build/

# PyBuilder
target/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre / pytype
.pyre/
.pytype/

# Cython debug symbols
cython_debug/

# IDEs
.idea/
.vscode/

# ruff
.ruff_cache/

.ropeproject/
# LSP config files
pyrightconfig.json
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
10 changes: 0 additions & 10 deletions .travis.yml

This file was deleted.

51 changes: 45 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/NorthIsUp/measure.svg)](https://travis-ci.org/NorthIsUp/measure)
[![CI](https://github.com/NorthIsUp/measure/actions/workflows/ci.yaml/badge.svg)](https://github.com/NorthIsUp/measure/actions/workflows/ci.yaml)
[![PyPI](https://img.shields.io/pypi/v/measure.svg)](https://pypi.python.org/pypi/measure)

# Measure
Expand All @@ -11,11 +11,8 @@ Measure is a metrics library that allows the user to swap metrics provider ie. (

```python
import measure
stat = measure.stats.Stats(
"homepage",
measure.Meter("pageviews", "Pageview on homepage"),
client=measure.client.PyStatsdClient()
)

stat = measure.stats.Stats("homepage", measure.Meter("pageviews", "Pageview on homepage"), client=measure.client.PyStatsdClient())

stat.pageviews.mark()
```
Expand All @@ -36,3 +33,45 @@ stat.pageviews.mark()
- `SetDict`
- `FakeStat`

## Development

This project uses [uv](https://docs.astral.sh/uv/) and [mise](https://mise.jdx.dev/) for tooling.

```bash
# install tooling and create the virtualenv
mise install
mise run sync

# run tests
mise run test

# lint and format
mise run lint
mise run fmt

# type check
mise run typecheck

# build sdist + wheel into dist/
mise run build
```

## Releases

Releases are published automatically to PyPI by `.github/workflows/release.yaml`
whenever the version in `pyproject.toml` changes on the default branch. The
workflow uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
via OIDC, so no API tokens are needed — configure trusted publishing on PyPI for
the `measure` project, pointing at this repo, the `release.yaml` workflow, and
the `pypi` GitHub environment.

To cut a release:

```bash
mise run bump-patch # or bump-minor / bump-major
git commit -am "release: vX.Y.Z"
git push
```

The workflow will detect the new version, build, publish to PyPI, push a `vX.Y.Z`
tag, and create a GitHub release.
40 changes: 16 additions & 24 deletions measure/__init__.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import

from .stats import (
Counter,
CounterDict,
FakeStat,
FakeStatDict,
Gauge,
GaugeDict,
Meter,
MeterDict,
Stat,
StatDict,
Stats,
Timer,
TimerDict,
)

from .client import (
PyStatsdClient,
Boto3Client
)
from .client import Boto3Client as Boto3Client
from .client import PyStatsdClient as PyStatsdClient
from .client import TestStatsdClient as TestStatsdClient
from .stats import Counter as Counter
from .stats import CounterDict as CounterDict
from .stats import FakeStat as FakeStat
from .stats import FakeStatDict as FakeStatDict
from .stats import Gauge as Gauge
from .stats import GaugeDict as GaugeDict
from .stats import Meter as Meter
from .stats import MeterDict as MeterDict
from .stats import Stat as Stat
from .stats import StatDict as StatDict
from .stats import Stats as Stats
from .stats import Timer as Timer
from .stats import TimerDict as TimerDict
Loading
Loading