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
20 changes: 16 additions & 4 deletions .github/workflows/publish-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,14 @@ jobs:

Write-Output "Package version set to $DEV_VERSION"

$startMarker = "<!-- DEV_PACKAGE_START -->"
$endMarker = "<!-- DEV_PACKAGE_END -->"

$dependencyMessage = @"
$startMarker
## Development Package

- Use ``uipath pack --nolock`` to get the latest dev build from this PR (requires version range).
- Add this package as a dependency in your pyproject.toml:

``````toml
Expand All @@ -83,7 +88,13 @@ jobs:

[tool.uv.sources]
$PROJECT_NAME = { index = "testpypi" }

[tool.uv]
override-dependencies = [
"$PROJECT_NAME>=$MIN_VERSION,<$MAX_VERSION",
]
``````
$endMarker
"@

# Get the owner and repo from the GitHub repository
Expand All @@ -101,10 +112,11 @@ jobs:
$pr = Invoke-RestMethod -Uri $prUri -Method Get -Headers $headers
$currentBody = $pr.body

# Check if there's already a development package section
if ($currentBody -match '## Development Package') {
# Replace the existing section with the new dependency message
$newBody = $currentBody -replace '## Development Package(\r?\n|.)*?(?=##|$)', $dependencyMessage
# Check if markers already exist in the PR description
$markerPattern = "(?s)$([regex]::Escape($startMarker)).*?$([regex]::Escape($endMarker))"
if ($currentBody -match $markerPattern) {
# Replace everything between markers (including markers)
$newBody = $currentBody -replace $markerPattern, $dependencyMessage
} else {
# Append the dependency message to the end of the description
$newBody = if ($currentBody) { "$currentBody`n`n$dependencyMessage" } else { $dependencyMessage }
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-core"
version = "0.1.6"
version = "0.1.7"
description = "UiPath Core abstractions"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
14 changes: 14 additions & 0 deletions src/uipath/core/tracing/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from functools import wraps
from typing import Any, Callable, Optional

from opentelemetry import context as context_api
from opentelemetry import trace
from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY
from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags
from opentelemetry.trace.status import StatusCode

Expand Down Expand Up @@ -78,6 +80,8 @@ def get_span():
# --------- Sync wrapper ---------
@wraps(func)
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
return func(*args, **kwargs)
span_cm, span = get_span()
try:
# Set input attributes BEFORE execution
Expand Down Expand Up @@ -113,6 +117,8 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
# --------- Async wrapper ---------
@wraps(func)
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
return await func(*args, **kwargs)
span_cm, span = get_span()
try:
# Set input attributes BEFORE execution
Expand Down Expand Up @@ -148,6 +154,10 @@ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
# --------- Generator wrapper ---------
@wraps(func)
def generator_wrapper(*args: Any, **kwargs: Any) -> Any:
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
for item in func(*args, **kwargs):
yield item
return
span_cm, span = get_span()
try:
# Set input attributes BEFORE execution
Expand Down Expand Up @@ -186,6 +196,10 @@ def generator_wrapper(*args: Any, **kwargs: Any) -> Any:
# --------- Async generator wrapper ---------
@wraps(func)
async def async_generator_wrapper(*args: Any, **kwargs: Any) -> Any:
if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
async for item in func(*args, **kwargs):
yield item
return
span_cm, span = get_span()
try:
# Set input attributes BEFORE execution
Expand Down
158 changes: 158 additions & 0 deletions tests/tracing/test_traced.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,3 +816,161 @@ async def sample_async_generator_function(n):
spans = exporter.get_exported_spans()

assert len(spans) == 0


# --------- Suppress Instrumentation Tests ---------


def test_suppress_instrumentation_sync_function(setup_tracer):
"""Test that suppress_instrumentation prevents spans from being created for sync functions."""
from opentelemetry.instrumentation.utils import suppress_instrumentation

exporter, provider = setup_tracer

@traced()
def sample_function(x, y):
return x + y

# Call without suppression - should create span
result = sample_function(2, 3)
assert result == 5

spans = exporter.get_exported_spans()
assert len(spans) == 1

exporter.clear_exported_spans()

# Call with suppression - should NOT create span
with suppress_instrumentation():
result = sample_function(4, 5)
assert result == 9

provider.shutdown()
spans = exporter.get_exported_spans()
assert len(spans) == 0


@pytest.mark.asyncio
async def test_suppress_instrumentation_async_function(setup_tracer):
"""Test that suppress_instrumentation prevents spans from being created for async functions."""
from opentelemetry.instrumentation.utils import suppress_instrumentation

exporter, provider = setup_tracer

@traced()
async def sample_async_function(x, y):
return x * y

# Call without suppression - should create span
result = await sample_async_function(2, 3)
assert result == 6

await sleep(0.1)
spans = exporter.get_exported_spans()
assert len(spans) == 1

exporter.clear_exported_spans()

# Call with suppression - should NOT create span
with suppress_instrumentation():
result = await sample_async_function(4, 5)
assert result == 20

provider.shutdown()
await sleep(0.1)
spans = exporter.get_exported_spans()
assert len(spans) == 0


def test_suppress_instrumentation_generator_function(setup_tracer):
"""Test that suppress_instrumentation prevents spans from being created for generator functions."""
from opentelemetry.instrumentation.utils import suppress_instrumentation

exporter, provider = setup_tracer

@traced()
def sample_generator_function(n):
for i in range(n):
yield i

# Call without suppression - should create span
results = list(sample_generator_function(3))
assert results == [0, 1, 2]

spans = exporter.get_exported_spans()
assert len(spans) == 1

exporter.clear_exported_spans()

# Call with suppression - should NOT create span
with suppress_instrumentation():
results = list(sample_generator_function(4))
assert results == [0, 1, 2, 3]

provider.shutdown()
spans = exporter.get_exported_spans()
assert len(spans) == 0


@pytest.mark.asyncio
async def test_suppress_instrumentation_async_generator_function(setup_tracer):
"""Test that suppress_instrumentation prevents spans from being created for async generator functions."""
from opentelemetry.instrumentation.utils import suppress_instrumentation

exporter, provider = setup_tracer

@traced()
async def sample_async_generator_function(n):
for i in range(n):
yield i

# Call without suppression - should create span
results = [item async for item in sample_async_generator_function(3)]
assert results == [0, 1, 2]

spans = exporter.get_exported_spans()
assert len(spans) == 1

exporter.clear_exported_spans()

# Call with suppression - should NOT create span
with suppress_instrumentation():
results = [item async for item in sample_async_generator_function(4)]
assert results == [0, 1, 2, 3]

provider.shutdown()
spans = exporter.get_exported_spans()
assert len(spans) == 0


def test_suppress_instrumentation_nested_functions(setup_tracer):
"""Test that suppress_instrumentation prevents spans for nested traced function calls."""
from opentelemetry.instrumentation.utils import suppress_instrumentation

exporter, provider = setup_tracer

@traced()
def inner_function(x):
return x * 2

@traced()
def outer_function(x):
return inner_function(x) + 1

# Call without suppression - should create 2 spans
result = outer_function(5)
assert result == 11

spans = exporter.get_exported_spans()
assert len(spans) == 2

exporter.clear_exported_spans()

# Call with suppression - should NOT create any spans
with suppress_instrumentation():
result = outer_function(10)
assert result == 21

provider.shutdown()
spans = exporter.get_exported_spans()
assert len(spans) == 0
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.