Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
49 changes: 49 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Cursor Rules for Cadence Python Client

## Package Management
- **Always use `uv` for Python package management**
- Use `uv run` for running Python commands instead of `python` directly
- Use `uv sync` for installing dependencies instead of `pip install`
- Use `uv tool run` for running development tools (pytest, mypy, ruff, etc.)
- Only use `pip` or `python` directly when specifically required by the tool or documentation

## Examples
```bash
# ✅ Correct
uv run python scripts/generate_proto.py
uv run python -m pytest tests/
uv tool run mypy cadence/
uv tool run ruff check

# ❌ Avoid
python scripts/generate_proto.py
pip install -e ".[dev]"
```

## Virtual Environment
- The project uses `uv` for virtual environment management
- Always activate the virtual environment using `uv` commands
- Dependencies are managed through `pyproject.toml` and `uv.lock`

## Testing
- Run tests with `uv run python -m pytest`
- Use `uv run` for any Python script execution
- Development tools should be run with `uv tool run`

## Code Generation
- Use `uv run python scripts/generate_proto.py` for protobuf generation
- Use `uv run python scripts/dev.py` for development tasks

## Code Quality
- **ALWAYS run linter and type checker after making code changes**
- Run linter with auto-fix: `uv tool run ruff check --fix`
- Run type checking: `uv tool run mypy cadence/`
- Use `uv tool run ruff check --fix && uv tool run mypy cadence/` to run both together
- **Standard workflow**: Make changes → Run linter → Run type checker → Commit

## Development Workflow
1. Make code changes
2. Run `uv tool run ruff check --fix` (fixes formatting and linting issues)
3. Run `uv tool run mypy cadence/` (checks type safety)
4. Run `uv run python -m pytest` (run tests)
5. Commit changes
12 changes: 12 additions & 0 deletions cadence/_internal/visibility/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Visibility and metrics collection components for Cadence client."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MetricsEmitter and the prometheus stuff should be in a public package. Users need to specify it in the ClientOptions so it should be visibile. Maybe cadence/metrics

Additionally we should go with metrics as the package name. I don't think visibility accurately describes it.


from .metrics import MetricsHandler, NoOpMetricsHandler, get_default_handler
from .prometheus import PrometheusMetrics, PrometheusConfig

__all__ = [
"MetricsHandler",
"NoOpMetricsHandler",
"get_default_handler",
"PrometheusMetrics",
"PrometheusConfig",
]
86 changes: 86 additions & 0 deletions cadence/_internal/visibility/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Core metrics collection interface and registry for Cadence client."""

import logging
from enum import Enum
from typing import Dict, Optional, Protocol

logger = logging.getLogger(__name__)


class MetricType(Enum):
"""Types of metrics that can be collected."""

COUNTER = "counter"
GAUGE = "gauge"
HISTOGRAM = "histogram"
SUMMARY = "summary"


class MetricsHandler(Protocol):
"""Protocol for metrics collection backends."""

def counter(
self, key: str, n: int = 1, tags: Optional[Dict[str, str]] = None
) -> None:
"""Send a counter metric."""
...

def gauge(
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
) -> None:
"""Send a gauge metric."""
...

def timer(
self, key: str, duration: float, tags: Optional[Dict[str, str]] = None
) -> None:
"""Send a timer metric."""
...

def histogram(
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
) -> None:
"""Send a histogram metric."""
...


class NoOpMetricsHandler:
"""No-op metrics handler that discards all metrics."""

def counter(
self, key: str, n: int = 1, tags: Optional[Dict[str, str]] = None
) -> None:
pass

def gauge(
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
) -> None:
pass

def timer(
self, key: str, duration: float, tags: Optional[Dict[str, str]] = None
) -> None:
pass

def histogram(
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
) -> None:
pass


# Global default handler
_default_handler: Optional[MetricsHandler] = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should have a global for this, I'd rather we pass it into the Client which propagates to the Workers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks



def get_default_handler() -> MetricsHandler:
"""Get the default global metrics handler."""
global _default_handler
if _default_handler is None:
_default_handler = NoOpMetricsHandler()
return _default_handler


def set_default_handler(handler: MetricsHandler) -> None:
"""Set the default global metrics handler."""
global _default_handler
_default_handler = handler
Loading