codemie-sdk-python 0.1.704__py3-none-any.whl → 0.1.706__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codemie_sdk/__init__.py +18 -0
- codemie_sdk/client/client.py +8 -0
- codemie_sdk/models/servicenow_integration.py +103 -0
- codemie_sdk/models/usage_attribution.py +124 -0
- codemie_sdk/services/servicenow_integration.py +90 -0
- codemie_sdk/services/usage_attribution.py +324 -0
- {codemie_sdk_python-0.1.704.dist-info → codemie_sdk_python-0.1.706.dist-info}/METADATA +1 -1
- {codemie_sdk_python-0.1.704.dist-info → codemie_sdk_python-0.1.706.dist-info}/RECORD +9 -5
- {codemie_sdk_python-0.1.704.dist-info → codemie_sdk_python-0.1.706.dist-info}/WHEEL +0 -0
codemie_sdk/__init__.py
CHANGED
|
@@ -175,6 +175,16 @@ from .models.a2a import (
|
|
|
175
175
|
AgentSkill,
|
|
176
176
|
)
|
|
177
177
|
from .services.a2a import A2AService
|
|
178
|
+
from .models.usage_attribution import (
|
|
179
|
+
UsageAttributionQueryParams,
|
|
180
|
+
UsageAttributionRange,
|
|
181
|
+
UsageAttributionAnalysisSettings,
|
|
182
|
+
UsageAttributionAnalysis,
|
|
183
|
+
UsageAttributionResponse,
|
|
184
|
+
UsageAttributionTableResponse,
|
|
185
|
+
UsageAttributionSettingsResponse,
|
|
186
|
+
)
|
|
187
|
+
from .services.usage_attribution import UsageAttributionService
|
|
178
188
|
|
|
179
189
|
try:
|
|
180
190
|
__version__ = _pkg_version("codemie-sdk-python")
|
|
@@ -311,4 +321,12 @@ __all__ = [
|
|
|
311
321
|
"AgentCapabilities",
|
|
312
322
|
"AgentSkill",
|
|
313
323
|
"A2AService",
|
|
324
|
+
"UsageAttributionQueryParams",
|
|
325
|
+
"UsageAttributionRange",
|
|
326
|
+
"UsageAttributionAnalysisSettings",
|
|
327
|
+
"UsageAttributionAnalysis",
|
|
328
|
+
"UsageAttributionResponse",
|
|
329
|
+
"UsageAttributionTableResponse",
|
|
330
|
+
"UsageAttributionSettingsResponse",
|
|
331
|
+
"UsageAttributionService",
|
|
314
332
|
]
|
codemie_sdk/client/client.py
CHANGED
|
@@ -33,6 +33,8 @@ from ..services.mcp_configs import MCPConfigsService
|
|
|
33
33
|
from ..services.cost_center import CostCenterService
|
|
34
34
|
from ..services.customer_config_service import CustomerConfigService
|
|
35
35
|
from ..services.project_budget_groups import ProjectBudgetGroupsService
|
|
36
|
+
from ..services.usage_attribution import UsageAttributionService
|
|
37
|
+
from ..services.servicenow_integration import ServiceNowIntegrationService
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
class CodeMieClient:
|
|
@@ -183,12 +185,18 @@ class CodeMieClient:
|
|
|
183
185
|
self.project_budget_groups = ProjectBudgetGroupsService(
|
|
184
186
|
self._api_domain, _token_source, verify_ssl=self._verify_ssl
|
|
185
187
|
)
|
|
188
|
+
self.servicenow_integration = ServiceNowIntegrationService(
|
|
189
|
+
self._api_domain, _token_source, verify_ssl=self._verify_ssl
|
|
190
|
+
)
|
|
186
191
|
self.a2a = A2AService(
|
|
187
192
|
self._api_domain, _token_source, verify_ssl=self._verify_ssl
|
|
188
193
|
)
|
|
189
194
|
self.workspaces = WorkspaceService(
|
|
190
195
|
self._api_domain, _token_source, verify_ssl=self._verify_ssl
|
|
191
196
|
)
|
|
197
|
+
self.usage_attribution = UsageAttributionService(
|
|
198
|
+
self._api_domain, _token_source, verify_ssl=self._verify_ssl
|
|
199
|
+
)
|
|
192
200
|
|
|
193
201
|
@property
|
|
194
202
|
def token(self) -> str:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Copyright 2026 EPAM Systems, Inc. ("EPAM")
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""ServiceNow integration models for the CodeMie SDK.
|
|
16
|
+
|
|
17
|
+
The integration endpoints are callable only by the ServiceNow integration service account.
|
|
18
|
+
The request model is deliberately permissive: the server owns the validation rules, so the
|
|
19
|
+
SDK sends what the caller gives it and surfaces the server's verdict as an HTTP error.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from enum import Enum
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ServiceNowBudgetAction(str, Enum):
|
|
28
|
+
"""Budget action requested by ServiceNow."""
|
|
29
|
+
|
|
30
|
+
SET = "set"
|
|
31
|
+
ADJUST = "adjust"
|
|
32
|
+
CANCEL = "cancel"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ServiceNowProject(BaseModel):
|
|
36
|
+
"""A shared project returned by the project lookup."""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(extra="ignore")
|
|
39
|
+
|
|
40
|
+
project_name: str
|
|
41
|
+
display_name: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ServiceNowBudgetRequest(BaseModel):
|
|
45
|
+
"""Request body for applying a ServiceNow budget request to a project.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
action: ``set`` replaces the budget, ``adjust`` merges into it, ``cancel`` deletes it.
|
|
49
|
+
request_id: ServiceNow request number, used for audit logs and new budget names.
|
|
50
|
+
epam_project_code: EPAM project code; names the cost center when it differs from
|
|
51
|
+
the CodeMie project name.
|
|
52
|
+
codemie_project_name: CodeMie project to manage; the server defaults it to
|
|
53
|
+
``epam_project_code`` when omitted.
|
|
54
|
+
admin_emails: People to make project admins (ignored by ``cancel``).
|
|
55
|
+
amounts: Monthly budget in USD per category (``platform``, ``cli``,
|
|
56
|
+
``premium_models``); required for ``set`` and ``adjust``.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
# Non-finite amounts are sent as the JSON constants NaN / Infinity instead of null,
|
|
60
|
+
# so the server, not the client, rejects them.
|
|
61
|
+
model_config = ConfigDict(ser_json_inf_nan="constants")
|
|
62
|
+
|
|
63
|
+
action: ServiceNowBudgetAction
|
|
64
|
+
request_id: str
|
|
65
|
+
epam_project_code: str
|
|
66
|
+
codemie_project_name: str | None = None
|
|
67
|
+
admin_emails: list[str] = Field(default_factory=list)
|
|
68
|
+
amounts: dict[str, float] | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ServiceNowBudgetCategory(BaseModel):
|
|
72
|
+
"""Budget of one spend category in a budget response."""
|
|
73
|
+
|
|
74
|
+
model_config = ConfigDict(extra="ignore")
|
|
75
|
+
|
|
76
|
+
category: str
|
|
77
|
+
max_budget: float
|
|
78
|
+
soft_budget: float
|
|
79
|
+
budget_reset_at: str | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ServiceNowBudgetGroup(BaseModel):
|
|
83
|
+
"""Budget of a project after the request, with its categories in a fixed order."""
|
|
84
|
+
|
|
85
|
+
model_config = ConfigDict(extra="ignore")
|
|
86
|
+
|
|
87
|
+
total_amount: float
|
|
88
|
+
budget_duration: str
|
|
89
|
+
categories: list[ServiceNowBudgetCategory]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ServiceNowBudgetResponse(BaseModel):
|
|
93
|
+
"""Outcome of a budget request."""
|
|
94
|
+
|
|
95
|
+
model_config = ConfigDict(extra="ignore")
|
|
96
|
+
|
|
97
|
+
project_name: str
|
|
98
|
+
cost_center: str | None = None
|
|
99
|
+
created_project: bool
|
|
100
|
+
created_cost_center: bool
|
|
101
|
+
budget: ServiceNowBudgetGroup | None = None
|
|
102
|
+
admins_assigned: list[str] = Field(default_factory=list)
|
|
103
|
+
admins_pending: list[str] = Field(default_factory=list)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright 2026 EPAM Systems, Inc. ("EPAM")
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Usage Attribution analytics models for the CodeMie SDK."""
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
20
|
+
|
|
21
|
+
from .analytics import PaginationMetadata
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UsageAttributionQueryParams(BaseModel):
|
|
25
|
+
"""Period parameters shared by the Usage Attribution read endpoints.
|
|
26
|
+
|
|
27
|
+
The endpoints take no project or user filter: they always attribute all usage.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(extra="ignore")
|
|
31
|
+
|
|
32
|
+
time_period: str | None = Field(
|
|
33
|
+
None,
|
|
34
|
+
description='Time period: "last_hour", "last_6_hours", "last_24_hours", "last_7_days", "last_30_days", "last_60_days", "last_year"',
|
|
35
|
+
)
|
|
36
|
+
start_date: str | None = Field(
|
|
37
|
+
None, description="ISO 8601 start date (use with end_date)"
|
|
38
|
+
)
|
|
39
|
+
end_date: str | None = Field(
|
|
40
|
+
None, description="ISO 8601 end date (use with start_date)"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UsageAttributionRange(BaseModel):
|
|
45
|
+
"""The effective range of a response; ranges longer than the maximum are clamped."""
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
|
48
|
+
|
|
49
|
+
from_: str = Field(alias="from", description="Effective start")
|
|
50
|
+
to: str = Field(description="Effective end")
|
|
51
|
+
days: float = Field(description="Length of the effective range in days")
|
|
52
|
+
clamped: bool = Field(description="The requested range was cut to the maximum")
|
|
53
|
+
requested_from: str | None = Field(None, description="Requested start")
|
|
54
|
+
requested_to: str | None = Field(None, description="Requested end")
|
|
55
|
+
evidence_from: str | None = Field(
|
|
56
|
+
None, description="Start of the evidence window used for categories"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class UsageAttributionAnalysisSettings(BaseModel):
|
|
61
|
+
"""The analysis settings behind a response."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(extra="ignore")
|
|
64
|
+
|
|
65
|
+
hash: str | None = None
|
|
66
|
+
defaults_hash: str | None = None
|
|
67
|
+
schema_version: int | None = None
|
|
68
|
+
overridden: list[str] = Field(default_factory=list)
|
|
69
|
+
tier: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class UsageAttributionAnalysis(BaseModel):
|
|
73
|
+
"""When and how the cached analysis behind a response was computed."""
|
|
74
|
+
|
|
75
|
+
model_config = ConfigDict(extra="ignore")
|
|
76
|
+
|
|
77
|
+
computed_at: str | None = None
|
|
78
|
+
duration_ms: float | None = None
|
|
79
|
+
cache: str = Field(description="hit, miss, shared or stale")
|
|
80
|
+
stale: bool = False
|
|
81
|
+
refreshing: bool = Field(False, description="A newer analysis is being computed")
|
|
82
|
+
error: str | None = None
|
|
83
|
+
ttl_s: int | None = None
|
|
84
|
+
window: dict[str, Any] | None = None
|
|
85
|
+
evidence_window: dict[str, Any] | None = None
|
|
86
|
+
coverage: dict[str, Any] | None = None
|
|
87
|
+
unclassified_reasons: dict[str, Any] | None = None
|
|
88
|
+
allocated: float | None = None
|
|
89
|
+
timings: dict[str, Any] = Field(default_factory=dict)
|
|
90
|
+
settings: UsageAttributionAnalysisSettings | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class UsageAttributionResponse(BaseModel):
|
|
94
|
+
"""A composite view: totals, tiles, charts, a popup or How It Works."""
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(extra="ignore")
|
|
97
|
+
|
|
98
|
+
data: dict[str, Any]
|
|
99
|
+
range: UsageAttributionRange | None = None
|
|
100
|
+
analysis: UsageAttributionAnalysis | None = Field(
|
|
101
|
+
None, description="Absent for the fast totals, which skip the analysis"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class UsageAttributionTableResponse(BaseModel):
|
|
106
|
+
"""A table page: rows of the page, totals over every matching row and filter counts."""
|
|
107
|
+
|
|
108
|
+
model_config = ConfigDict(extra="ignore")
|
|
109
|
+
|
|
110
|
+
data: dict[str, Any] = Field(description="rows and totals")
|
|
111
|
+
pagination: PaginationMetadata
|
|
112
|
+
counts: dict[str, int] = Field(default_factory=dict)
|
|
113
|
+
threshold: float | None = None
|
|
114
|
+
display: dict[str, float] | None = None
|
|
115
|
+
range: UsageAttributionRange
|
|
116
|
+
analysis: UsageAttributionAnalysis
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class UsageAttributionSettingsResponse(BaseModel):
|
|
120
|
+
"""The tunable settings schema, the defaults and whether the caller may tune."""
|
|
121
|
+
|
|
122
|
+
model_config = ConfigDict(extra="ignore")
|
|
123
|
+
|
|
124
|
+
data: dict[str, Any]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2026 EPAM Systems, Inc. ("EPAM")
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""ServiceNow integration service for the CodeMie SDK."""
|
|
16
|
+
|
|
17
|
+
from ..models.servicenow_integration import (
|
|
18
|
+
ServiceNowBudgetRequest,
|
|
19
|
+
ServiceNowBudgetResponse,
|
|
20
|
+
ServiceNowProject,
|
|
21
|
+
)
|
|
22
|
+
from ..utils.http import ApiRequestHandler, TokenSource
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ServiceNowIntegrationService:
|
|
26
|
+
"""Service for the ServiceNow integration endpoints.
|
|
27
|
+
|
|
28
|
+
Only the ServiceNow integration service account may call these endpoints; any other
|
|
29
|
+
caller is rejected with HTTP 403.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, api_domain: str, token: TokenSource, verify_ssl: bool = True):
|
|
33
|
+
"""Initialize the ServiceNowIntegrationService.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_domain: Base URL for the CodeMie API
|
|
37
|
+
token: Authentication token
|
|
38
|
+
verify_ssl: Whether to verify SSL certificates
|
|
39
|
+
"""
|
|
40
|
+
self._api = ApiRequestHandler(api_domain, token, verify_ssl)
|
|
41
|
+
|
|
42
|
+
def list_projects(self, emails: list[str]) -> list[ServiceNowProject]:
|
|
43
|
+
"""List the shared projects that any of the given users belong to.
|
|
44
|
+
|
|
45
|
+
The result is the distinct union across all emails, ordered by project name.
|
|
46
|
+
Emails with no active CodeMie user are skipped, and an empty list is returned
|
|
47
|
+
when none match. The emails are sent as given; the server rejects an empty
|
|
48
|
+
list or an invalid email with HTTP 422.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
emails: Requester and approver emails
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of ServiceNowProject with the project name and display name
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
HTTPError: If the request fails
|
|
58
|
+
"""
|
|
59
|
+
return self._api.get(
|
|
60
|
+
"/v1/integrations/servicenow/projects",
|
|
61
|
+
response_model=list[ServiceNowProject],
|
|
62
|
+
params={"emails": emails},
|
|
63
|
+
wrap_response=False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def submit_budget_request(
|
|
67
|
+
self, request: ServiceNowBudgetRequest
|
|
68
|
+
) -> ServiceNowBudgetResponse:
|
|
69
|
+
"""Apply a ServiceNow budget request to a project.
|
|
70
|
+
|
|
71
|
+
``set`` replaces the project budget, ``adjust`` merges into it and ``cancel``
|
|
72
|
+
deletes it. The project (and its cost center) is created when it does not exist,
|
|
73
|
+
and every resolvable ``admin_emails`` user becomes a project admin.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
request: The budget request
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
ServiceNowBudgetResponse with the resolved project, cost center, the budget
|
|
80
|
+
after the request (None after cancel) and the admin assignment outcome
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
HTTPError: If the request is rejected (400, 403, 404, 409 or 422)
|
|
84
|
+
"""
|
|
85
|
+
return self._api.post(
|
|
86
|
+
"/v1/integrations/servicenow/budgets",
|
|
87
|
+
response_model=ServiceNowBudgetResponse,
|
|
88
|
+
data=request.model_dump_json(exclude_none=True),
|
|
89
|
+
wrap_response=False,
|
|
90
|
+
)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Copyright 2026 EPAM Systems, Inc. ("EPAM")
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Usage Attribution analytics service for the CodeMie SDK.
|
|
16
|
+
|
|
17
|
+
Wraps the read endpoints under ``/v1/analytics/usage-attribution``, available to
|
|
18
|
+
admins, maintainers and auditors when the ``features:usageAttribution`` flag is on.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any
|
|
22
|
+
from urllib.parse import quote
|
|
23
|
+
|
|
24
|
+
from ..models.usage_attribution import (
|
|
25
|
+
UsageAttributionQueryParams,
|
|
26
|
+
UsageAttributionResponse,
|
|
27
|
+
UsageAttributionSettingsResponse,
|
|
28
|
+
UsageAttributionTableResponse,
|
|
29
|
+
)
|
|
30
|
+
from ..utils import ApiRequestHandler, TokenSource
|
|
31
|
+
|
|
32
|
+
_BASE = "/v1/analytics/usage-attribution"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class UsageAttributionService:
|
|
36
|
+
"""Service for reading Usage Attribution analytics: who pays for Web Platform and CLI usage."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, api_domain: str, token: TokenSource, verify_ssl: bool = True):
|
|
39
|
+
"""Initialize the usage attribution service.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
api_domain: Base URL for the CodeMie API
|
|
43
|
+
token: Authentication token
|
|
44
|
+
verify_ssl: Whether to verify SSL certificates
|
|
45
|
+
"""
|
|
46
|
+
self._api = ApiRequestHandler(api_domain, token, verify_ssl)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def _query(
|
|
50
|
+
params: UsageAttributionQueryParams | None, **extra: Any
|
|
51
|
+
) -> dict[str, Any]:
|
|
52
|
+
"""Period parameters plus the extra ones that were given (None means the server default)."""
|
|
53
|
+
query = params.model_dump(exclude_none=True) if params else {}
|
|
54
|
+
query.update({key: value for key, value in extra.items() if value is not None})
|
|
55
|
+
return query
|
|
56
|
+
|
|
57
|
+
def _get_view(
|
|
58
|
+
self,
|
|
59
|
+
path: str,
|
|
60
|
+
params: UsageAttributionQueryParams | None = None,
|
|
61
|
+
**extra: Any,
|
|
62
|
+
) -> UsageAttributionResponse:
|
|
63
|
+
return self._api.get(
|
|
64
|
+
f"{_BASE}{path}",
|
|
65
|
+
UsageAttributionResponse,
|
|
66
|
+
params=self._query(params, **extra),
|
|
67
|
+
wrap_response=False,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _get_table(
|
|
71
|
+
self,
|
|
72
|
+
path: str,
|
|
73
|
+
params: UsageAttributionQueryParams | None = None,
|
|
74
|
+
**extra: Any,
|
|
75
|
+
) -> UsageAttributionTableResponse:
|
|
76
|
+
return self._api.get(
|
|
77
|
+
f"{_BASE}{path}",
|
|
78
|
+
UsageAttributionTableResponse,
|
|
79
|
+
params=self._query(params, **extra),
|
|
80
|
+
wrap_response=False,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def get_totals(
|
|
84
|
+
self, params: UsageAttributionQueryParams | None = None
|
|
85
|
+
) -> UsageAttributionResponse:
|
|
86
|
+
"""Get the fast totals: Total, Web Platform, CLI, Charged Back and Who Pays.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
params: Period parameters
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
UsageAttributionResponse whose data holds the amounts. The analysis
|
|
93
|
+
block is absent for this view.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
requests.HTTPError: 403 without an allowed role, 404 when the feature is off
|
|
97
|
+
"""
|
|
98
|
+
return self._get_view("/totals", params)
|
|
99
|
+
|
|
100
|
+
def get_status(
|
|
101
|
+
self, params: UsageAttributionQueryParams | None = None
|
|
102
|
+
) -> UsageAttributionResponse:
|
|
103
|
+
"""Get How It Works: coverage, data quality, run details and the settings in effect.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
params: Period parameters
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
UsageAttributionResponse with the analysis block
|
|
110
|
+
"""
|
|
111
|
+
return self._get_view("/status", params)
|
|
112
|
+
|
|
113
|
+
def get_cli_summary(
|
|
114
|
+
self, params: UsageAttributionQueryParams | None = None
|
|
115
|
+
) -> UsageAttributionResponse:
|
|
116
|
+
"""Get the CLI categories: tiles, Who Pays and CLI spend by category.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
params: Period parameters
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
UsageAttributionResponse with tiles, who_pays, categories and the analysis block
|
|
123
|
+
"""
|
|
124
|
+
return self._get_view("/cli-summary", params)
|
|
125
|
+
|
|
126
|
+
def get_settings(self) -> UsageAttributionSettingsResponse:
|
|
127
|
+
"""Get the analysis settings: defaults, tunable schema and what the caller may do.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
UsageAttributionSettingsResponse, whose data carries ``can_tune`` and ``can_refresh``
|
|
131
|
+
"""
|
|
132
|
+
return self._api.get(
|
|
133
|
+
f"{_BASE}/settings",
|
|
134
|
+
UsageAttributionSettingsResponse,
|
|
135
|
+
wrap_response=False,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def get_shared_projects(
|
|
139
|
+
self,
|
|
140
|
+
params: UsageAttributionQueryParams | None = None,
|
|
141
|
+
scope: str | None = None,
|
|
142
|
+
search: str | None = None,
|
|
143
|
+
page: int | None = None,
|
|
144
|
+
per_page: int | None = None,
|
|
145
|
+
sort_by: str | None = None,
|
|
146
|
+
sort_order: str | None = None,
|
|
147
|
+
) -> UsageAttributionTableResponse:
|
|
148
|
+
"""List shared projects without a budget.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
params: Period parameters
|
|
152
|
+
scope: "threshold" (default) or "all"
|
|
153
|
+
search: Case-insensitive text to find in the project name
|
|
154
|
+
page: Zero-indexed page number
|
|
155
|
+
per_page: Items per page
|
|
156
|
+
sort_by: Column to sort by
|
|
157
|
+
sort_order: "asc" or "desc"
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
UsageAttributionTableResponse with the rows of the page and the filter counts
|
|
161
|
+
"""
|
|
162
|
+
return self._get_table(
|
|
163
|
+
"/shared-projects",
|
|
164
|
+
params,
|
|
165
|
+
scope=scope,
|
|
166
|
+
search=search,
|
|
167
|
+
page=page,
|
|
168
|
+
per_page=per_page,
|
|
169
|
+
sort_by=sort_by,
|
|
170
|
+
sort_order=sort_order,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def get_shared_project(
|
|
174
|
+
self,
|
|
175
|
+
project_name: str,
|
|
176
|
+
params: UsageAttributionQueryParams | None = None,
|
|
177
|
+
) -> UsageAttributionResponse:
|
|
178
|
+
"""Get the popup of one shared project.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
project_name: Name of the project
|
|
182
|
+
params: Period parameters
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
UsageAttributionResponse whose data holds ``row`` and ``detail``
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
requests.HTTPError: 404 when the project is not in the analysis of the period
|
|
189
|
+
"""
|
|
190
|
+
return self._get_view(
|
|
191
|
+
f"/shared-projects/{quote(project_name, safe='')}", params
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def get_repository_groups(
|
|
195
|
+
self,
|
|
196
|
+
params: UsageAttributionQueryParams | None = None,
|
|
197
|
+
scope: str | None = None,
|
|
198
|
+
search: str | None = None,
|
|
199
|
+
page: int | None = None,
|
|
200
|
+
per_page: int | None = None,
|
|
201
|
+
sort_by: str | None = None,
|
|
202
|
+
sort_order: str | None = None,
|
|
203
|
+
) -> UsageAttributionTableResponse:
|
|
204
|
+
"""List repository groups in personal projects.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
params: Period parameters
|
|
208
|
+
scope: "threshold" (default) or "all"
|
|
209
|
+
search: Case-insensitive text to find in the group name or its repositories
|
|
210
|
+
page: Zero-indexed page number
|
|
211
|
+
per_page: Items per page
|
|
212
|
+
sort_by: Column to sort by
|
|
213
|
+
sort_order: "asc" or "desc"
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
UsageAttributionTableResponse with the rows of the page and the filter counts
|
|
217
|
+
"""
|
|
218
|
+
return self._get_table(
|
|
219
|
+
"/repository-groups",
|
|
220
|
+
params,
|
|
221
|
+
scope=scope,
|
|
222
|
+
search=search,
|
|
223
|
+
page=page,
|
|
224
|
+
per_page=per_page,
|
|
225
|
+
sort_by=sort_by,
|
|
226
|
+
sort_order=sort_order,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def get_repository_group(
|
|
230
|
+
self,
|
|
231
|
+
group_id: str,
|
|
232
|
+
params: UsageAttributionQueryParams | None = None,
|
|
233
|
+
) -> UsageAttributionResponse:
|
|
234
|
+
"""Get the popup of one repository group.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
group_id: Group id as listed by get_repository_groups
|
|
238
|
+
params: Period parameters
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
UsageAttributionResponse whose data holds ``row`` and ``detail``
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
requests.HTTPError: 404 when the group is not in the analysis of the period
|
|
245
|
+
"""
|
|
246
|
+
return self._get_view(f"/repository-groups/{quote(group_id, safe='')}", params)
|
|
247
|
+
|
|
248
|
+
def get_people(
|
|
249
|
+
self,
|
|
250
|
+
params: UsageAttributionQueryParams | None = None,
|
|
251
|
+
filter: str | None = None,
|
|
252
|
+
search: str | None = None,
|
|
253
|
+
page: int | None = None,
|
|
254
|
+
per_page: int | None = None,
|
|
255
|
+
sort_by: str | None = None,
|
|
256
|
+
sort_order: str | None = None,
|
|
257
|
+
) -> UsageAttributionTableResponse:
|
|
258
|
+
"""List people with their spend.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
params: Period parameters
|
|
262
|
+
filter: "all" (default), "cand", "prod", "exp", "shared" or "platform"
|
|
263
|
+
search: Case-insensitive text to find in the name, e-mail or main repository
|
|
264
|
+
page: Zero-indexed page number
|
|
265
|
+
per_page: Items per page
|
|
266
|
+
sort_by: Column to sort by
|
|
267
|
+
sort_order: "asc" or "desc"
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
UsageAttributionTableResponse with the rows of the page and a count per filter
|
|
271
|
+
"""
|
|
272
|
+
return self._get_table(
|
|
273
|
+
"/people",
|
|
274
|
+
params,
|
|
275
|
+
filter=filter,
|
|
276
|
+
search=search,
|
|
277
|
+
page=page,
|
|
278
|
+
per_page=per_page,
|
|
279
|
+
sort_by=sort_by,
|
|
280
|
+
sort_order=sort_order,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def get_person(
|
|
284
|
+
self,
|
|
285
|
+
user_id: str,
|
|
286
|
+
params: UsageAttributionQueryParams | None = None,
|
|
287
|
+
) -> UsageAttributionResponse:
|
|
288
|
+
"""Get the popup of one person.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
user_id: User id as listed by get_people
|
|
292
|
+
params: Period parameters
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
UsageAttributionResponse whose data holds ``row`` and ``detail``
|
|
296
|
+
|
|
297
|
+
Raises:
|
|
298
|
+
requests.HTTPError: 404 when the person is not in the analysis of the period
|
|
299
|
+
"""
|
|
300
|
+
return self._get_view(f"/people/{quote(user_id, safe='')}", params)
|
|
301
|
+
|
|
302
|
+
def get_breakdown(
|
|
303
|
+
self,
|
|
304
|
+
params: UsageAttributionQueryParams | None = None,
|
|
305
|
+
dimension: str | None = None,
|
|
306
|
+
category: str | None = None,
|
|
307
|
+
) -> UsageAttributionResponse:
|
|
308
|
+
"""Get CLI spend paid from personal budgets, by country or job title group.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
params: Period parameters
|
|
312
|
+
dimension: "country" (default) or "job"
|
|
313
|
+
category: "production" (default), "learning", "testing", "experimental",
|
|
314
|
+
"pet_project" or "unclassified"
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
UsageAttributionResponse whose data holds ``rows`` (label and spend) and ``total``
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
requests.HTTPError: 404 when user enrichment is disabled
|
|
321
|
+
"""
|
|
322
|
+
return self._get_view(
|
|
323
|
+
"/breakdown", params, dimension=dimension, category=category
|
|
324
|
+
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
codemie_sdk/__init__.py,sha256=
|
|
1
|
+
codemie_sdk/__init__.py,sha256=yOdC5rH8Q7vFn6odg1dzczxzXfsqfZ69Jw36cXg2bO0,9627
|
|
2
2
|
codemie_sdk/auth/__init__.py,sha256=IksEj223xEZtJ-cQ0AT9L0Bs9psIJ8QNzDXrPTUQ3xQ,126
|
|
3
3
|
codemie_sdk/auth/credentials.py,sha256=hO0X3EQWZSlIm0ABhiJo4fIIndm2blB0JZWMaWvsuTE,7774
|
|
4
4
|
codemie_sdk/builders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -7,7 +7,7 @@ codemie_sdk/builders/workflow/builder.py,sha256=bb_1uUe0AFhWuqe60Dao_1E-INxPwa7b
|
|
|
7
7
|
codemie_sdk/builders/workflow/definition.py,sha256=YsKFoYC_VdKsTO2_AobSduP-MGjImEddn_0W5HzPZHQ,14338
|
|
8
8
|
codemie_sdk/builders/workflow/publish.py,sha256=wrXFDNX0LpRTULp8zm5DD6hEmAqEwIMxPm0mSs82xjQ,7370
|
|
9
9
|
codemie_sdk/client/__init__.py,sha256=yf6C39MmrJ6gK9ZHMhBeynKwUUYVSUTQbKxU8-4qpKg,101
|
|
10
|
-
codemie_sdk/client/client.py,sha256=
|
|
10
|
+
codemie_sdk/client/client.py,sha256=J-PfxYNwB5XAU5d7BAVOaOGmOKM94bzvvf2Itrw6D7Y,10202
|
|
11
11
|
codemie_sdk/exceptions.py,sha256=uuzvtvYBMzrNGt7Q4k5RVq0Pd8eJ_tR2tLy3pF2ICDs,1148
|
|
12
12
|
codemie_sdk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
codemie_sdk/models/a2a.py,sha256=93GGBg4vkrlnuGCtakMLgYtrD9U54Dme9Dm5sJGO2bc,1986
|
|
@@ -28,8 +28,10 @@ codemie_sdk/models/llm.py,sha256=ViADvsSAkc85YQyPUkGE3ZvMc5pnHWHO0_rOkLSTfu4,133
|
|
|
28
28
|
codemie_sdk/models/mcp_config.py,sha256=lqHEYNWW6MhZEx2tMTryVTfq_wThnmMS7vmWxtPkbYM,3792
|
|
29
29
|
codemie_sdk/models/mermaid.py,sha256=dhRgX-itvfyrt0LUg5pbSo0CTH5PewiegqcZn3hWggU,675
|
|
30
30
|
codemie_sdk/models/project.py,sha256=9nMsPNw0mUHud2sAISXrJx2lXz9VKw2Dn-ABq53UgZ8,7600
|
|
31
|
+
codemie_sdk/models/servicenow_integration.py,sha256=xE-QYCaEWsN3znEnw9CkGiRCsAlRzv1SME4rky9k-Y0,3531
|
|
31
32
|
codemie_sdk/models/skill.py,sha256=KE4lsF8TMxIQa_UE0oRDMT6XIEHqKjsDyi0HYKEp1mM,6918
|
|
32
33
|
codemie_sdk/models/task.py,sha256=YHu5DRnECS2wnqIWZ_YH8Z72p1I4aV3xWFW5joxN_8Q,1442
|
|
34
|
+
codemie_sdk/models/usage_attribution.py,sha256=uMHmtcTGD65kf6ZWHvNpRGwpV-kAUl1b5S01vq76OV0,4444
|
|
33
35
|
codemie_sdk/models/user.py,sha256=YqPWzTgIKF-7Jw6aV7RD__su1iahEJPGaDR6Px-B4bU,3848
|
|
34
36
|
codemie_sdk/models/vendor_assistant.py,sha256=vdpFq8aRlLj7IgRMWTA6pkrq8HZty8rgwA-kWrKKf3A,6357
|
|
35
37
|
codemie_sdk/models/vendor_guardrail.py,sha256=dQtuFZXFUs_IUQlnPQth0Sd49Sx9gfDtkA2bB2Qh5e0,5227
|
|
@@ -61,8 +63,10 @@ codemie_sdk/services/mcp_configs.py,sha256=bfe0QTrMqE5hEvyUlNMCjTxqL7u0AG7aZloQr
|
|
|
61
63
|
codemie_sdk/services/mermaid.py,sha256=7cLykdrJVW576raUbON3yhuWnbGaXl8rX1QfVwbu2JE,2246
|
|
62
64
|
codemie_sdk/services/project.py,sha256=v58ALdLv4BuxMkZNlkNZPikkXcCIePMoQtmKwdKCgko,14703
|
|
63
65
|
codemie_sdk/services/project_budget_groups.py,sha256=B7QJt3sh_LV3lfwALZlxUq0SLR30BWafHcS6eHyOh8Y,5471
|
|
66
|
+
codemie_sdk/services/servicenow_integration.py,sha256=UiMlyiSxegs4gY604rDlKTDxDymKMDP-GRZ85DRlbl8,3376
|
|
64
67
|
codemie_sdk/services/skill.py,sha256=0mExdeKTMNqkEpKafD0ya1WyLj9CMCFaprkggKYqnQg,13216
|
|
65
68
|
codemie_sdk/services/task.py,sha256=UaYh__xGF7VKWCJKPrncVjTvx46Gz1AN9XczPVL71oA,1052
|
|
69
|
+
codemie_sdk/services/usage_attribution.py,sha256=iLjs4trg3Fj_uapLW1PAHgrtkVb2hiTPjRKOvO1vdaY,10782
|
|
66
70
|
codemie_sdk/services/user.py,sha256=W0-fkY6vIbJDlIpb2PxxljSMOdWh910BmBK-n94gBs0,6998
|
|
67
71
|
codemie_sdk/services/vendor_assistant.py,sha256=Vwa0rH8TYTCV1iAeKuSP5skbX8V04NLXE0g9VQRKqGI,13484
|
|
68
72
|
codemie_sdk/services/vendor_guardrail.py,sha256=HFDRPKb1wXhz4sG_XUISLcBHvZB2bJkqxLCUlG_SS5Q,14280
|
|
@@ -76,6 +80,6 @@ codemie_sdk/services/workflow_execution_state.py,sha256=u1xmGvaHjqvQS5ujhyvJ_Ac7
|
|
|
76
80
|
codemie_sdk/services/workspace.py,sha256=eLwZ0fhi1BfOesrXaQlTdOll8q7ENP9b_jb5eybKSVw,5038
|
|
77
81
|
codemie_sdk/utils/__init__.py,sha256=EzK_UrS5KHmAnE73Hci9LaObB5omjVUhIq4svh0gfwo,137
|
|
78
82
|
codemie_sdk/utils/http.py,sha256=PWejBKa5pwzQ5eLVhdN8lYKEu1B1QtwGTRmZfnGFL6c,14158
|
|
79
|
-
codemie_sdk_python-0.1.
|
|
80
|
-
codemie_sdk_python-0.1.
|
|
81
|
-
codemie_sdk_python-0.1.
|
|
83
|
+
codemie_sdk_python-0.1.706.dist-info/METADATA,sha256=U0IJS0NRdpGniojMRgUeqDZRsMRUUTvEB-sybGaSWkk,47938
|
|
84
|
+
codemie_sdk_python-0.1.706.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
|
|
85
|
+
codemie_sdk_python-0.1.706.dist-info/RECORD,,
|
|
File without changes
|