Skip to content

Commit bc73a63

Browse files
committed
Add issues API
1 parent 220849f commit bc73a63

10 files changed

+759
-0
lines changed

graphsignal/client/models/issue.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# coding: utf-8
2+
3+
"""
4+
Graphsignal API
5+
6+
API for uploading and querying spans, issues, metrics, and logs.
7+
8+
The version of the OpenAPI document: 1.0.0
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from graphsignal.client.models.tag import Tag
23+
from typing import Optional, Set
24+
from typing_extensions import Self
25+
26+
class Issue(BaseModel):
27+
"""
28+
Issue
29+
""" # noqa: E501
30+
issue_id: Optional[StrictStr] = Field(default=None, description="Unique identifier for the issue.")
31+
span_id: Optional[StrictStr] = Field(default=None, description="The associated span identifier, if the issue is being associated with a span.")
32+
tags: Optional[List[Tag]] = Field(default=None, description="Tags associated with the issue.")
33+
name: StrictStr = Field(description="The name of the issue.")
34+
severity: Optional[StrictInt] = Field(default=None, description="Severity of the issue, 1-5 (info, low, medium, high, critical).")
35+
description: Optional[StrictStr] = Field(default=None, description="The description of the issue.")
36+
create_ts: StrictInt = Field(description="Unix timestamp (seconds) when the issue was created.")
37+
__properties: ClassVar[List[str]] = ["issue_id", "span_id", "tags", "name", "severity", "description", "create_ts"]
38+
39+
model_config = ConfigDict(
40+
populate_by_name=True,
41+
validate_assignment=True,
42+
protected_namespaces=(),
43+
)
44+
45+
46+
def to_str(self) -> str:
47+
"""Returns the string representation of the model using alias"""
48+
return pprint.pformat(self.model_dump(by_alias=True))
49+
50+
def to_json(self) -> str:
51+
"""Returns the JSON representation of the model using alias"""
52+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
53+
return json.dumps(self.to_dict())
54+
55+
@classmethod
56+
def from_json(cls, json_str: str) -> Optional[Self]:
57+
"""Create an instance of Issue from a JSON string"""
58+
return cls.from_dict(json.loads(json_str))
59+
60+
def to_dict(self) -> Dict[str, Any]:
61+
"""Return the dictionary representation of the model using alias.
62+
63+
This has the following differences from calling pydantic's
64+
`self.model_dump(by_alias=True)`:
65+
66+
* `None` is only added to the output dict for nullable fields that
67+
were set at model initialization. Other fields with value `None`
68+
are ignored.
69+
"""
70+
excluded_fields: Set[str] = set([
71+
])
72+
73+
_dict = self.model_dump(
74+
by_alias=True,
75+
exclude=excluded_fields,
76+
exclude_none=True,
77+
)
78+
# override the default output from pydantic by calling `to_dict()` of each item in tags (list)
79+
_items = []
80+
if self.tags:
81+
for _item_tags in self.tags:
82+
if _item_tags:
83+
_items.append(_item_tags.to_dict())
84+
_dict['tags'] = _items
85+
return _dict
86+
87+
@classmethod
88+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
89+
"""Create an instance of Issue from a dict"""
90+
if obj is None:
91+
return None
92+
93+
if not isinstance(obj, dict):
94+
return cls.model_validate(obj)
95+
96+
_obj = cls.model_validate({
97+
"issue_id": obj.get("issue_id"),
98+
"span_id": obj.get("span_id"),
99+
"tags": [Tag.from_dict(_item) for _item in obj["tags"]] if obj.get("tags") is not None else None,
100+
"name": obj.get("name"),
101+
"severity": obj.get("severity"),
102+
"description": obj.get("description"),
103+
"create_ts": obj.get("create_ts")
104+
})
105+
return _obj
106+
107+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# coding: utf-8
2+
3+
"""
4+
Graphsignal API
5+
6+
API for uploading and querying spans, issues, metrics, and logs.
7+
8+
The version of the OpenAPI document: 1.0.0
9+
Generated by OpenAPI Generator (https://openapi-generator.tech)
10+
11+
Do not edit the class manually.
12+
""" # noqa: E501
13+
14+
15+
from __future__ import annotations
16+
import pprint
17+
import re # noqa: F401
18+
import json
19+
20+
from pydantic import BaseModel, ConfigDict, Field
21+
from typing import Any, ClassVar, Dict, List, Optional
22+
from graphsignal.client.models.issue import Issue
23+
from typing import Optional, Set
24+
from typing_extensions import Self
25+
26+
class IssueQueryResult(BaseModel):
27+
"""
28+
IssueQueryResult
29+
""" # noqa: E501
30+
data: Optional[List[Issue]] = Field(default=None, description="List of issues resulting from the query.")
31+
__properties: ClassVar[List[str]] = ["data"]
32+
33+
model_config = ConfigDict(
34+
populate_by_name=True,
35+
validate_assignment=True,
36+
protected_namespaces=(),
37+
)
38+
39+
40+
def to_str(self) -> str:
41+
"""Returns the string representation of the model using alias"""
42+
return pprint.pformat(self.model_dump(by_alias=True))
43+
44+
def to_json(self) -> str:
45+
"""Returns the JSON representation of the model using alias"""
46+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
47+
return json.dumps(self.to_dict())
48+
49+
@classmethod
50+
def from_json(cls, json_str: str) -> Optional[Self]:
51+
"""Create an instance of IssueQueryResult from a JSON string"""
52+
return cls.from_dict(json.loads(json_str))
53+
54+
def to_dict(self) -> Dict[str, Any]:
55+
"""Return the dictionary representation of the model using alias.
56+
57+
This has the following differences from calling pydantic's
58+
`self.model_dump(by_alias=True)`:
59+
60+
* `None` is only added to the output dict for nullable fields that
61+
were set at model initialization. Other fields with value `None`
62+
are ignored.
63+
"""
64+
excluded_fields: Set[str] = set([
65+
])
66+
67+
_dict = self.model_dump(
68+
by_alias=True,
69+
exclude=excluded_fields,
70+
exclude_none=True,
71+
)
72+
# override the default output from pydantic by calling `to_dict()` of each item in data (list)
73+
_items = []
74+
if self.data:
75+
for _item_data in self.data:
76+
if _item_data:
77+
_items.append(_item_data.to_dict())
78+
_dict['data'] = _items
79+
return _dict
80+
81+
@classmethod
82+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
83+
"""Create an instance of IssueQueryResult from a dict"""
84+
if obj is None:
85+
return None
86+
87+
if not isinstance(obj, dict):
88+
return cls.model_validate(obj)
89+
90+
_obj = cls.model_validate({
91+
"data": [Issue.from_dict(_item) for _item in obj["data"]] if obj.get("data") is not None else None
92+
})
93+
return _obj
94+
95+

graphsignal/env_vars.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from typing import Dict, Any, Union, Optional, Type
2+
import os
3+
4+
5+
def parse_env_param(name: str, value: Any, expected_type: Type) -> Any:
6+
if value is None:
7+
return None
8+
9+
try:
10+
if expected_type == bool:
11+
return value if isinstance(value, bool) else str(value).lower() in ("true", "1", "yes")
12+
elif expected_type == int:
13+
return int(value)
14+
elif expected_type == float:
15+
return float(value)
16+
elif expected_type == str:
17+
return str(value)
18+
elif expected_type == list:
19+
return [item.strip() for item in str(value).split(',') if item.strip()]
20+
except (ValueError, TypeError):
21+
pass
22+
23+
raise ValueError(f"Invalid type for {name}: expected {expected_type.__name__}, got {type(value).__name__}")
24+
25+
26+
def read_config_param(name: str, expected_type: Type, provided_value: Optional[Any] = None, default_value: Optional[Any] = None, required: bool = False) -> Any:
27+
# Check if the value was provided as an argument
28+
if provided_value is not None:
29+
return provided_value
30+
31+
# Check if the value was provided as an environment variable
32+
env_value = os.getenv(f'GRAPHSIGNAL_{name.upper()}')
33+
if env_value is not None:
34+
parsed_env_value = parse_env_param(name, env_value, expected_type)
35+
if parsed_env_value is not None:
36+
return parsed_env_value
37+
38+
if required:
39+
raise ValueError(f"Missing required argument: {name}")
40+
41+
return default_value
42+
43+
44+
def read_config_tags(provided_value: Optional[dict] = None, prefix: str = "GRAPHSIGNAL_TAG_") -> Dict[str, str]:
45+
# Check if the value was provided as an argument
46+
if provided_value is not None:
47+
return provided_value
48+
49+
# Check if the value was provided as an environment variable
50+
return {key[len(prefix):].lower(): value for key, value in os.environ.items() if key.startswith(prefix)}

graphsignal/profiles.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from typing import Union, Any, Optional, Dict
2+
import logging
3+
import json
4+
5+
logger = logging.getLogger('graphsignal')
6+
7+
8+
class EventAverages():
9+
__slots__ = [
10+
'events'
11+
]
12+
13+
def __init__(self):
14+
self.events = {}
15+
16+
def is_empty(self) -> bool:
17+
return len(self.events) == 0
18+
19+
def inc_counters(self, event_name, stats):
20+
if event_name not in self.events:
21+
event_counters = self.events[event_name] = dict()
22+
else:
23+
event_counters = self.events[event_name]
24+
25+
for key, value in stats.items():
26+
if key not in event_counters:
27+
event_counters[key] = value
28+
else:
29+
event_counters[key] += value
30+
31+
if 'count' not in stats:
32+
if 'count' not in event_counters:
33+
event_counters['count'] = 1
34+
else:
35+
event_counters['count'] += 1
36+
37+
def dumps(self, max_events: int = 250, limit_by: str = 'count') -> str:
38+
# sort by limit_by key of event counters in descending order and return first max_events
39+
sorted_events = sorted(self.events.items(), key=lambda x: x[1].get(limit_by, 0), reverse=True)
40+
limited_events = dict(sorted_events[:max_events])
41+
42+
return json.dumps(limited_events)
43+
44+
def clear(self):
45+
self.events.clear()

0 commit comments

Comments
 (0)