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 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
  ]
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codemie-sdk-python
3
- Version: 0.1.704
3
+ Version: 0.1.706
4
4
  Summary: CodeMie SDK for Python
5
5
  License: Apache-2.0
6
6
  Author: CodeMie
@@ -1,4 +1,4 @@
1
- codemie_sdk/__init__.py,sha256=b3eCiXsjw_q4eubzF97OEDYNJKMq4U98ekRNgvqmcuo,9014
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=3v4uRF4SGGxR5Rs6fPQRbyysbKI_QEvrTB6wJH3MUN4,9770
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.704.dist-info/METADATA,sha256=Jd9tt-r_yspSpt02J4_RNWn7JfwTOLu3PzsXpO3XJow,47938
80
- codemie_sdk_python-0.1.704.dist-info/WHEEL,sha256=L-WvLdSBvJWHXv5zrQxDvNRnIgoBzLUpcbuiw19KCuQ,88
81
- codemie_sdk_python-0.1.704.dist-info/RECORD,,
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,,