clear-skies-cortex 2.0.3__py3-none-any.whl → 2.0.5__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.
@@ -2,6 +2,44 @@ import clearskies
2
2
 
3
3
 
4
4
  class DefaultCortexUrl(clearskies.di.AdditionalConfigAutoImport):
5
+ """
6
+ Default URL provider for Cortex API.
7
+
8
+ This class provides automatic configuration for the Cortex API base URL using
9
+ the clearskies dependency injection system. It is auto-imported when the clearskies_cortex
10
+ package is used, making URL configuration seamless.
11
+
12
+ The URL can be configured via the `CORTEX_URL` environment variable. If not set,
13
+ it defaults to the standard Cortex API endpoint: `https://api.getcortexapp.com/api/v1/`
14
+
15
+ ```python
16
+ import clearskies
17
+ from clearskies_cortex.models import CortexTeam
18
+
19
+ # The URL is automatically configured via environment variable
20
+ # export CORTEX_URL=https://custom-cortex-instance.example.com/api/v1/
21
+
22
+ # Or use the default Cortex API URL by not setting the variable
23
+
24
+ # Then use models normally - URL is handled automatically
25
+ teams = CortexTeam()
26
+ for team in teams:
27
+ print(team.get_name())
28
+ ```
29
+ """
30
+
5
31
  def provide_cortex_url(self, environment: clearskies.Environment) -> str:
32
+ """
33
+ Provide the Cortex API base URL.
34
+
35
+ Checks for `CORTEX_URL` environment variable, falling back to the default
36
+ Cortex API URL if not set.
37
+
38
+ Args:
39
+ environment: The clearskies Environment instance for accessing env variables.
40
+
41
+ Returns:
42
+ The Cortex API base URL string.
43
+ """
6
44
  cortex_url = environment.get("CORTEX_URL", True)
7
45
  return cortex_url if cortex_url else "https://api.getcortexapp.com/api/v1/"
@@ -17,7 +17,34 @@ from clearskies_cortex.models import (
17
17
 
18
18
 
19
19
  class CortexCatalogEntity(Model):
20
- """Model for entities."""
20
+ """
21
+ Model for Cortex catalog entities.
22
+
23
+ This model represents entities in the Cortex catalog, which can include services, domains,
24
+ teams, and other entity types. It provides access to entity metadata, ownership information,
25
+ hierarchy data, and associated scorecards.
26
+
27
+ The model connects to the Cortex catalog API endpoint and supports various search parameters
28
+ for filtering entities.
29
+
30
+ ```python
31
+ from clearskies_cortex.models import CortexCatalogEntity
32
+
33
+ # Fetch all entities
34
+ entities = CortexCatalogEntity()
35
+ for entity in entities:
36
+ print(f"Entity: {entity.name} ({entity.type})")
37
+
38
+ # Find a specific entity by tag
39
+ entity = entities.find("tag=my-service")
40
+ print(entity.description)
41
+
42
+ # Access parsed hierarchy data
43
+ hierarchy = entity.parse_hierarchy()
44
+ if hierarchy.parents:
45
+ print(f"Parent: {hierarchy.parents[0].tag}")
46
+ ```
47
+ """
21
48
 
22
49
  id_column_name: str = "tag"
23
50
 
@@ -28,42 +55,172 @@ class CortexCatalogEntity(Model):
28
55
  """Return the slug of the api endpoint for this model."""
29
56
  return "catalog"
30
57
 
58
+ """
59
+ The unique identifier for the entity.
60
+ """
31
61
  id = String()
62
+
63
+ """
64
+ The unique tag identifier for the entity (e.g., "my-service").
65
+
66
+ This is the primary identifier used to reference entities in the Cortex catalog.
67
+ """
32
68
  tag = String()
69
+
70
+ """
71
+ List of groups the entity belongs to.
72
+
73
+ Groups are key-value pairs formatted as "key:value" strings. Use `parse_groups()`
74
+ to convert to a dictionary.
75
+ """
33
76
  groups = StringList("groups")
77
+
78
+ """
79
+ JSON object containing owner information for the entity.
80
+
81
+ Contains team and user ownership data. Use `parse_owners()` to convert to a typed dataclass.
82
+ """
34
83
  owners = Json()
84
+
85
+ """
86
+ JSON object containing ownership configuration.
87
+ """
35
88
  ownership = Json()
89
+
90
+ """
91
+ JSON object containing version 2 owner information.
92
+ """
36
93
  owners_v2 = Json()
94
+
95
+ """
96
+ Human-readable description of the entity.
97
+ """
37
98
  description = String()
99
+
100
+ """
101
+ JSON object containing Git repository information.
102
+
103
+ Includes repository URLs, default branch, and other Git-related metadata.
104
+ """
38
105
  git = Json()
106
+
107
+ """
108
+ JSON object containing hierarchy information.
109
+
110
+ Contains parent and child relationships. Use `parse_hierarchy()` to convert to a typed dataclass.
111
+ """
39
112
  hierarchy = Json()
113
+
114
+ """
115
+ Timestamp of when the entity was last updated.
116
+ """
40
117
  last_updated = Datetime()
118
+
119
+ """
120
+ Whether the entity is archived.
121
+ """
41
122
  is_archived = Boolean()
123
+
124
+ """
125
+ JSON object containing external links associated with the entity.
126
+ """
42
127
  links = Json()
128
+
129
+ """
130
+ JSON object containing member information for team entities.
131
+ """
43
132
  members = Json()
133
+
134
+ """
135
+ JSON object containing custom metadata for the entity.
136
+ """
44
137
  metadata = Json()
138
+
139
+ """
140
+ JSON object containing Slack channel configurations.
141
+ """
45
142
  slack_channels = Json()
143
+
144
+ """
145
+ Human-readable name of the entity.
146
+ """
46
147
  name = String()
148
+
149
+ """
150
+ The type of entity (e.g., "service", "domain", "team").
151
+ """
47
152
  type = String()
153
+
154
+ """
155
+ Related scorecards for this entity.
156
+
157
+ HasMany relationship to CortexCatalogEntityScorecard.
158
+ """
48
159
  scorecards = HasMany(
49
160
  cortex_catalog_entity_scorecard.CortexCatalogEntityScorecard,
50
161
  foreign_column_name="entity_tag",
51
162
  )
163
+
164
+ """
165
+ Related groups for this entity.
166
+
167
+ HasMany relationship to CortexCatalogEntityGroup.
168
+ """
52
169
  group_models = HasMany(
53
170
  cortex_catalog_entity_group.CortexCatalogEntityGroup,
54
171
  foreign_column_name="entity_tag",
55
172
  )
56
173
 
57
174
  # search columns
175
+
176
+ """
177
+ Search parameter: Filter by hierarchy depth level.
178
+ """
58
179
  hierarchy_depth = String(is_searchable=True, is_temporary=True)
180
+
181
+ """
182
+ Search parameter: Filter by Git repository URLs.
183
+ """
59
184
  git_repositories = StringList(is_searchable=True, is_temporary=True)
185
+
186
+ """
187
+ Search parameter: Filter by entity types (e.g., "service", "domain").
188
+ """
60
189
  types = StringList(is_searchable=True, is_temporary=True)
190
+
191
+ """
192
+ Search parameter: Free-text search query.
193
+ """
61
194
  query = String(is_searchable=True, is_temporary=True)
195
+
196
+ """
197
+ Search parameter: Include archived entities in results.
198
+ """
62
199
  include_archived = Boolean(is_searchable=True, is_temporary=True)
200
+
201
+ """
202
+ Search parameter: Include metadata in response.
203
+ """
63
204
  include_metadata = Boolean(is_searchable=True, is_temporary=True)
205
+
206
+ """
207
+ Search parameter: Include links in response.
208
+ """
64
209
  include_links = Boolean(is_searchable=True, is_temporary=True)
210
+
211
+ """
212
+ Search parameter: Include owner information in response.
213
+ """
65
214
  include_owners = Boolean(is_searchable=True)
215
+
216
+ """
217
+ Search parameter: Include nested fields in response (e.g., "team:members").
218
+ """
66
219
  include_nested_fields = StringList(is_searchable=True, is_temporary=True)
220
+
221
+ """
222
+ Search parameter: Include hierarchy fields in response (e.g., "groups").
223
+ """
67
224
  include_hierarchy_fields = StringList(is_searchable=True, is_temporary=True)
68
225
 
69
226
  def parse_hierarchy(self) -> dataclasses.ServiceEntityHierarchy:
@@ -88,3 +245,162 @@ class CortexCatalogEntity(Model):
88
245
  def parse_owners(self) -> dataclasses.EntityTeamOwner:
89
246
  """Parse the owners column into a dictionary."""
90
247
  return from_dict(dataclasses.EntityTeamOwner, data=self.owners)
248
+
249
+ def get_group_tags(self) -> list[str]:
250
+ """Get groups as simple tags.
251
+
252
+ Returns groups that don't have a key:value format.
253
+
254
+ Returns:
255
+ List of group names that are simple tags without key:value format.
256
+
257
+ Example:
258
+ >>> entity.groups = ["my-tag", "team:platform", "production"]
259
+ >>> entity.get_group_tags()
260
+ ["my-tag", "production"]
261
+ """
262
+ tags: list[str] = []
263
+ if self.groups:
264
+ for group in self.groups:
265
+ if ":" not in group:
266
+ tags.append(group)
267
+ return tags
268
+
269
+ def get_group_value(self, key: str) -> str | None:
270
+ """Get the value for a specific group key.
271
+
272
+ Groups can be formatted as "key:value" pairs. This method extracts
273
+ the value for a given key.
274
+
275
+ Args:
276
+ key: The group key to look up.
277
+
278
+ Returns:
279
+ The value associated with the key, or None if not found.
280
+
281
+ Example:
282
+ >>> entity.groups = ["team:platform", "env:production"]
283
+ >>> entity.get_group_value("team")
284
+ "platform"
285
+ """
286
+ if self.groups:
287
+ for group in self.groups:
288
+ if group.startswith(f"{key}:"):
289
+ return group.split(":", 1)[1]
290
+ return None
291
+
292
+ def get_git_repository_url(self) -> str | None:
293
+ """Get the Git repository URL.
294
+
295
+ Returns:
296
+ The repository URL, or None if not configured.
297
+
298
+ Example:
299
+ >>> entity.git = {"repository": "https://github.com/org/repo"}
300
+ >>> entity.get_git_repository_url()
301
+ "https://github.com/org/repo"
302
+ """
303
+ if not self.git:
304
+ return None
305
+ return self.git.get("repository") or self.git.get("repositoryUrl")
306
+
307
+ def get_git_provider(self) -> str | None:
308
+ """Get the Git provider name.
309
+
310
+ Returns:
311
+ The provider name (e.g., "github", "gitlab", "bitbucket", "azure-devops"),
312
+ or None if not configured.
313
+
314
+ Example:
315
+ >>> entity.git = {"provider": "github", "repository": "..."}
316
+ >>> entity.get_git_provider()
317
+ "github"
318
+ """
319
+ if not self.git:
320
+ return None
321
+ return self.git.get("provider")
322
+
323
+ def get_git_project_id(self) -> str | None:
324
+ """Extract the project/repository identifier from the Git configuration.
325
+
326
+ Cortex supports multiple SCM providers (GitHub, GitLab, Bitbucket, Azure DevOps).
327
+ This method extracts the project identifier in a provider-agnostic way.
328
+
329
+ For GitLab, this returns the numeric project ID if available in the URL.
330
+ For GitHub/Bitbucket, this returns the "owner/repo" format.
331
+ For Azure DevOps, this returns the project/repo path.
332
+
333
+ Returns:
334
+ The project identifier as a string, or None if not found.
335
+
336
+ Example:
337
+ >>> entity.git = {"repository": "https://github.com/org/repo"}
338
+ >>> entity.get_git_project_id()
339
+ "org/repo"
340
+
341
+ >>> entity.git = {"repository": "https://gitlab.com/projects/12345"}
342
+ >>> entity.get_git_project_id()
343
+ "12345"
344
+ """
345
+ if not self.git:
346
+ return None
347
+
348
+ repo_url = self.git.get("repository") or self.git.get("repositoryUrl") or ""
349
+
350
+ # GitLab numeric project ID format: https://gitlab.com/projects/12345
351
+ if "/projects/" in repo_url:
352
+ try:
353
+ project_id = repo_url.split("/projects/")[1].split("/")[0]
354
+ return project_id
355
+ except IndexError:
356
+ pass
357
+
358
+ # Standard URL format: https://provider.com/owner/repo or https://provider.com/owner/repo.git
359
+ # Works for GitHub, GitLab (path format), Bitbucket
360
+ for prefix in ["github.com/", "gitlab.com/", "bitbucket.org/"]:
361
+ if prefix in repo_url:
362
+ try:
363
+ path = repo_url.split(prefix)[1]
364
+ # Remove .git suffix if present
365
+ if path.endswith(".git"):
366
+ path = path[:-4]
367
+ # Remove trailing slashes
368
+ path = path.rstrip("/")
369
+ # Return owner/repo format
370
+ parts = path.split("/")
371
+ if len(parts) >= 2:
372
+ return f"{parts[0]}/{parts[1]}"
373
+ except IndexError:
374
+ pass
375
+
376
+ # Azure DevOps format: https://dev.azure.com/org/project/_git/repo
377
+ if "dev.azure.com/" in repo_url:
378
+ try:
379
+ path = repo_url.split("dev.azure.com/")[1]
380
+ parts = path.split("/")
381
+ if len(parts) >= 4 and parts[2] == "_git":
382
+ return f"{parts[0]}/{parts[1]}/{parts[3]}"
383
+ except IndexError:
384
+ pass
385
+
386
+ return None
387
+
388
+ @property
389
+ def is_cloud_resource(self) -> bool:
390
+ """Check if entity represents a cloud resource.
391
+
392
+ Cortex supports pulling in resources from AWS, Azure, and Google Cloud.
393
+ This property checks if the entity type indicates a cloud resource.
394
+
395
+ Returns:
396
+ True if the entity type indicates a cloud resource.
397
+
398
+ Example:
399
+ >>> entity.type = "AWS::Lambda::Function"
400
+ >>> entity.is_cloud_resource
401
+ True
402
+ """
403
+ if not self.type:
404
+ return False
405
+ cloud_prefixes = ("AWS::", "Azure::", "Google::")
406
+ return self.type.startswith(cloud_prefixes)
@@ -7,7 +7,35 @@ from clearskies_cortex.models import cortex_catalog_entity
7
7
 
8
8
 
9
9
  class CortexCatalogEntityDomain(cortex_catalog_entity.CortexCatalogEntity):
10
- """Model for domain entities."""
10
+ """
11
+ Model for Cortex domain entities.
12
+
13
+ This model extends CortexCatalogEntity to specifically handle domain-type entities
14
+ in the Cortex catalog. Domains represent logical groupings of services and can be
15
+ organized hierarchically.
16
+
17
+ The model automatically filters for domain entities and includes nested fields for
18
+ team members, owners, metadata, and hierarchy groups.
19
+
20
+ ```python
21
+ from clearskies_cortex.models import CortexCatalogEntityDomain
22
+
23
+ # Fetch all domains
24
+ domains = CortexCatalogEntityDomain()
25
+ for domain in domains.get_final_query():
26
+ print(f"Domain: {domain.name}")
27
+
28
+ # Get the top-level domain for a given domain
29
+ domain = domains.find("tag=my-domain")
30
+ top_level = domain.get_top_level_domain()
31
+ print(f"Top-level domain: {top_level.name}")
32
+
33
+ # Get the immediate parent domain
34
+ parent = domain.get_parent()
35
+ if parent.exists():
36
+ print(f"Parent domain: {parent.name}")
37
+ ```
38
+ """
11
39
 
12
40
  def get_final_query(self) -> Query:
13
41
  return (
@@ -7,7 +7,25 @@ from clearskies_cortex.backends import CortexBackend
7
7
 
8
8
 
9
9
  class CortexCatalogEntityGroup(Model):
10
- """Model for teams."""
10
+ """
11
+ Model for Cortex entity groups.
12
+
13
+ This model represents groups associated with a specific catalog entity in Cortex.
14
+ Groups are used to categorize and organize entities with key-value pairs.
15
+
16
+ The model connects to the Cortex API endpoint `catalog/{entity_tag}/groups` to fetch
17
+ group information for a specific entity.
18
+
19
+ ```python
20
+ from clearskies_cortex.models import CortexCatalogEntityGroup
21
+
22
+ # Fetch groups for a specific entity
23
+ groups = CortexCatalogEntityGroup()
24
+ entity_groups = groups.where("entity_tag=my-service")
25
+ for group in entity_groups:
26
+ print(f"Group tag: {group.tag}")
27
+ ```
28
+ """
11
29
 
12
30
  id_column_name: str = "entity_tag"
13
31
 
@@ -18,5 +36,12 @@ class CortexCatalogEntityGroup(Model):
18
36
  """Return the slug of the api endpoint for this model."""
19
37
  return "catalog/{entity_tag}/groups"
20
38
 
39
+ """
40
+ The tag identifier of the parent entity this group belongs to.
41
+ """
21
42
  entity_tag = String()
43
+
44
+ """
45
+ JSON object containing the group tag information.
46
+ """
22
47
  tag = Json()
@@ -8,7 +8,27 @@ from clearskies_cortex.backends import CortexBackend
8
8
 
9
9
 
10
10
  class CortexCatalogEntityScorecard(Model):
11
- """Model for teams."""
11
+ """
12
+ Model for Cortex entity scorecards.
13
+
14
+ This model represents scorecard data associated with a specific catalog entity in Cortex.
15
+ Scorecards track compliance, quality, and other metrics for entities based on defined rules.
16
+
17
+ The model connects to the Cortex API endpoint `catalog/:entity_tag/scorecards` to fetch
18
+ scorecard scores and ladder level information for a specific entity.
19
+
20
+ ```python
21
+ from clearskies_cortex.models import CortexCatalogEntityScorecard
22
+
23
+ # Fetch scorecards for a specific entity
24
+ scorecards = CortexCatalogEntityScorecard()
25
+ entity_scorecards = scorecards.where("entity_tag=my-service")
26
+ for scorecard in entity_scorecards:
27
+ print(f"Scorecard: {scorecard.score_card_name}")
28
+ print(f"Score: {scorecard.score}/{scorecard.total_possible_score}")
29
+ print(f"Percentage: {scorecard.score_percentage}%")
30
+ ```
31
+ """
12
32
 
13
33
  id_column_name: str = "scorecard_id"
14
34
 
@@ -19,12 +39,41 @@ class CortexCatalogEntityScorecard(Model):
19
39
  """Return the slug of the api endpoint for this model."""
20
40
  return "catalog/:entity_tag/scorecards"
21
41
 
42
+ """
43
+ The unique identifier for the scorecard.
44
+ """
22
45
  scorecard_id = Integer()
46
+
47
+ """
48
+ The tag identifier of the entity this scorecard belongs to.
49
+ """
23
50
  entity_tag = String()
51
+
52
+ """
53
+ JSON object containing ladder level information.
54
+
55
+ Ladder levels define maturity stages for the scorecard (e.g., Bronze, Silver, Gold).
56
+ """
24
57
  ladder_levels = Json()
58
+
59
+ """
60
+ The current score achieved by the entity for this scorecard.
61
+ """
25
62
  score = Integer()
63
+
64
+ """
65
+ The score as a percentage of the total possible score.
66
+ """
26
67
  score_percentage = Float()
68
+
69
+ """
70
+ The human-readable name of the scorecard.
71
+ """
27
72
  score_card_name = String()
73
+
74
+ """
75
+ The maximum possible score for this scorecard.
76
+ """
28
77
  total_possible_score = Integer()
29
78
 
30
79
  def get_score_card_tag_name(self) -> str:
@@ -16,7 +16,41 @@ from clearskies_cortex.models import (
16
16
 
17
17
 
18
18
  class CortexCatalogEntityService(cortex_catalog_entity.CortexCatalogEntity):
19
- """Model for domain entities."""
19
+ """
20
+ Model for Cortex service entities.
21
+
22
+ This model extends CortexCatalogEntity to specifically handle service-type entities
23
+ in the Cortex catalog. Services represent individual applications, microservices, or
24
+ other software components tracked in Cortex.
25
+
26
+ The model automatically filters for service entities and includes nested fields for
27
+ team members, owners, metadata, and hierarchy groups. It also provides methods to
28
+ navigate team and domain relationships.
29
+
30
+ ```python
31
+ from clearskies_cortex.models import CortexCatalogEntityService
32
+
33
+ # Fetch all services
34
+ services = CortexCatalogEntityService()
35
+ for service in services.get_final_query():
36
+ print(f"Service: {service.name}")
37
+
38
+ # Get the owning team for a service
39
+ service = services.find("tag=my-service")
40
+ team = service.get_team()
41
+ if team.exists():
42
+ print(f"Owned by team: {team.get_name()}")
43
+
44
+ # Get the top-level team in the hierarchy
45
+ top_team = service.get_top_level_team()
46
+ print(f"Top-level team: {top_team.get_name()}")
47
+
48
+ # Get the parent domain for a service
49
+ parent_domain = service.get_parent_domain()
50
+ if parent_domain.exists():
51
+ print(f"Parent domain: {parent_domain.name}")
52
+ ```
53
+ """
20
54
 
21
55
  backend = CortexBackend()
22
56
 
@@ -7,7 +7,36 @@ from clearskies_cortex.models import cortex_catalog_entity
7
7
 
8
8
 
9
9
  class CortexCatalogEntityTeam(cortex_catalog_entity.CortexCatalogEntity):
10
- """Model for team entities."""
10
+ """
11
+ Model for Cortex team entities.
12
+
13
+ This model extends CortexCatalogEntity to specifically handle team-type entities
14
+ in the Cortex catalog. Teams represent organizational units that own and manage
15
+ services and other entities.
16
+
17
+ The model automatically filters for team entities and includes nested fields for
18
+ team members, owners, metadata, and hierarchy groups. It provides methods to
19
+ navigate the team hierarchy.
20
+
21
+ ```python
22
+ from clearskies_cortex.models import CortexCatalogEntityTeam
23
+
24
+ # Fetch all team entities
25
+ teams = CortexCatalogEntityTeam()
26
+ for team in teams.get_final_query():
27
+ print(f"Team: {team.name}")
28
+
29
+ # Get the top-level team in the hierarchy
30
+ team = teams.find("tag=my-team")
31
+ top_team = team.get_top_level_team()
32
+ print(f"Top-level team: {top_team.name}")
33
+
34
+ # Get the immediate parent team
35
+ parent = team.get_parent()
36
+ if parent.exists():
37
+ print(f"Parent team: {parent.name}")
38
+ ```
39
+ """
11
40
 
12
41
  def get_final_query(self) -> Query:
13
42
  return (
@@ -7,7 +7,25 @@ from clearskies_cortex.backends import CortexBackend
7
7
 
8
8
 
9
9
  class CortexCatalogEntityType(Model):
10
- """Model for entities."""
10
+ """
11
+ Model for Cortex entity type definitions.
12
+
13
+ This model represents the available entity types in the Cortex catalog. Entity types
14
+ define the schema and structure for different kinds of entities (e.g., service, domain, team).
15
+
16
+ The model connects to the Cortex API endpoint `catalog/definitions` to fetch
17
+ entity type definitions.
18
+
19
+ ```python
20
+ from clearskies_cortex.models import CortexCatalogEntityType
21
+
22
+ # Fetch all entity type definitions
23
+ entity_types = CortexCatalogEntityType()
24
+ for entity_type in entity_types:
25
+ print(f"Type: {entity_type.name}")
26
+ print(f"Description: {entity_type.description}")
27
+ ```
28
+ """
11
29
 
12
30
  id_column_name: str = "type"
13
31
 
@@ -18,8 +36,27 @@ class CortexCatalogEntityType(Model):
18
36
  """Return the slug of the api endpoint for this model."""
19
37
  return "catalog/definitions"
20
38
 
39
+ """
40
+ The human-readable name of the entity type.
41
+ """
21
42
  name = String()
43
+
44
+ """
45
+ A description of what this entity type represents.
46
+ """
22
47
  description = String()
48
+
49
+ """
50
+ JSON object containing the schema definition for this entity type.
51
+ """
23
52
  schema = Json()
53
+
54
+ """
55
+ The source of the entity type definition (e.g., "builtin", "custom").
56
+ """
24
57
  source = String()
58
+
59
+ """
60
+ The unique type identifier (e.g., "service", "domain", "team").
61
+ """
25
62
  type = String()