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.

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