recce-nightly 1.2.0.20250506__py3-none-any.whl → 1.26.0.20251124__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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +27 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +810 -480
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +119 -51
- recce/cli.py +1299 -323
- recce/config.py +42 -33
- recce/connect_to_cloud.py +138 -0
- recce/core.py +55 -47
- recce/data/404.html +1 -1
- recce/data/__next.__PAGE__.txt +10 -0
- recce/data/__next._full.txt +23 -0
- recce/data/__next._head.txt +8 -0
- recce/data/__next._index.txt +8 -0
- recce/data/__next._tree.txt +5 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_buildManifest.js +11 -0
- recce/data/_next/static/52aV_JrNUZU6dMFgvTQEO/_clientMiddlewareManifest.json +1 -0
- recce/data/_next/static/chunks/02b996c7f6a29a06.js +4 -0
- recce/data/_next/static/chunks/19c10d219a6a21ff.js +1 -0
- recce/data/_next/static/chunks/2df9ec28a061971d.js +11 -0
- recce/data/_next/static/chunks/3098c987393bda15.js +1 -0
- recce/data/_next/static/chunks/393dc43e483f717a.css +2 -0
- recce/data/_next/static/chunks/399e8d91a7e45073.js +2 -0
- recce/data/_next/static/chunks/4d0186f631230245.js +1 -0
- recce/data/_next/static/chunks/5794ba9e10a9c060.js +11 -0
- recce/data/_next/static/chunks/715761c929a3f28b.js +110 -0
- recce/data/_next/static/chunks/71f88fcc615bf282.js +1 -0
- recce/data/_next/static/chunks/80d2a95eaf1201ea.js +1 -0
- recce/data/_next/static/chunks/9979c6109bbbee35.js +1 -0
- recce/data/_next/static/chunks/99d638224186c118.js +1 -0
- recce/data/_next/static/chunks/d003eb36240e92f3.js +1 -0
- recce/data/_next/static/chunks/d3167cdfec4fc351.js +1 -0
- recce/data/_next/static/chunks/e124bccf574a3361.css +1 -0
- recce/data/_next/static/chunks/f40141db1bdb46f0.css +6 -0
- recce/data/_next/static/chunks/fcc53a88741a52f9.js +1 -0
- recce/data/_next/static/chunks/turbopack-b1920d28cfb1f28d.js +3 -0
- recce/data/_next/static/media/favicon.a8d38d84.ico +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.d80d830d.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.f9d58125.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.076c2a93.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.a4fa76b5.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.cde454cc.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.d5761935.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.40ec0659.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.b671449b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.9f7b8541.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.f9eb854e.woff2 +0 -0
- recce/data/_next/static/media/reload-image.7aa931c7.svg +4 -0
- recce/data/_not-found/__next._full.txt +17 -0
- recce/data/_not-found/__next._head.txt +8 -0
- recce/data/_not-found/__next._index.txt +8 -0
- recce/data/_not-found/__next._not-found.__PAGE__.txt +5 -0
- recce/data/_not-found/__next._not-found.txt +4 -0
- recce/data/_not-found/__next._tree.txt +3 -0
- recce/data/_not-found.html +1 -0
- recce/data/_not-found.txt +17 -0
- recce/data/auth_callback.html +68 -0
- recce/data/imgs/reload-image.svg +4 -0
- recce/data/index.html +1 -27
- recce/data/index.txt +23 -7
- recce/diff.py +6 -12
- recce/event/__init__.py +86 -74
- recce/event/collector.py +33 -22
- recce/event/track.py +49 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/mcp_server.py +716 -0
- recce/models/__init__.py +4 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +131 -28
- recce/pull_request.py +27 -25
- recce/run.py +165 -121
- recce/server.py +303 -111
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +632 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +119 -0
- recce/state/state_loader.py +174 -0
- recce/summary.py +188 -143
- recce/tasks/__init__.py +19 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +82 -18
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +152 -86
- recce/tasks/query.py +139 -87
- recce/tasks/rowcount.py +37 -31
- recce/tasks/schema.py +18 -15
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/__init__.py +3 -0
- recce/util/api_token.py +80 -0
- recce/util/breaking.py +87 -85
- recce/util/cll.py +274 -219
- recce/util/io.py +22 -17
- recce/util/lineage.py +65 -16
- recce/util/logger.py +1 -1
- recce/util/onboarding_state.py +45 -0
- recce/util/perf_tracking.py +85 -0
- recce/util/recce_cloud.py +322 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- recce_cloud/__init__.py +24 -0
- recce_cloud/api/__init__.py +17 -0
- recce_cloud/api/base.py +111 -0
- recce_cloud/api/client.py +150 -0
- recce_cloud/api/exceptions.py +26 -0
- recce_cloud/api/factory.py +63 -0
- recce_cloud/api/github.py +76 -0
- recce_cloud/api/gitlab.py +82 -0
- recce_cloud/artifact.py +57 -0
- recce_cloud/ci_providers/__init__.py +9 -0
- recce_cloud/ci_providers/base.py +82 -0
- recce_cloud/ci_providers/detector.py +147 -0
- recce_cloud/ci_providers/github_actions.py +136 -0
- recce_cloud/ci_providers/gitlab_ci.py +130 -0
- recce_cloud/cli.py +245 -0
- recce_cloud/upload.py +214 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/METADATA +68 -37
- recce_nightly-1.26.0.20251124.dist-info/RECORD +180 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/WHEEL +1 -1
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/top_level.txt +1 -0
- tests/adapter/dbt_adapter/conftest.py +9 -5
- tests/adapter/dbt_adapter/dbt_test_helper.py +37 -22
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +656 -41
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/recce_cloud/__init__.py +0 -0
- tests/recce_cloud/test_ci_providers.py +351 -0
- tests/recce_cloud/test_cli.py +372 -0
- tests/recce_cloud/test_client.py +273 -0
- tests/recce_cloud/test_platform_clients.py +333 -0
- tests/tasks/conftest.py +1 -1
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +339 -15
- tests/tasks/test_query.py +46 -46
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +174 -60
- tests/test_cli_mcp_optional.py +45 -0
- tests/test_cloud_listing_cli.py +324 -0
- tests/test_config.py +7 -9
- tests/test_connect_to_cloud.py +82 -0
- tests/test_core.py +151 -4
- tests/test_dbt.py +7 -7
- tests/test_mcp_server.py +332 -0
- tests/test_pull_request.py +1 -1
- tests/test_server.py +25 -19
- tests/test_summary.py +29 -17
- recce/data/_next/static/Kcbs3GEIyH2LxgLYat0es/_buildManifest.js +0 -1
- recce/data/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js +0 -1
- recce/data/_next/static/chunks/29e3cc0d-8c150e37dff9631b.js +0 -1
- recce/data/_next/static/chunks/368-7587b306577df275.js +0 -65
- recce/data/_next/static/chunks/36e1c10d-bb0210cbd6573a8d.js +0 -1
- recce/data/_next/static/chunks/3998a672-eaad84bdd88cc73e.js +0 -1
- recce/data/_next/static/chunks/3a92ee20-3b5d922d4157af5e.js +0 -1
- recce/data/_next/static/chunks/450c323b-1bb5db526e54435a.js +0 -1
- recce/data/_next/static/chunks/47d8844f-79a1b53c66a7d7ec.js +0 -1
- recce/data/_next/static/chunks/6dc81886-c94b9b91bc2c3caf.js +0 -1
- recce/data/_next/static/chunks/6ef81909-694dc38134099299.js +0 -1
- recce/data/_next/static/chunks/700-3b65fc3666820d00.js +0 -2
- recce/data/_next/static/chunks/7a8a3e83-d7fa409d97b38b2b.js +0 -1
- recce/data/_next/static/chunks/7f27ae6c-413f6b869a04183a.js +0 -1
- recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
- recce/data/_next/static/chunks/9746af58-d74bef4d03eea6ab.js +0 -1
- recce/data/_next/static/chunks/a30376cd-7d806e1602f2dc3a.js +0 -1
- recce/data/_next/static/chunks/app/_not-found/page-8a886fa0855c3105.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-cee661090afbd6aa.js +0 -1
- recce/data/_next/static/chunks/b63b1b3f-7395c74e11a14e95.js +0 -1
- recce/data/_next/static/chunks/c132bf7d-8102037f9ccf372a.js +0 -1
- recce/data/_next/static/chunks/c1ceaa8b-a1e442154d23515e.js +0 -1
- recce/data/_next/static/chunks/cd9f8d63-cf0d5a7b0f7a92e8.js +0 -54
- recce/data/_next/static/chunks/ce84277d-f42c2c58049cea2d.js +0 -1
- recce/data/_next/static/chunks/e24bf851-0f8cbc99656833e7.js +0 -1
- recce/data/_next/static/chunks/fee69bc6-f17d36c080742e74.js +0 -1
- recce/data/_next/static/chunks/framework-ded83d71b51ce901.js +0 -1
- recce/data/_next/static/chunks/main-a0859f1f36d0aa6c.js +0 -1
- recce/data/_next/static/chunks/main-app-0225a2255968e566.js +0 -1
- recce/data/_next/static/chunks/pages/_app-d5672bf3d8b6371b.js +0 -1
- recce/data/_next/static/chunks/pages/_error-ed75be3f25588548.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce/data/_next/static/css/c9ecb46a4b21c126.css +0 -14
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2 +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-800-normal.31d693bb.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.7e2c1e62.woff +0 -0
- recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.6f8fa298.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-800-normal.97e20d5e.woff +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.013b84f9.woff2 +0 -0
- recce/data/_next/static/media/montserrat-latin-ext-800-normal.aff52ab0.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.5f21869b.woff +0 -0
- recce/data/_next/static/media/montserrat-vietnamese-800-normal.c0035377.woff2 +0 -0
- recce/state.py +0 -753
- recce_nightly-1.2.0.20250506.dist-info/RECORD +0 -142
- tests/test_state.py +0 -123
- /recce/data/_next/static/{Kcbs3GEIyH2LxgLYat0es → 52aV_JrNUZU6dMFgvTQEO}/_ssgManifest.js +0 -0
- /recce/data/_next/static/chunks/{polyfills-42372ed130431b0a.js → a6dad97d9634a72d.js} +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.2.0.20250506.dist-info → recce_nightly-1.26.0.20251124.dist-info}/licenses/LICENSE +0 -0
recce/github.py
CHANGED
|
@@ -3,10 +3,10 @@ import os
|
|
|
3
3
|
import re
|
|
4
4
|
import zipfile
|
|
5
5
|
from datetime import datetime
|
|
6
|
-
from typing import List,
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
|
-
from github import Artifact, Github,
|
|
9
|
+
from github import Artifact, Auth, Github, PullRequest, UnknownObjectException
|
|
10
10
|
|
|
11
11
|
from recce.git import current_branch, hosting_repo
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ def download_artifact(github_token: str, artifact: Artifact) -> List[str]:
|
|
|
16
16
|
Download the artifact from the Github.
|
|
17
17
|
"""
|
|
18
18
|
headers = {
|
|
19
|
-
|
|
19
|
+
"Authorization": f"Bearer {github_token}",
|
|
20
20
|
}
|
|
21
21
|
r = requests.get(artifact.archive_download_url, headers=headers)
|
|
22
22
|
r.raise_for_status()
|
|
@@ -27,17 +27,17 @@ def download_artifact(github_token: str, artifact: Artifact) -> List[str]:
|
|
|
27
27
|
|
|
28
28
|
def recce_ci_artifact(**kwargs):
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
Download the artifact from the GitHub CI.
|
|
31
31
|
"""
|
|
32
|
-
from rich.console import Console
|
|
33
|
-
|
|
34
32
|
import git
|
|
33
|
+
|
|
35
34
|
# Authentication is defined via github.Auth
|
|
36
35
|
from github import Auth, Github
|
|
36
|
+
from rich.console import Console
|
|
37
37
|
|
|
38
38
|
console = Console()
|
|
39
|
-
github_token = kwargs.get(
|
|
40
|
-
github_repository = kwargs.get(
|
|
39
|
+
github_token = kwargs.get("github_token")
|
|
40
|
+
github_repository = kwargs.get("github_repository")
|
|
41
41
|
|
|
42
42
|
if github_token is None:
|
|
43
43
|
console.print("[[red]Error[/red]] Missing GitHub token. Please provide a GitHub token.")
|
|
@@ -70,11 +70,11 @@ def recce_ci_artifact(**kwargs):
|
|
|
70
70
|
|
|
71
71
|
head_branch = github_repo.get_branch(current_branch)
|
|
72
72
|
|
|
73
|
-
console.rule(
|
|
74
|
-
console.print(f
|
|
75
|
-
console.print(f
|
|
73
|
+
console.rule("GitHub Repository Information")
|
|
74
|
+
console.print(f"Repository: {github_repository}")
|
|
75
|
+
console.print(f"Current Branch: {current_branch}")
|
|
76
76
|
|
|
77
|
-
pull_requests = github_repo.get_pulls(head=f
|
|
77
|
+
pull_requests = github_repo.get_pulls(head=f"{github_owner}:{current_branch}")
|
|
78
78
|
if pull_requests.totalCount == 0:
|
|
79
79
|
# No pull request found for the current branch
|
|
80
80
|
console.print("[[red]Error[/red]] No pull request found for the current branch.")
|
|
@@ -82,32 +82,32 @@ def recce_ci_artifact(**kwargs):
|
|
|
82
82
|
|
|
83
83
|
pr = pull_requests[0]
|
|
84
84
|
|
|
85
|
-
console.rule(
|
|
86
|
-
console.print(f
|
|
87
|
-
console.print(f
|
|
88
|
-
console.print(f
|
|
89
|
-
console.print(f
|
|
85
|
+
console.rule("GitHub Pull Request Information")
|
|
86
|
+
console.print(f"{pr.title} - {pr.html_url}")
|
|
87
|
+
console.print(f"State: {pr.state.title()}")
|
|
88
|
+
console.print(f"Author: {pr.user.name}")
|
|
89
|
+
console.print(f"Created At: {pr.created_at}")
|
|
90
90
|
|
|
91
91
|
workflow_runs = github_repo.get_workflow_runs(
|
|
92
|
-
event=
|
|
93
|
-
status=
|
|
92
|
+
event="pull_request",
|
|
93
|
+
status="success",
|
|
94
94
|
branch=head_branch,
|
|
95
95
|
)
|
|
96
96
|
if workflow_runs.totalCount == 0:
|
|
97
97
|
console.print("[[yellow]Skip[/yellow]] No successful workflow runs found.")
|
|
98
98
|
return 0
|
|
99
99
|
last_workflow_run = workflow_runs[0]
|
|
100
|
-
console.rule(
|
|
101
|
-
console.print(f
|
|
100
|
+
console.rule("GitHub Workflow Run Information")
|
|
101
|
+
console.print(f"Last Workflow Run: {last_workflow_run.name} {last_workflow_run.html_url}")
|
|
102
102
|
artifacts = last_workflow_run.get_artifacts()
|
|
103
103
|
if artifacts.totalCount == 0:
|
|
104
104
|
console.print("[[yellow]Skip[/yellow]] No artifacts found.")
|
|
105
105
|
return 0
|
|
106
106
|
for artifact in artifacts:
|
|
107
|
-
console.print(f
|
|
107
|
+
console.print(f"Artifact: {artifact.name} {artifact.archive_download_url}")
|
|
108
108
|
artifact_files = download_artifact(github_token, artifact)
|
|
109
109
|
console.print(f'Extracted Files: {", ".join(artifact_files)}')
|
|
110
|
-
console.rule(
|
|
110
|
+
console.rule("Complete")
|
|
111
111
|
return 0
|
|
112
112
|
|
|
113
113
|
|
|
@@ -121,7 +121,7 @@ def get_pull_request(branch, owner, repo_name, github_token=None) -> Tuple[Optio
|
|
|
121
121
|
|
|
122
122
|
try:
|
|
123
123
|
repo = g.get_repo(f"{owner}/{repo_name}")
|
|
124
|
-
pulls = repo.get_pulls(state=
|
|
124
|
+
pulls = repo.get_pulls(state="open")
|
|
125
125
|
|
|
126
126
|
for pr in pulls:
|
|
127
127
|
if pr.head.ref == branch:
|
|
@@ -129,7 +129,10 @@ def get_pull_request(branch, owner, repo_name, github_token=None) -> Tuple[Optio
|
|
|
129
129
|
|
|
130
130
|
except UnknownObjectException:
|
|
131
131
|
if github_token is not None:
|
|
132
|
-
return
|
|
132
|
+
return (
|
|
133
|
+
None,
|
|
134
|
+
f"Repository {owner}/{repo_name} not found. If it is private repo, please add the 'repo' scope to the token.",
|
|
135
|
+
)
|
|
133
136
|
|
|
134
137
|
return None, None
|
|
135
138
|
|
|
@@ -139,53 +142,54 @@ def recce_pr_information(github_token=None) -> Tuple[Optional[type(PullRequest)]
|
|
|
139
142
|
repo = hosting_repo()
|
|
140
143
|
|
|
141
144
|
if not repo:
|
|
142
|
-
return None,
|
|
143
|
-
if
|
|
144
|
-
return None,
|
|
145
|
+
return None, "This is not a git repository."
|
|
146
|
+
if "/" not in repo:
|
|
147
|
+
return None, "This is not a GitHub repository."
|
|
145
148
|
|
|
146
|
-
owner, repo_name = repo.split(
|
|
149
|
+
owner, repo_name = repo.split("/")
|
|
147
150
|
|
|
148
151
|
github_token = github_token if github_token else os.getenv("GITHUB_TOKEN")
|
|
149
152
|
return get_pull_request(branch, owner, repo_name, github_token)
|
|
150
153
|
|
|
151
154
|
|
|
152
155
|
def is_github_codespace():
|
|
153
|
-
return os.getenv(
|
|
156
|
+
return os.getenv("CODESPACES") == "true"
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
def get_github_codespace_name():
|
|
157
|
-
return os.getenv(
|
|
160
|
+
return os.getenv("CODESPACE_NAME")
|
|
158
161
|
|
|
159
162
|
|
|
160
163
|
def get_github_codespace_info():
|
|
161
164
|
if is_github_codespace() is False:
|
|
162
165
|
return None
|
|
163
166
|
|
|
164
|
-
codespace_name = os.environ.get(
|
|
165
|
-
github_token = os.environ.get(
|
|
167
|
+
codespace_name = os.environ.get("CODESPACE_NAME")
|
|
168
|
+
github_token = os.environ.get("GITHUB_TOKEN")
|
|
166
169
|
|
|
167
170
|
response = requests.get(
|
|
168
|
-
f
|
|
171
|
+
f"https://api.github.com/user/codespaces/{codespace_name}",
|
|
169
172
|
headers={
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
173
|
+
"Accept": "application/vnd.github+json",
|
|
174
|
+
"Authorization": f"token {github_token}",
|
|
175
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
176
|
+
},
|
|
177
|
+
)
|
|
174
178
|
|
|
175
179
|
if response.status_code != 200:
|
|
176
180
|
return None
|
|
177
181
|
codespace_info = response.json()
|
|
178
182
|
|
|
179
183
|
return dict(
|
|
180
|
-
name=codespace_info.get(
|
|
181
|
-
machine=codespace_info.get(
|
|
182
|
-
prebuild=codespace_info.get(
|
|
183
|
-
created_at=codespace_info.get(
|
|
184
|
-
updated_at=codespace_info.get(
|
|
185
|
-
last_used_at=codespace_info.get(
|
|
186
|
-
state=codespace_info.get(
|
|
187
|
-
location=codespace_info.get(
|
|
188
|
-
idle_timeout_minutes=codespace_info.get(
|
|
184
|
+
name=codespace_info.get("name"),
|
|
185
|
+
machine=codespace_info.get("machine"),
|
|
186
|
+
prebuild=codespace_info.get("prebuild"),
|
|
187
|
+
created_at=codespace_info.get("created_at"),
|
|
188
|
+
updated_at=codespace_info.get("updated_at"),
|
|
189
|
+
last_used_at=codespace_info.get("last_used_at"),
|
|
190
|
+
state=codespace_info.get("state"),
|
|
191
|
+
location=codespace_info.get("location"),
|
|
192
|
+
idle_timeout_minutes=codespace_info.get("idle_timeout_minutes"),
|
|
189
193
|
)
|
|
190
194
|
|
|
191
195
|
|
|
@@ -194,25 +198,25 @@ def get_github_codespace_available_at(codespace):
|
|
|
194
198
|
return None
|
|
195
199
|
|
|
196
200
|
def search_in_file(file_path, search_string):
|
|
197
|
-
with open(file_path,
|
|
201
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
198
202
|
for _, line in enumerate(f, 1):
|
|
199
203
|
if search_string in line:
|
|
200
204
|
return line
|
|
201
205
|
return None
|
|
202
206
|
|
|
203
207
|
def extract_datatime(log_line):
|
|
204
|
-
pattern = r
|
|
208
|
+
pattern = r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}).*\]"
|
|
205
209
|
match = re.search(pattern, log_line)
|
|
206
210
|
if match:
|
|
207
211
|
datetime_str = match.group(1)
|
|
208
|
-
return datetime.strptime(datetime_str,
|
|
212
|
+
return datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S.%f")
|
|
209
213
|
return None
|
|
210
214
|
|
|
211
|
-
github_codepsce_log_dir =
|
|
215
|
+
github_codepsce_log_dir = "/tmp/codespaces_logs"
|
|
212
216
|
try:
|
|
213
217
|
log_file = os.listdir(github_codepsce_log_dir)[-1] # Get the latest log file
|
|
214
|
-
start_monitor_line = search_in_file(f
|
|
218
|
+
start_monitor_line = search_in_file(f"{github_codepsce_log_dir}/{log_file}", "Starting monitor")
|
|
215
219
|
return extract_datatime(start_monitor_line)
|
|
216
220
|
except Exception:
|
|
217
221
|
# If there is any error, use the updated_at time from the codespace info
|
|
218
|
-
return datetime.fromisoformat(codespace.get(
|
|
222
|
+
return datetime.fromisoformat(codespace.get("updated_at"))
|