Skip to content

Commit c6cb82f

Browse files
committed
feat(traceloop-sdk): enhance type annotations and add new dependencies
- Updated poetry.lock to include pandas-stubs and types-pytz for improved type checking. - Modified project.json to specify the mypy command for type checking the sdk directory. - Enhanced type annotations across various modules for better type safety and clarity. - Refactored function signatures to include return types for improved type hinting.
1 parent a0f3129 commit c6cb82f

12 files changed

Lines changed: 100 additions & 73 deletions

File tree

packages/traceloop-sdk/poetry.lock

Lines changed: 32 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/traceloop-sdk/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"executor": "@nxlv/python:run-commands",
5555
"outputs": [],
5656
"options": {
57-
"command": "poetry run mypy",
57+
"command": "poetry run mypy traceloop/sdk",
5858
"cwd": "packages/traceloop-sdk"
5959
}
6060
},

packages/traceloop-sdk/pyproject.toml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pytest-sugar = "1.0.0"
8383
mypy = "^1.18.2"
8484
types-requests = "^2.31.0"
8585
types-colorama = "^0.4.15"
86+
pandas-stubs = "*"
8687

8788
[tool.poetry.group.test.dependencies]
8889
openai = "^1.31.1"
@@ -114,16 +115,7 @@ namespace_packages = true
114115
explicit_package_bases = true
115116
plugins = ["pydantic.mypy"]
116117

117-
# Target specific folders for strict type checking
118-
files = [
119-
"traceloop/sdk/annotation",
120-
"traceloop/sdk/client",
121-
"traceloop/sdk/dataset",
122-
"traceloop/sdk/evaluator",
123-
"traceloop/sdk/experiment",
124-
]
125-
126-
# Exclude other folders (blacklist approach - new folders will be checked by default)
118+
# Blacklist approach - all folders checked except those excluded below
127119
exclude = [
128120
"traceloop/sdk/decorators",
129121
"traceloop/sdk/prompts",

packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ class UserFeedback(BaseAnnotation):
88
def __init__(self, http: HTTPClient, app_name: str):
99
super().__init__(http, app_name, "user_feedback")
1010

11-
1211
def create(
1312
self,
1413
annotation_task: str,

packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import csv
2-
from typing import List, Optional
2+
from typing import List, Optional, cast
33
from pathlib import Path
44

55
try:
@@ -81,6 +81,10 @@ def from_csv(
8181

8282
reader = csv.DictReader(csvfile, delimiter=delimiter)
8383

84+
# TODO: Handle None case for fieldnames more gracefully
85+
if reader.fieldnames is None:
86+
raise ValueError("CSV file has no headers")
87+
8488
for field_name in reader.fieldnames:
8589
columns_definition.append(
8690
ColumnDefinition(
@@ -138,8 +142,9 @@ def from_dataframe(
138142
)
139143
)
140144

145+
# TODO: Pandas returns Hashable keys, should ensure they're strings
141146
rows = [
142-
{self._slugify(k): v for k, v in row.items()}
147+
{self._slugify(str(k)): v for k, v in row.items()}
143148
for row in df.to_dict(orient="records")
144149
]
145150

@@ -160,14 +165,14 @@ def get_version_csv(self, slug: str, version: str) -> str:
160165
result = self._http.get(f"datasets/{slug}/versions/{version}")
161166
if result is None:
162167
raise Exception(f"Failed to get dataset {slug} by version {version}")
163-
return result
168+
return cast(str, result)
164169

165170
def get_version_jsonl(self, slug: str, version: str) -> str:
166171
"""Get a specific version of a dataset as a JSONL string"""
167172
result = self._http.get(f"datasets/{slug}/versions/{version}/jsonl")
168173
if result is None:
169174
raise Exception(f"Failed to get dataset {slug} by version {version}")
170-
return result
175+
return cast(str, result)
171176

172177
def _create_dataset(self, input: CreateDatasetRequest) -> CreateDatasetResponse:
173178
"""Create new dataset"""

packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, TypeVar, Callable, Any, ParamSpec, Awaitable
1+
from typing import Optional, TypeVar, Callable
22
import warnings
33

44
from opentelemetry.semconv_ai import TraceloopSpanKindValues
@@ -8,9 +8,7 @@
88
entity_method,
99
)
1010

11-
P = ParamSpec("P")
12-
R = TypeVar("R")
13-
F = TypeVar("F", bound=Callable[P, R | Awaitable[R]])
11+
F = TypeVar("F")
1412

1513

1614
def task(

packages/traceloop-sdk/traceloop/sdk/decorators/base.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
Callable,
88
Any,
99
cast,
10-
ParamSpec,
11-
Awaitable,
1210
)
1311
import inspect
1412
import warnings
@@ -29,10 +27,7 @@
2927
from traceloop.sdk.utils import camel_to_snake
3028
from traceloop.sdk.utils.json_encoder import JSONEncoder
3129

32-
P = ParamSpec("P")
33-
34-
R = TypeVar("R")
35-
F = TypeVar("F", bound=Callable[P, R | Awaitable[R]])
30+
F = TypeVar("F")
3631

3732

3833
def _truncate_json_if_needed(json_str: str) -> str:

packages/traceloop-sdk/traceloop/sdk/fetcher.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import requests
77

88
from threading import Thread, Event
9-
from typing import Dict
9+
from typing import Dict, Any
1010
from tenacity import (
1111
RetryError,
1212
retry,
@@ -51,7 +51,7 @@ def __init__(self, base_url: str, api_key: str):
5151
),
5252
)
5353

54-
def run(self):
54+
def run(self) -> None:
5555
refresh_data(
5656
self._base_url,
5757
self._api_key,
@@ -61,10 +61,10 @@ def run(self):
6161
self._exit_monitor.start()
6262
self._poller_thread.start()
6363

64-
def post(self, api: str, body: Dict[str, str]):
64+
def post(self, api: str, body: Dict[str, str]) -> None:
6565
post_url(f"{self._base_url}/v1/traceloop/{api}", self._api_key, body)
6666

67-
def api_post(self, api: str, body: Dict[str, typing.Any]):
67+
def api_post(self, api: str, body: Dict[str, typing.Any]) -> None:
6868
try:
6969
post_url(f"{self._base_url}/v2/{api}", self._api_key, body)
7070
except Exception as e:
@@ -83,7 +83,7 @@ def __init__(
8383
super().__init__(lambda e: check_http_error(e))
8484

8585

86-
def check_http_error(e):
86+
def check_http_error(e: BaseException) -> bool:
8787
return isinstance(e, requests.exceptions.HTTPError) and (
8888
500 <= e.response.status_code < 600
8989
)
@@ -94,7 +94,7 @@ def check_http_error(e):
9494
stop=stop_after_attempt(MAX_RETRIES),
9595
retry=RetryIfServerError(),
9696
)
97-
def fetch_url(url: str, api_key: str):
97+
def fetch_url(url: str, api_key: str) -> Any:
9898
response = requests.get(
9999
url,
100100
headers={
@@ -114,7 +114,7 @@ def fetch_url(url: str, api_key: str):
114114
return response.json()
115115

116116

117-
def post_url(url: str, api_key: str, body: Dict[str, typing.Any]):
117+
def post_url(url: str, api_key: str, body: Dict[str, typing.Any]) -> None:
118118
response = requests.post(
119119
url,
120120
headers={
@@ -135,7 +135,7 @@ def thread_func(
135135
api_key: str,
136136
stop_polling_event: Event,
137137
seconds_interval: float = 5.0,
138-
):
138+
) -> None:
139139
while not stop_polling_event.is_set():
140140
try:
141141
refresh_data(base_url, api_key, prompt_registry, content_allow_list)
@@ -151,15 +151,15 @@ def refresh_data(
151151
api_key: str,
152152
prompt_registry: PromptRegistry,
153153
content_allow_list: ContentAllowList,
154-
):
154+
) -> None:
155155
response = fetch_url(f"{base_url}/v1/traceloop/prompts", api_key)
156156
prompt_registry.load(response)
157157

158158
response = fetch_url(f"{base_url}/v1/traceloop/pii/tracing-allow-list", api_key)
159159
content_allow_list.load(response)
160160

161161

162-
def monitor_exit(exit_event: Event):
162+
def monitor_exit(exit_event: Event) -> None:
163163
main_thread = threading.main_thread()
164164
main_thread.join()
165165
exit_event.set()

packages/traceloop-sdk/traceloop/sdk/images/image_uploader.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@
66

77

88
class ImageUploader:
9-
def __init__(self, base_url, api_key):
9+
def __init__(self, base_url: str, api_key: str) -> None:
1010
self.base_url = base_url
1111
self.api_key = api_key
1212
self.logger = logging.getLogger(__name__)
1313

14-
def upload_base64_image(self, trace_id, span_id, image_name, image_file):
15-
asyncio.run(self.aupload_image_file(trace_id, span_id, image_name, image_file))
14+
def upload_base64_image(
15+
self, trace_id: str, span_id: str, image_name: str, image_file: str
16+
) -> None:
17+
asyncio.run(self.aupload_base64_image(trace_id, span_id, image_name, image_file))
1618

17-
async def aupload_base64_image(self, trace_id, span_id, image_name, image_file):
19+
async def aupload_base64_image(
20+
self, trace_id: str, span_id: str, image_name: str, image_file: str
21+
) -> str:
1822
url = self._get_image_url(trace_id, span_id, image_name)
1923

2024
await self._async_upload(url, image_file)
2125

2226
return url
2327

24-
def _get_image_url(self, trace_id, span_id, image_name):
28+
def _get_image_url(self, trace_id: str, span_id: str, image_name: str) -> str:
2529
response = requests.post(
2630
f"{self.base_url}/v2/traces/{trace_id}/spans/{span_id}/images",
2731
json={
@@ -33,9 +37,9 @@ def _get_image_url(self, trace_id, span_id, image_name):
3337
},
3438
)
3539

36-
return response.json()["url"]
40+
return response.json()["url"] # type: ignore[no-any-return]
3741

38-
async def _async_upload(self, url, base64_image):
42+
async def _async_upload(self, url: str, base64_image: str) -> None:
3943
headers = {
4044
"Authorization": f"Bearer {self.api_key}",
4145
"Content-Type": "application/json",

packages/traceloop-sdk/traceloop/sdk/logging/logging.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Dict
2+
from typing import Dict, Optional, Any
33

44
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
55
OTLPLogExporter as GRPCExporter,
@@ -15,13 +15,13 @@
1515

1616

1717
class LoggerWrapper(object):
18-
resource_attributes: dict = {}
19-
endpoint: str = None
18+
resource_attributes: Dict[Any, Any] = {}
19+
endpoint: Optional[str] = None
2020
headers: Dict[str, str] = {}
21-
__logging_exporter: LogExporter = None
22-
__logging_provider: LoggerProvider = None
21+
__logging_exporter: Optional[LogExporter] = None
22+
__logging_provider: Optional[LoggerProvider] = None
2323

24-
def __new__(cls, exporter: LogExporter = None) -> "LoggerWrapper":
24+
def __new__(cls, exporter: Optional[LogExporter] = None) -> "LoggerWrapper":
2525
if not hasattr(cls, "instance"):
2626
obj = cls.instance = super(LoggerWrapper, cls).__new__(cls)
2727
if not LoggerWrapper.endpoint:
@@ -58,7 +58,7 @@ def init_logging_exporter(endpoint: str, headers: Dict[str, str]) -> LogExporter
5858

5959

6060
def init_logging_provider(
61-
exporter: LogExporter, resource_attributes: dict = None
61+
exporter: LogExporter, resource_attributes: Optional[Dict[Any, Any]] = None
6262
) -> LoggerProvider:
6363
resource = (
6464
Resource.create(resource_attributes)

0 commit comments

Comments
 (0)