Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:

jobs:
quality:
name: Black, Pylint, and Mypy
name: Ruff, Pylint, and Mypy
runs-on: ubuntu-latest

steps:
Expand All @@ -24,8 +24,8 @@ jobs:
- name: Install tox
run: python -m pip install --upgrade pip tox

- name: Check formatting with Black
run: tox -e black
- name: Lint and check formatting with Ruff
run: tox -e ruff

- name: Lint with Pylint
run: tox -e pylint
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ python -m pip install -e .[dev] tox
Run all environments at once or pick individual ones.

```bash
# Run everything (pylint, mypy, black, tests on Python 3.10-3.14)
# Run everything (pylint, mypy, ruff, tests on Python 3.10-3.14)
tox

# Run a single check
tox -e pylint # Lint with pylint
tox -e mypy # Type-check with mypy
tox -e black # Format with black
tox -e ruff # Lint and check formatting with ruff

# Run tests on a specific Python version
tox -e pytest-py312
Expand Down
2 changes: 1 addition & 1 deletion dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ pytest>=8.0
pytest-asyncio>=0.23.0
pytest-cov>=5.0
pytest-benchmark>=4.0
black>=24.0
ruff>=0.6
microsoft-agents-activity
microsoft-agents-hosting-core
41 changes: 36 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ test = [
"pytest-benchmark>=4.0",
]
dev = [
"black>=24.0",
"pylint>=3.3",
"mypy>=1.10",
"pyright>=1.1",
"pytest>=8.0",
"pytest-cov>=5.0",
"pytest-benchmark>=4.0",
"ruff>=0.6",
]
docs = [
"sphinx>=7.0",
Expand All @@ -100,10 +100,6 @@ package-dir = {"" = "src"}
where = ["src"]
namespaces = true

[tool.black]
line-length = 120
target-version = ["py310"]

[tool.pylint.main]
py-version = "3.10"

Expand Down Expand Up @@ -147,3 +143,38 @@ testpaths = ["tests"]
# Skip pytest-benchmark tests by default; the perf workflow opts in with
# --benchmark-only.
addopts = "--benchmark-skip"

[tool.ruff]
line-length = 120
target-version = "py310"
extend-exclude = [
".venv",
"build",
"dist",
"src/microsoft_opentelemetry.egg-info",
"docs",
]

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"B", # flake8-bugbear
"UP", # pyupgrade
"SIM", # flake8-simplify
"C4", # flake8-comprehensions
]
ignore = [
"E501", # line length handled by formatter
"UP007",
"UP045",
]
Comment thread
rads-1996 marked this conversation as resolved.

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["B011"]
"__init__.py" = ["F401"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
4 changes: 0 additions & 4 deletions samples/a365/manual_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ def main():
agent_details=agent,
caller_details=caller,
) as invoke_scope:

# Record structured input messages
invoke_scope.record_input_messages(
InputMessages(
Expand All @@ -139,7 +138,6 @@ def main():
agent_details=agent,
user_details=user,
) as inference_scope:

inference_scope.record_input_messages(
InputMessages(
messages=[
Expand Down Expand Up @@ -190,7 +188,6 @@ def main():
agent_details=agent,
user_details=user,
) as tool_scope:

# Simulate tool execution
time.sleep(0.02)
tool_result = '{"temperature": 62, "condition": "Partly cloudy"}'
Expand All @@ -213,7 +210,6 @@ def main():
agent_details=agent,
user_details=user,
) as inference_scope_2:

time.sleep(0.05)

final_answer = "It's currently 62°F and partly cloudy in Seattle."
Expand Down
2 changes: 1 addition & 1 deletion samples/distro/fastapi_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
logger = getLogger(__name__)
logger.setLevel(INFO)

from fastapi import FastAPI # pylint: disable=wrong-import-position
from fastapi import FastAPI # noqa: E402, pylint: disable=wrong-import-position

app = FastAPI()

Expand Down
24 changes: 11 additions & 13 deletions samples/langchain/validate_traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ def check(label, condition, detail=""):
print(f" {status} {label}" + (f" — {detail}" if detail else ""))

# ── LLM span checks ────────────────────────────────────────────────
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
print(f"LLM SPANS ({len(llm_spans)} found)")
print(f"{'='*60}")
print(f"{'=' * 60}")

for span in llm_spans:
attrs = span.attributes
Expand All @@ -150,12 +150,10 @@ def check(label, condition, detail=""):

# ── Responses-API fake span: response.model / response.id from
# message.response_metadata (the bug this fix addresses) ─────────
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
print("RESPONSES-API FAKE SPAN CHECKS")
print(f"{'='*60}")
responses_spans = [
s for s in llm_spans if s.attributes.get("gen_ai.response.id") == "resp_abc123"
]
print(f"{'=' * 60}")
responses_spans = [s for s in llm_spans if s.attributes.get("gen_ai.response.id") == "resp_abc123"]
check("Responses-API fake LLM span found", len(responses_spans) == 1, f"found {len(responses_spans)}")
if responses_spans:
attrs = responses_spans[0].attributes
Expand All @@ -171,9 +169,9 @@ def check(label, condition, detail=""):
)

# ── Tool span checks ───────────────────────────────────────────────
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
print(f"TOOL SPANS ({len(tool_spans)} found)")
print(f"{'='*60}")
print(f"{'=' * 60}")

for span in tool_spans:
attrs = span.attributes
Expand All @@ -194,22 +192,22 @@ def check(label, condition, detail=""):
check("gen_ai.tool.call.result present (content on)", "gen_ai.tool.call.result" in attrs)

# ── All-span attribute dump ─────────────────────────────────────────
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
print("ALL SPANS ATTRIBUTE DUMP")
print(f"{'='*60}")
print(f"{'=' * 60}")
for span in spans:
print(f"\n [{span.kind.name}] {span.name}")
for k, v in sorted(span.attributes.items()):
val = repr(v) if len(repr(v)) < 120 else repr(v)[:120] + "..."
print(f" {k} = {val}")

# ── Summary ─────────────────────────────────────────────────────────
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
if ok:
print("✅ ALL CHECKS PASSED")
else:
print("❌ SOME CHECKS FAILED")
print(f"{'='*60}")
print(f"{'=' * 60}")

inst.uninstrument()
provider.shutdown()
Expand Down
2 changes: 1 addition & 1 deletion samples/microsoft_agent_framework/sample_maf_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ async def main():


if __name__ == "__main__":
asyncio.run(main())
asyncio.run(main())
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
class AzureMonitorConfigurator(_OTelSDKConfigurator):
def _configure(self, **kwargs):
if not _is_attach_enabled():
warn(_PREVIEW_ENTRY_POINT_WARNING)
warn(_PREVIEW_ENTRY_POINT_WARNING, stacklevel=2)
try:
if environ.get(OTEL_TRACES_EXPORTER, "").lower().strip() != "none":
kwargs.setdefault(TRACE_EXPORTER_NAMES_ARG, ["azure_monitor_opentelemetry_exporter"])
Expand All @@ -63,7 +63,7 @@ def _configure(self, **kwargs):
)
except Exception as e:
AzureDiagnosticLogging.error( # pylint: disable=C
"Azure Monitor Configurator failed during configuration: %s" % str(e),
"Azure Monitor Configurator failed during configuration: %s" % str(e), # noqa: UP031
_ATTACH_FAILURE_CONFIGURATOR,
)
raise e
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
class AzureMonitorDistro(BaseDistro):
def _configure(self, **kwargs) -> None:
if not _is_attach_enabled():
warn(_PREVIEW_ENTRY_POINT_WARNING)
warn(_PREVIEW_ENTRY_POINT_WARNING, stacklevel=2)
try:
_configure_auto_instrumentation()
AzureStatusLogger.log_status(True)
Expand All @@ -51,7 +51,7 @@ def _configure(self, **kwargs) -> None:
except Exception as e:
AzureStatusLogger.log_status(False, reason=str(e))
AzureDiagnosticLogging.error( # pylint: disable=C
"Azure Monitor OpenTelemetry Distro failed during configuration: %s" % str(e),
"Azure Monitor OpenTelemetry Distro failed during configuration: %s" % str(e), # noqa: UP031
_ATTACH_FAILURE_DISTRO,
)
raise e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# license information.
# -------------------------------------------------------------------------

from typing import Optional, Dict, Any
from typing import Optional, Any


class BrowserSDKConfig:
Expand Down Expand Up @@ -34,13 +34,13 @@ def __init__(self, enabled: bool = True, connection_string: Optional[str] = None
self.enabled = enabled
self.connection_string = connection_string

def to_dict(self) -> Dict[str, Dict[str, Any]]:
def to_dict(self) -> dict[str, dict[str, Any]]:
"""Convert the config to a dictionary for the web snippet.

:return: Dictionary representation of the configuration.
:rtype: dict
"""
cfg: Dict[str, Any] = {}
cfg: dict[str, Any] = {}
if self.connection_string:
cfg["connectionString"] = self.connection_string
return {"cfg": cfg}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# license information.
# -------------------------------------------------------------------------

from typing import Optional, Callable, Any, Union
from typing import Optional, Any, Union
from collections.abc import Callable
from logging import getLogger

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import importlib
import re
from logging import getLogger
from typing import Any, Dict, Optional, Tuple
from typing import Any, Optional

from ._config import BrowserSDKConfig

Expand Down Expand Up @@ -194,7 +194,7 @@ def inject_with_compression(
content: bytes,
content_encoding: Optional[str] = None,
encoding: str = "utf-8",
) -> Tuple[bytes, Optional[str]]: # pylint: disable=too-many-return-statements
) -> tuple[bytes, Optional[str]]: # pylint: disable=too-many-return-statements
"""Inject snippet handling compression/decompression efficiently using cached decompressed content.

:param content: Response content bytes, potentially compressed.
Expand Down Expand Up @@ -264,7 +264,7 @@ def _get_decompressed_content(self, content: bytes, content_encoding: Optional[s
:rtype: bytes
"""
# Fast path: if no encoding specified and content doesn't look compressed, return as-is
if not content_encoding:
if not content_encoding: # noqa: SIM102
# Quick check for compression headers to avoid unnecessary processing
if not self._appears_compressed(content):
return content
Expand Down Expand Up @@ -308,9 +308,7 @@ def _appears_compressed(self, content: bytes) -> bool:
# Brotli: no reliable magic number, but check for common patterns
# (Brotli detection is harder without trying to decompress)
# zlib/deflate: starts with 0x78 followed by various values
if content[0] == 0x78 and content[1] in (0x01, 0x5E, 0x9C, 0xDA):
return True
return False
return content[0] == 0x78 and content[1] in (0x01, 0x5E, 0x9C, 0xDA)

def _clear_decompression_cache(self) -> None:
"""Clear the decompressed content cache.
Expand Down Expand Up @@ -464,7 +462,7 @@ def _format_config_value(self, value):
return self._dict_to_js_object(value)
return f'"{str(value)}"'

def _dict_to_js_object(self, obj: Dict[str, Any]) -> str:
def _dict_to_js_object(self, obj: dict[str, Any]) -> str:
"""Convert Python dict to JavaScript object literal.

:param obj: Python dictionary to convert to JavaScript object syntax.
Expand Down
Loading
Loading