Skip to content

Commit 1c60be9

Browse files
committed
feat: release changes for version 0.4.9
1 parent 23c1b92 commit 1c60be9

File tree

10 files changed

+111
-79
lines changed

10 files changed

+111
-79
lines changed

galaxy/cli/cookiecutter/{{cookiecutter.integration_name}}/client.py

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from types import TracebackType
2-
from typing import Any
3-
from galaxy.core.utils import make_request
4-
from galaxy.utils.requests import ClientSession, RequestError, create_session
2+
3+
from galaxy.utils.requests import ClientSession, create_session
54

65
__all__ = ["{{ cookiecutter.integration_name_pascalcase }}Client"]
76

@@ -13,6 +12,7 @@ def __init__(self, config, logger):
1312

1413
# (If needed) Client session
1514
self._session: ClientSession | None = None
15+
self._headers = {}
1616

1717
# (If needed) Implement logic for client context manager
1818
async def __aenter__(self) -> "{{ cookiecutter.integration_name_pascalcase }}Client":
@@ -26,33 +26,12 @@ async def close(self) -> None:
2626
if self._session is not None:
2727
await self._session.close()
2828

29-
# (If needed) Session request utilities
30-
async def _make_request(
31-
self,
32-
method: str,
33-
url: str,
34-
*,
35-
retry: bool = True,
36-
raise_on_error: bool = False,
37-
none_on_404: bool = True,
38-
**kwargs: Any,
39-
) -> Any:
40-
try:
41-
return await make_request(
42-
self._session,
43-
method,
44-
url,
45-
**kwargs,
46-
logger=self.logger,
47-
retry_policy=self._retry_policy,
48-
retry=retry,
49-
none_on_404=none_on_404,
50-
)
51-
except RequestError as e:
52-
if raise_on_error:
53-
raise
54-
self.logger.error(f"Error while making request, defaulting to empty response. ({e})")
55-
return None
29+
@property
30+
def session(self) -> ClientSession:
31+
if self._session is None:
32+
raise ValueError("client session has not been created")
33+
34+
return self._session
5635

5736

5837
# Implement the logic to make the API requests in this class

galaxy/cli/cookiecutter/{{cookiecutter.integration_name}}/config.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ rely:
22
token: "{{ "{{" }} env('RELY_API_TOKEN') {{ "}}" }}"
33
url: "{{ "{{" }} env('RELY_API_URL') {{ "}}" }}"
44
integration:
5-
# The identifier of this integration instance.
6-
id: "{{ "{{" }} env('RELY_INTEGRATION_ID') | default('00000', true) {{ "}}" }}"
7-
# The type of the integration.
5+
id: "{{ "{{" }} env('RELY_INTEGRATION_ID') {{ "}}" }}"
86
type: "{{ cookiecutter.integration_name }}"
9-
# The execution type of the integration.
107
executionType: "{{ "{{" }} env('RELY_INTEGRATION_EXECUTION_TYPE') | default('cronjob', true) {{ "}}" }}"
11-
# The configuration for the integration.
8+
129
scheduledInterval: "{{ "{{" }} env('RELY_INTEGRATION_DAEMON_INTERVAL') | default(60, true) | int {{ "}}" }}"
1310
defaultModelMappings: {}
1411
properties:
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1+
from types import TracebackType
2+
from typing import Any
3+
14
from galaxy.core.galaxy import register, Integration
25
from galaxy.core.models import Config
3-
from galaxy.integrations.{{cookiecutter.integration_name}}.client import {{cookiecutter.integration_name_pascalcase}}Client
6+
from galaxy.integrations.{{ cookiecutter.integration_name }}.client import {{ cookiecutter.integration_name_pascalcase }}Client
47

58

6-
class {{cookiecutter.integration_name_pascalcase}}(Integration):
9+
class {{ cookiecutter.integration_name_pascalcase }}(Integration):
710
_methods = []
811

912
def __init__(self, config: Config):
1013
super().__init__(config)
11-
self.client = {{cookiecutter.integration_name_pascalcase}}Client(self.config, self.logger)
14+
self.client = {{ cookiecutter.integration_name_pascalcase }}Client(self.config, self.logger)
15+
16+
async def __aenter__(self) -> "{{ cookiecutter.integration_name_pascalcase }}":
17+
await self.client.__aenter__()
18+
return self
19+
20+
async def __aexit__(self, exc_type: type, exc: Exception, tb: TracebackType) -> None:
21+
await self.client.__aexit__(exc_type, exc, tb)
1222

1323
@register(_methods, group=1)
14-
async def entities(self) -> list[dict]:
15-
self.entities = await self.client.api_request()
16-
entities = await self.mapper.process("entities", self.entities)
17-
return entities
24+
async def entities(self) -> tuple[Any]:
25+
entities = await self.client.api_request()
26+
mapped_entities = await self.mapper.process("entities", entities)
27+
return mapped_entities

galaxy/core/mapper.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,23 @@ def _replace_non_matching_characters(self, input_string: str, regex_pattern: str
5757

5858
def _sanitize(self, entity: dict) -> dict:
5959
if entity.get("id"):
60-
entity["id"] = str(entity["id"]).lower()
61-
entity["id"] = self._replace_non_matching_characters(entity["id"], self.id_allowed_chars).lower()
60+
entity["id"] = self._replace_non_matching_characters(str(entity["id"]).lower(), self.id_allowed_chars)
61+
6262
for relation in entity.get("relations", {}).values():
63+
if not relation.get("value"):
64+
continue
65+
6366
if isinstance(relation["value"], list):
6467
relation["value"] = [
65-
self._replace_non_matching_characters(value, self.id_allowed_chars).lower()
68+
value
69+
if not value
70+
else self._replace_non_matching_characters(str(value).lower(), self.id_allowed_chars)
6671
for value in relation["value"]
6772
]
6873
else:
69-
if relation["value"]:
70-
relation["value"] = relation["value"].lower()
71-
relation["value"] = self._replace_non_matching_characters(relation["value"], self.id_allowed_chars)
74+
relation["value"] = self._replace_non_matching_characters(
75+
str(relation["value"]).lower(), self.id_allowed_chars
76+
)
7277

7378
return entity
7479

galaxy/integrations/github/.rely/mappings.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ resources:
7878
| sort
7979
| first
8080
// null
81-
additions: .additions
82-
deletions: .deletions
81+
additions: .additions
82+
deletions: .deletions
8383
relations:
8484
repository:
8585
value: .context.repositoryId | tostring

galaxy/integrations/github/client.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717

1818
class GithubClient:
19+
DEFAULT_DAYS_OF_HISTORY: int = 30
20+
1921
@staticmethod
2022
def get_access_token(
2123
app_id: str, app_private_key: str, app_installation_id: str = "", app_auth: bool = False
@@ -43,8 +45,6 @@ def get_access_token(
4345
def __init__(self, config, logger, token):
4446
self.config = config
4547
self.logger = logger
46-
self.history_limit_timestamp = datetime.now() - timedelta(days=self.days_of_history)
47-
self.repo_activity_limit_timestamp = datetime.now() - timedelta(days=self.days_of_history * 3)
4848

4949
self._headers = {
5050
"Authorization": f"Bearer {token}",
@@ -54,6 +54,11 @@ def __init__(self, config, logger, token):
5454
self._session: ClientSession | None = None
5555
self._retry_policy = RetryPolicy(logger=self.logger)
5656

57+
self._days_of_history: int | None = None
58+
59+
self.history_limit_timestamp = datetime.now() - timedelta(days=self.days_of_history)
60+
self.repo_activity_limit_timestamp = datetime.now() - timedelta(days=self.days_of_history * 3)
61+
5762
async def __aenter__(self) -> "GithubClient":
5863
self._session = create_session(timeout=self.timeout, headers=self._headers)
5964
return self
@@ -67,12 +72,19 @@ async def close(self) -> None:
6772

6873
@property
6974
def days_of_history(self) -> int:
70-
days = int(self.config.integration.properties["daysOfHistory"])
71-
if days < 1:
72-
self.logger.warning("Invalid days of history, using default value 30")
73-
return 30
75+
if self._days_of_history is None:
76+
try:
77+
days = int(self.config.integration.properties["daysOfHistory"])
78+
if days < 1:
79+
self.logger.warning("Invalid days of history, using default value %d", self.DEFAULT_DAYS_OF_HISTORY)
80+
days = self.DEFAULT_DAYS_OF_HISTORY
81+
except (ValueError, TypeError):
82+
self.logger.warning("Missing days of history, using default value %d", self.DEFAULT_DAYS_OF_HISTORY)
83+
days = self.DEFAULT_DAYS_OF_HISTORY
84+
85+
self._days_of_history = days
7486

75-
return days
87+
return self._days_of_history
7688

7789
@property
7890
def base_url(self) -> str:
@@ -134,10 +146,16 @@ async def _make_request(
134146
self.logger.error(f"Error while making request, defaulting to empty response. ({e})")
135147
return None
136148

137-
async def _make_graphql_request(self, query: dict[str, Any]) -> Any:
149+
async def _make_graphql_request(self, query: dict[str, Any], *, ignore_file_not_found_errors: bool = True) -> Any:
138150
response = await self._make_request("POST", f"{self.base_url}/graphql", json=query, raise_on_error=True)
139151
if response.get("errors"):
140-
self.logger.warning("GraphQL error: %r", response["errors"])
152+
if not ignore_file_not_found_errors:
153+
errors = response["errors"]
154+
else:
155+
errors = [e for e in response["errors"] if isinstance(e, dict) and e.get("type") != "NOT_FOUND"]
156+
157+
if errors:
158+
self.logger.warning("GraphQL error: %r", errors)
141159
return response
142160

143161
def _parse_datetime(self, datetime_str: str) -> datetime:
@@ -170,12 +188,12 @@ async def get_repos(
170188

171189
return repos_list
172190

173-
async def get_repo(self, organization: str, repo: str) -> dict[str, str | int]:
191+
async def get_repo(self, organization: str, repo: str) -> dict[str, Any]:
174192
query = build_graphql_query(query_type=QueryType.REPOSITORY, repo_id=self.build_repo_id(organization, repo))
175193
response = await self._make_graphql_request(query)
176194
return response["data"]["repository"]
177195

178-
async def get_pull_requests(self, organization: str, repo: str, states: list[str]) -> list[dict[str, str | int]]:
196+
async def get_pull_requests(self, organization: str, repo: str, states: list[str]) -> list[dict[str, Any]]:
179197
all_pull_requests = []
180198

181199
cursor = None
@@ -205,7 +223,7 @@ async def get_pull_requests(self, organization: str, repo: str, states: list[str
205223

206224
return all_pull_requests
207225

208-
async def get_issues(self, organization: str, repo: str, state: str) -> list[dict[str, str | int]]:
226+
async def get_issues(self, organization: str, repo: str, state: str) -> list[dict[str, Any]]:
209227
all_issues = []
210228

211229
cursor = None
@@ -234,7 +252,7 @@ async def get_issues(self, organization: str, repo: str, state: str) -> list[dic
234252

235253
return all_issues
236254

237-
async def get_workflows(self, organization: str, repo: str) -> list[dict[str, str | int]]:
255+
async def get_workflows(self, organization: str, repo: str) -> list[dict[str, Any]]:
238256
all_workflows = []
239257

240258
url = f"{self.base_url}/repos/{organization}/{repo}/actions/workflows"
@@ -250,7 +268,7 @@ async def get_workflows(self, organization: str, repo: str) -> list[dict[str, st
250268

251269
return all_workflows
252270

253-
async def get_workflow_runs(self, organization: str, repo: str, workflow_id: str) -> list[dict[str, str | int]]:
271+
async def get_workflow_runs(self, organization: str, repo: str, workflow_id: str) -> list[dict[str, Any]]:
254272
all_workflow_runs = []
255273

256274
url = f"{self.base_url}/repos/{organization}/{repo}/actions/workflows/{workflow_id}/runs"
@@ -290,7 +308,7 @@ async def get_workflow_run_jobs(self, organization: str, repo: str, run_id: str)
290308

291309
return all_jobs
292310

293-
async def get_members(self, organization: str) -> list[dict[str, str | int]]:
311+
async def get_members(self, organization: str) -> list[dict[str, Any]]:
294312
all_members = []
295313
cursor = None
296314
while True:
@@ -391,7 +409,7 @@ async def get_team_repositories(self, organization: str, team_id: str) -> list[d
391409

392410
return all_repositories
393411

394-
async def get_environments(self, organization: str, repo: str) -> list[dict[str, str | int]]:
412+
async def get_environments(self, organization: str, repo: str) -> list[dict[str, Any]]:
395413
all_environments = []
396414

397415
url = f"{self.config.integration.properties['url']}/repos/{organization}/{repo}/environments"
@@ -409,7 +427,7 @@ async def get_environments(self, organization: str, repo: str) -> list[dict[str,
409427

410428
async def get_deployments(
411429
self, organization: str, repo: str, environments: Iterable[str] | None = None
412-
) -> list[dict[str, str | int]]:
430+
) -> list[dict[str, Any]]:
413431
all_deployments = []
414432
cursor = None
415433
while True:
@@ -435,8 +453,12 @@ async def get_deployments(
435453
return all_deployments
436454

437455
async def get_commits(
438-
self, organization: str, repo: str, branch: str, *, exclude_merge_commits: bool = True
439-
) -> list[dict[str, str | int]]:
456+
self, organization: str, repo: str, branch: str | None, *, exclude_merge_commits: bool = True
457+
) -> list[dict[str, Any]]:
458+
if branch is None or not branch:
459+
self.logger.warning("Unable to fetch commits: branch not specified")
460+
return []
461+
440462
all_commits = []
441463

442464
query_builder = partial(

galaxy/integrations/github/main.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
__all__ = ["Github"]
1616

1717

18+
DEFAULT_BRANCH_NAME: str = "main"
19+
20+
1821
class Github(Integration):
1922
_methods = []
2023

@@ -91,7 +94,7 @@ async def repository(self) -> None:
9194
"owner": metadata["owner"]["login"],
9295
"link": metadata["html_url"],
9396
"metadata": metadata,
94-
"default_branch": content.get("defaultBranchRef", {}).get("name"),
97+
"default_branch": (content.get("defaultBranchRef") or {}).get("name") or DEFAULT_BRANCH_NAME,
9598
"content": content,
9699
}
97100

@@ -140,6 +143,7 @@ async def pull_request(self) -> list[dict]:
140143
self.repository_to_pull_requests[repo_id] = await self.client.get_pull_requests(
141144
repo["owner"], repo["slug"], self.PULL_REQUEST_STATUS_TO_FETCH
142145
)
146+
143147
prs_mapped.extend(
144148
(
145149
await self.mapper.process(
@@ -157,7 +161,7 @@ async def pull_request(self) -> list[dict]:
157161
self.logger.info(f"Found {len(prs_mapped)} pull requests from the last {self.client.days_of_history} days")
158162
new_entities = await self.register_inactive_users(inactive_usernames)
159163
if new_entities:
160-
self.logger.info(f"Found {len(new_entities) - 1} inactive members associated to deployments")
164+
self.logger.info(f"Found {len(new_entities) - 1} inactive members associated to pull requests")
161165

162166
return new_entities + prs_mapped
163167

@@ -206,7 +210,7 @@ async def workflow_run(self) -> list[dict]:
206210
workflows_runs_mapped = []
207211
inactive_usernames = set()
208212
for repo_id, repo in self.repositories.items():
209-
for workflow in self.repository_to_workflows[repo_id]:
213+
for workflow in self.repository_to_workflows.get(repo_id) or []:
210214
self.workflows_to_runs[repo_id] = await self.client.get_workflow_runs(
211215
repo["owner"], repo["slug"], workflow["id"]
212216
)
@@ -221,14 +225,14 @@ async def workflow_run(self) -> list[dict]:
221225
)
222226

223227
inactive_usernames.update(
224-
get_inactive_usernames_from_workflow_runs(self.repository_to_pull_requests[repo_id], self.users)
228+
get_inactive_usernames_from_workflow_runs(self.workflows_to_runs[repo_id], self.users)
225229
)
226230
self.logger.info(
227-
f"Found {len(workflows_runs_mapped)} workflow runs from the last {self.client.days_of_history} days"
231+
"Found %d workflow runs from the last %d days", len(workflows_runs_mapped), self.client.days_of_history
228232
)
229233
new_entities = await self.register_inactive_users(inactive_usernames)
230234
if new_entities:
231-
self.logger.info(f"Found {len(new_entities) - 1} inactive members associated to deployments")
235+
self.logger.info(f"Found {len(new_entities) - 1} inactive members associated to workflow runs")
232236

233237
return workflows_runs_mapped
234238

@@ -255,7 +259,7 @@ async def deployments(self) -> list[dict]:
255259
inactive_usernames = set()
256260
for repo_id, repo in self.repositories.items():
257261
self.repository_to_deployments[repo_id] = []
258-
for environment in self.repository_to_environments[repo_id]:
262+
for environment in self.repository_to_environments.get(repo_id) or []:
259263
repo_env_deployments = await self.client.get_deployments(
260264
repo["owner"], repo["slug"], [environment["name"]]
261265
)

0 commit comments

Comments
 (0)