recce-nightly 1.15.0.20250812__py3-none-any.whl → 1.16.0.20250814__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/cli.py +13 -4
- recce/data/404.html +1 -1
- recce/data/_next/static/chunks/{367-ab8b16dd5f8586ca.js → 367-ae35f5a152ee1ce5.js} +1 -1
- recce/data/_next/static/chunks/app/page-501b751b630a2ef8.js +1 -0
- recce/data/index.html +1 -1
- recce/data/index.txt +4 -4
- recce/state/__init__.py +31 -0
- recce/state/cloud.py +384 -0
- recce/state/const.py +26 -0
- recce/state/local.py +56 -0
- recce/state/state.py +118 -0
- recce/state/state_loader.py +179 -0
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/METADATA +1 -1
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/RECORD +24 -20
- tests/test_cli.py +3 -3
- tests/test_core.py +147 -0
- tests/test_server.py +6 -6
- recce/data/_next/static/chunks/app/page-e8f798c2ae3f59c2.js +0 -1
- recce/state.py +0 -865
- tests/test_state.py +0 -134
- /recce/data/_next/static/{2Vkj92N9DmkZ6Tr4mOIoQ → ee0xgtj4tA9NSgi1ChDxe}/_buildManifest.js +0 -0
- /recce/data/_next/static/{2Vkj92N9DmkZ6Tr4mOIoQ → ee0xgtj4tA9NSgi1ChDxe}/_ssgManifest.js +0 -0
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/WHEEL +0 -0
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/licenses/LICENSE +0 -0
- {recce_nightly-1.15.0.20250812.dist-info → recce_nightly-1.16.0.20250814.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Dict, Literal, Optional, Tuple, Union, final
|
|
6
|
+
|
|
7
|
+
from recce.exceptions import RecceException
|
|
8
|
+
from recce.pull_request import fetch_pr_metadata
|
|
9
|
+
|
|
10
|
+
from ..util.io import SupportedFileTypes, file_io_factory
|
|
11
|
+
from .const import (
|
|
12
|
+
RECCE_CLOUD_TOKEN_MISSING,
|
|
13
|
+
)
|
|
14
|
+
from .state import RecceState
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("uvicorn")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RecceStateLoader(ABC):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
review_mode: bool = False,
|
|
23
|
+
cloud_mode: bool = False,
|
|
24
|
+
state_file: Optional[str] = None,
|
|
25
|
+
cloud_options: Optional[Dict[str, str]] = None,
|
|
26
|
+
initial_state: Optional[RecceState] = None,
|
|
27
|
+
):
|
|
28
|
+
self.review_mode = review_mode
|
|
29
|
+
self.cloud_mode = cloud_mode
|
|
30
|
+
self.state_file = state_file
|
|
31
|
+
self.cloud_options = cloud_options or {}
|
|
32
|
+
self.error_message = None
|
|
33
|
+
self.hint_message = None
|
|
34
|
+
self.state: RecceState | None = initial_state
|
|
35
|
+
self.state_lock = threading.Lock()
|
|
36
|
+
self.state_etag = None
|
|
37
|
+
self.pr_info = None
|
|
38
|
+
self.catalog: Literal["github", "preview"] = "github"
|
|
39
|
+
self.share_id = None
|
|
40
|
+
|
|
41
|
+
if self.cloud_mode:
|
|
42
|
+
if self.cloud_options.get("github_token"):
|
|
43
|
+
self.catalog = "github"
|
|
44
|
+
self.pr_info = fetch_pr_metadata(
|
|
45
|
+
cloud=self.cloud_mode, github_token=self.cloud_options.get("github_token")
|
|
46
|
+
)
|
|
47
|
+
if self.pr_info.id is None:
|
|
48
|
+
raise RecceException("Cannot get the pull request information from GitHub.")
|
|
49
|
+
elif self.cloud_options.get("api_token"):
|
|
50
|
+
self.catalog = "preview"
|
|
51
|
+
self.share_id = self.cloud_options.get("share_id")
|
|
52
|
+
else:
|
|
53
|
+
raise RecceException(RECCE_CLOUD_TOKEN_MISSING.error_message)
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def verify(self) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Verify the state loader configuration.
|
|
59
|
+
Returns:
|
|
60
|
+
bool: True if the configuration is valid, False otherwise.
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def token(self):
|
|
66
|
+
return self.cloud_options.get("github_token") or self.cloud_options.get("api_token")
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def error_and_hint(self) -> (Union[str, None], Union[str, None]):
|
|
70
|
+
return self.error_message, self.hint_message
|
|
71
|
+
|
|
72
|
+
def update(self, state: RecceState):
|
|
73
|
+
self.state = state
|
|
74
|
+
|
|
75
|
+
@final
|
|
76
|
+
def load(self, refresh=False) -> RecceState:
|
|
77
|
+
if self.state is not None and refresh is False:
|
|
78
|
+
return self.state
|
|
79
|
+
self.state_lock.acquire()
|
|
80
|
+
try:
|
|
81
|
+
self.state, self.state_etag = self._load_state()
|
|
82
|
+
finally:
|
|
83
|
+
self.state_lock.release()
|
|
84
|
+
return self.state
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def _load_state(self) -> Tuple[RecceState, str]:
|
|
88
|
+
"""
|
|
89
|
+
Load the state from the specified source (file or cloud).
|
|
90
|
+
Returns:
|
|
91
|
+
RecceState: The loaded state object.
|
|
92
|
+
str: The etag of the state file (if applicable).
|
|
93
|
+
"""
|
|
94
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
95
|
+
|
|
96
|
+
def save_as(self, state_file: str, state: RecceState = None):
|
|
97
|
+
if self.cloud_mode:
|
|
98
|
+
raise Exception("Cannot save the state to Recce Cloud.")
|
|
99
|
+
|
|
100
|
+
self.state_file = state_file
|
|
101
|
+
self.export(state)
|
|
102
|
+
|
|
103
|
+
@final
|
|
104
|
+
def export(self, state: RecceState = None) -> Union[str, None]:
|
|
105
|
+
if state is not None:
|
|
106
|
+
self.update(state)
|
|
107
|
+
|
|
108
|
+
start_time = time.time()
|
|
109
|
+
self.state_lock.acquire()
|
|
110
|
+
try:
|
|
111
|
+
message, state_etag = self._export_state()
|
|
112
|
+
self.state_etag = state_etag
|
|
113
|
+
end_time = time.time()
|
|
114
|
+
elapsed_time = end_time - start_time
|
|
115
|
+
finally:
|
|
116
|
+
self.state_lock.release()
|
|
117
|
+
logger.info(f"Store state completed in {elapsed_time:.2f} seconds")
|
|
118
|
+
return message
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def _export_state(self, state: RecceState = None) -> Tuple[Union[str, None], str]:
|
|
122
|
+
"""
|
|
123
|
+
Export the current Recce state to a file or cloud storage.
|
|
124
|
+
Returns:
|
|
125
|
+
str: A message indicating the result of the export operation.
|
|
126
|
+
str: The etag of the exported state file (if applicable).
|
|
127
|
+
"""
|
|
128
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
129
|
+
|
|
130
|
+
def _export_state_to_file(self, file_path: str, file_type: SupportedFileTypes = SupportedFileTypes.FILE) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Store the state to a file. Store happens when terminating the server or run instance.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
json_data = self.state.to_json()
|
|
136
|
+
io = file_io_factory(file_type)
|
|
137
|
+
|
|
138
|
+
io.write(file_path, json_data)
|
|
139
|
+
return f"The state file is stored at '{file_path}'"
|
|
140
|
+
|
|
141
|
+
def refresh(self):
|
|
142
|
+
new_state = self.load(refresh=True)
|
|
143
|
+
return new_state
|
|
144
|
+
|
|
145
|
+
def check_conflict(self) -> bool:
|
|
146
|
+
if not self.cloud_mode:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
metadata = self._get_metadata_from_recce_cloud()
|
|
150
|
+
if not metadata:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
state_etag = metadata.get("etag")
|
|
154
|
+
return state_etag != self.state_etag
|
|
155
|
+
|
|
156
|
+
def info(self):
|
|
157
|
+
if self.state is None:
|
|
158
|
+
self.error_message = "No state is loaded."
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
state_info = {
|
|
162
|
+
"mode": "cloud" if self.cloud_mode else "local",
|
|
163
|
+
"source": None,
|
|
164
|
+
}
|
|
165
|
+
if self.cloud_mode:
|
|
166
|
+
state_info["source"] = "Recce Cloud"
|
|
167
|
+
state_info["pull_request"] = self.pr_info
|
|
168
|
+
else:
|
|
169
|
+
state_info["source"] = self.state_file
|
|
170
|
+
return state_info
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def purge(self) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Purge the state file or cloud storage.
|
|
176
|
+
Returns:
|
|
177
|
+
bool: True if the purge was successful, False otherwise.
|
|
178
|
+
"""
|
|
179
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
recce/VERSION,sha256=
|
|
1
|
+
recce/VERSION,sha256=Q2iHeFOuCVI1u8mLRlEEA_5cR9HurbaSvimgqL_p-xY,16
|
|
2
2
|
recce/__init__.py,sha256=yNb0QT-yoStex0VZALNJvUwtPLommoVCStcow31guqo,2392
|
|
3
3
|
recce/artifact.py,sha256=ds0erp0-gGnH4Nx2vvapWR02pX8FmKQcqcd6QGqiVPw,6536
|
|
4
|
-
recce/cli.py,sha256=
|
|
4
|
+
recce/cli.py,sha256=ECfV2IQg79_kOElCYBxd2ubBeG6KfoQqf6xcn4Tm9D4,44732
|
|
5
5
|
recce/config.py,sha256=A4CbKcIdwQ4A9Q2ba4riHUshVQw1D-qDelMOdlt2khU,4661
|
|
6
6
|
recce/connect_to_cloud.py,sha256=_SkX2pdyXa9FNyaHvItyYVPF2nZxy2JnCyd_o_psh2Y,4750
|
|
7
7
|
recce/core.py,sha256=MtBWxemvCQDdUITwkU2JyuQYqcjlA0a76M8Kg53ECxQ,10754
|
|
@@ -12,7 +12,6 @@ recce/github.py,sha256=PEpM6ZRiinsPbXSWj4aJCKbZrN1jUXzpzAfJq_CGah4,7420
|
|
|
12
12
|
recce/pull_request.py,sha256=aW0B1NE2LUKTam1S4TQ7smXB9KLE1DV8GnyBqNXA6j8,3832
|
|
13
13
|
recce/run.py,sha256=PNafwUUJdIG8b1o0y1QuvjACpA3E-5a3keH45CrWHN0,14044
|
|
14
14
|
recce/server.py,sha256=qW81QRZVYEfeDT0n6FwsN9TGhzk6-Zx2mPUjjy01DRM,22473
|
|
15
|
-
recce/state.py,sha256=p4CBneaat-OFwhABfj_zG6n0mPGzrYE-F5xi28Cnzuc,34069
|
|
16
15
|
recce/summary.py,sha256=Mbxvxr9KazR5o9icqhhjiGHsoAiWxQU4PdN7HytBJ1c,19154
|
|
17
16
|
recce/adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
17
|
recce/adapter/base.py,sha256=T_JNeLHgiHSaegw-DbrvHOaYjMyZcjj2Qtg5cWh_fco,3548
|
|
@@ -24,13 +23,11 @@ recce/apis/check_api.py,sha256=KMCXSMl1qqzx2jQgRqCrD4j_cY3EHBbM3H2-t-6saAU,6227
|
|
|
24
23
|
recce/apis/check_func.py,sha256=gktbCcyk3WGvWRJJ-wDnwv7NrIny2nTHWLl1-kdiVRo,4183
|
|
25
24
|
recce/apis/run_api.py,sha256=eOaxOxXDkH59uqGCd4blld7edavUx7JU_DCd2WAYrL8,3416
|
|
26
25
|
recce/apis/run_func.py,sha256=6wC8TDU-h7TLr2VZH7HNsWaUVlQ9HBN5N_dwqfi4lMY,7440
|
|
27
|
-
recce/data/404.html,sha256=
|
|
26
|
+
recce/data/404.html,sha256=iw-kLe4eGZrqOQZUsZIZb80jBni44af496e5rAYJ2vY,59079
|
|
28
27
|
recce/data/auth_callback.html,sha256=H-XfdlAFiw5VU2RpKBVQbwh1AIqJrPHrFA0S09nNJZA,94779
|
|
29
28
|
recce/data/favicon.ico,sha256=B2mBumUOnzvUrXrqNkrc5QfdDXjzEXRcWkWur0fJ6sM,2565
|
|
30
|
-
recce/data/index.html,sha256=
|
|
31
|
-
recce/data/index.txt,sha256=
|
|
32
|
-
recce/data/_next/static/2Vkj92N9DmkZ6Tr4mOIoQ/_buildManifest.js,sha256=xih3ZGwUimzWECoZJsiH_MfTi3cNvK2DbJ_VXPRZHWY,544
|
|
33
|
-
recce/data/_next/static/2Vkj92N9DmkZ6Tr4mOIoQ/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
29
|
+
recce/data/index.html,sha256=wv4yxX1Z8oiPZjGTTqttscAdFXKC71Oh9hh7kB4U64w,77443
|
|
30
|
+
recce/data/index.txt,sha256=5ala8bJ3iBuOFH-bLfoTqrAwzIBMSiIC9VLZK_Coxdo,6403
|
|
34
31
|
recce/data/_next/static/chunks/0376eeba-3db2196398d62270.js,sha256=ef3PXUZocFm_JH0fYU_EfcaLKov9vMUhrat1YvV-IIU,80953
|
|
35
32
|
recce/data/_next/static/chunks/068b80ea-833a129468ee1622.js,sha256=LrX07Wm6qBJ1uVFYOIkium2ZZ7TkhoCzaz5F_D5P-sM,4627
|
|
36
33
|
recce/data/_next/static/chunks/0ddaf06c-c7961285f66460f6.js,sha256=rY2q_JCikDHZGB8z6nHMV7TON5MlsD4B2zxkj_9DFkE,697
|
|
@@ -41,7 +38,7 @@ recce/data/_next/static/chunks/2541941f-2cd3a7c2d629bd33.js,sha256=03PEb_eExziXt
|
|
|
41
38
|
recce/data/_next/static/chunks/273-f3fa401bd2b6fc91.js,sha256=HopLAKoEGP0dFN57b_41PkPqWAdxDAGzXPrVB0tHSt4,1277071
|
|
42
39
|
recce/data/_next/static/chunks/2fc37c1e-910deebeb3d77c90.js,sha256=npExGSkJthgMbnqcvsiEb9b8aCCf7EQLWu1Io-6By0U,3836
|
|
43
40
|
recce/data/_next/static/chunks/338-2e7eed5135c64550.js,sha256=0MRikUq4wS_iboITYISGCEWffqgtIr95bvSA_gVx5KY,264552
|
|
44
|
-
recce/data/_next/static/chunks/367-
|
|
41
|
+
recce/data/_next/static/chunks/367-ae35f5a152ee1ce5.js,sha256=PgRntGGaiUZc3lYFiGE1kddE12rkmi7Ao112KXIDzVo,84706
|
|
45
42
|
recce/data/_next/static/chunks/3a92ee20-0400ffe460c7c803.js,sha256=wI3cuxjDqcE8hRKMg29Mt2dl16jcQ6lBr4JYcLk3_IU,177908
|
|
46
43
|
recce/data/_next/static/chunks/62446465-423c03bb8c1f59b6.js,sha256=xiGo3FRqRpbbBqPwj2UV2zXSfCAPA35TRbyA77kiNMY,2622
|
|
47
44
|
recce/data/_next/static/chunks/6af7f9e9-60aa8706f49dae45.js,sha256=bLrPO62eNT0ez4mooE8DDJITbbm_v6kFB_t8e5hlhx8,767
|
|
@@ -63,7 +60,7 @@ recce/data/_next/static/chunks/main-cd6c104af638214a.js,sha256=pYGvgDmnAThczzBHS
|
|
|
63
60
|
recce/data/_next/static/chunks/polyfills-42372ed130431b0a.js,sha256=CXPB1kyIrcjjyVBBDLWLKI9yEY1ZZbeASUON648vloM,112594
|
|
64
61
|
recce/data/_next/static/chunks/webpack-84df6dd5ae3cf908.js,sha256=_FsecrHvMVA-0EfxZv-v8svx7W50DkEH-jb1NbVSSlU,3580
|
|
65
62
|
recce/data/_next/static/chunks/app/layout-744f0a78e9e50e60.js,sha256=C1sJ1DG8RYccuG-VVTUBO-mmSjiOvJPdxqS9z0maJLk,1410
|
|
66
|
-
recce/data/_next/static/chunks/app/page-
|
|
63
|
+
recce/data/_next/static/chunks/app/page-501b751b630a2ef8.js,sha256=50pQu8x5XHrpV06Ft8mbRvk0PjbYimWV0lnIZTinbSo,166618
|
|
67
64
|
recce/data/_next/static/chunks/app/_not-found/page-c7ef8ed6dc07aaeb.js,sha256=1FE2NEJaSY3ZE8PzMkICM5FcbAbGa8K5ee0ucm2gNfU,2673
|
|
68
65
|
recce/data/_next/static/chunks/pages/_app-73008661edbd5e05.js,sha256=wV9XbJ2FbdpnEztEcX5YE7ecw4ec7LPp2fYhOnLbcoo,240
|
|
69
66
|
recce/data/_next/static/chunks/pages/_error-cf8bbdc3cf76c83f.js,sha256=k0UKnF-ueCkgYTKsUejnYmf31Stt9q5mOjX66XWVqrY,217
|
|
@@ -71,6 +68,8 @@ recce/data/_next/static/css/188a3a1687e2a064.css,sha256=fKJMV0A2hM3cjuRmNhx2V_IL
|
|
|
71
68
|
recce/data/_next/static/css/8edca58d4abcf908.css,sha256=Ab-wk7RhsXgiX5fkJsrCJ-EVvrS1hZtOFpI0uZQSgL0,8093
|
|
72
69
|
recce/data/_next/static/css/abdb9814a3dd18bb.css,sha256=Do5x9FJaOIe-pSBlbk_PP7PQAYFbDXTTl_0PWY4cnBM,1327
|
|
73
70
|
recce/data/_next/static/css/c21263c1520b615b.css,sha256=2TrfHvYUzCBJKLdPzYN6hOAg1n8CoOsJFo36LJPQbOI,13037
|
|
71
|
+
recce/data/_next/static/ee0xgtj4tA9NSgi1ChDxe/_buildManifest.js,sha256=xih3ZGwUimzWECoZJsiH_MfTi3cNvK2DbJ_VXPRZHWY,544
|
|
72
|
+
recce/data/_next/static/ee0xgtj4tA9NSgi1ChDxe/_ssgManifest.js,sha256=Z49s4suAsf5y_GfnQSvm4qtq2ggxEbZPfEDTXjy6XgA,80
|
|
74
73
|
recce/data/_next/static/media/montserrat-cyrillic-800-normal.22628180.woff2,sha256=P5Sx5PNkdTlDYzAdulW_OPRfMokDLi6XTpmS8KJSRoI,11148
|
|
75
74
|
recce/data/_next/static/media/montserrat-cyrillic-800-normal.bd5c9f50.woff,sha256=PFm1Nwt11gmigNvVqPOoDbUFo70fZzUfyZ9S_5f8AnE,10920
|
|
76
75
|
recce/data/_next/static/media/montserrat-cyrillic-ext-800-normal.94a63aea.woff2,sha256=qHQDcPj9VkK-6sE5nxIKvejtNLo5RwBULzyH9nZsEwo,12052
|
|
@@ -95,6 +94,12 @@ recce/models/__init__.py,sha256=F7cgALtdWnwv37R0eEgKZ_yBsMwxWnUfo3jAZ3u6qyU,209
|
|
|
95
94
|
recce/models/check.py,sha256=jjR1SGyczjrqyK-0Zas6ikLIGSgVp54lvfcQA19AniI,1588
|
|
96
95
|
recce/models/run.py,sha256=QK2gvOWvko9YYhd2NLs3BPt5l4MSCZGwpzTAiqx9zJw,1161
|
|
97
96
|
recce/models/types.py,sha256=9ReOyIv3rUD3_cIQfB9Rplb0L2YQuZr4kS9Zai30nB8,5234
|
|
97
|
+
recce/state/__init__.py,sha256=V1FRPTQJUz-uwI3Cn8wDa5Z9bueVs86MR_1Ti4RGfPc,650
|
|
98
|
+
recce/state/cloud.py,sha256=FJS9ktORHT17nBeh1ZDq0PVC21W1Dj8Xz5u4u8X3FLM,15443
|
|
99
|
+
recce/state/const.py,sha256=Me3uVQHi_uZysl3tfpmghPInuKF236X6LfMKVvJlCgA,827
|
|
100
|
+
recce/state/local.py,sha256=bZIkl7cAfyYaGgYEr3uD_JLrtwlHBnu8_o1Qz2djQzw,1920
|
|
101
|
+
recce/state/state.py,sha256=L1cEAl6S9rxTvSIc8ICNwOYoCYK5tR3M0kBo4NTcXK4,3617
|
|
102
|
+
recce/state/state_loader.py,sha256=vGM8HCIv_3PGfcKPgq_GgG_3roloaDL_Am0oTreHmTk,5979
|
|
98
103
|
recce/tasks/__init__.py,sha256=b553AtDHjYROgmMePv_Hv_X3fjh4iEn11gzzpUJz6_o,610
|
|
99
104
|
recce/tasks/core.py,sha256=JFYa1CfgOiRPQ7KVTwMuxJjhMB-pvCwB-xezVt-h3RU,4080
|
|
100
105
|
recce/tasks/dataframe.py,sha256=03UBWwt0DFTXlaEOtnV5i_mxdRKD7UbRayewEL2Ub48,3650
|
|
@@ -120,16 +125,15 @@ recce/util/pydantic_model.py,sha256=KumKuyCjbTzEMsKLE4-b-eZfp0gLhYDdmVtw1-hxiJw,
|
|
|
120
125
|
recce/util/recce_cloud.py,sha256=mqZn63Fh1kZaT0L6AR9K4yqTnGpZBGEWGBHFapLmS_w,9882
|
|
121
126
|
recce/util/singleton.py,sha256=1cU99I0f9tjuMQLMJyLsK1oK3fZJMsO5-TbRHAMXqds,627
|
|
122
127
|
recce/yaml/__init__.py,sha256=EgXYlFeJZchatUClRDXbIC5Oqb2_nBvB2NqItYVihio,1292
|
|
123
|
-
recce_nightly-1.
|
|
128
|
+
recce_nightly-1.16.0.20250814.dist-info/licenses/LICENSE,sha256=CQjjMy9aYPhfe8xG_bcpIfKtNkdxLZ5IOb8oPygtUhY,11343
|
|
124
129
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
125
|
-
tests/test_cli.py,sha256=
|
|
130
|
+
tests/test_cli.py,sha256=4N_F_lg00fGBuK3I9EyaF2Doi7PpKCrjqbuJ5YI1KqE,5467
|
|
126
131
|
tests/test_config.py,sha256=ODDFe_XF6gphmSmmc422dGLBaCCmG-IjDzTkD5SJsJE,1557
|
|
127
132
|
tests/test_connect_to_cloud.py,sha256=b2fgV8L1iQBdEwh6RumMsIIyYg7GtMOMRz1dvE3WRPg,3059
|
|
128
|
-
tests/test_core.py,sha256=
|
|
133
|
+
tests/test_core.py,sha256=WgCFm8Au3YQI-V5UbZ6LA8irMNHRc7NWutIq2Mny0LE,6242
|
|
129
134
|
tests/test_dbt.py,sha256=VzXvdoJNwwEmKNhJJDNB1N_qZYwp1aoJQ1sLcoyRBmk,1316
|
|
130
135
|
tests/test_pull_request.py,sha256=HmZo5MoDaoKSgPwbLxJ3Ur3ajZ7IxhkzJxaOmhg6bwE,3562
|
|
131
|
-
tests/test_server.py,sha256=
|
|
132
|
-
tests/test_state.py,sha256=phSYlyxTx2Sq0K8DXUrfaO5Jc7lgZeeWNNtc9JD3fYc,4988
|
|
136
|
+
tests/test_server.py,sha256=sq03awzG-kvLNJwHaZGDvVjd2uAs502XUKZ2nLNwvRQ,3391
|
|
133
137
|
tests/test_summary.py,sha256=D0WvAkdO-vzGcvholH2rfS1wTxUXjVHwWm59fWy45eA,2876
|
|
134
138
|
tests/adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
135
139
|
tests/adapter/dbt_adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -149,8 +153,8 @@ tests/tasks/test_row_count.py,sha256=21PaP2aq-x8-pqwzWHRT1sixhQ8g3CQNRWOZTTmbK0s
|
|
|
149
153
|
tests/tasks/test_schema.py,sha256=7ds4Vx8ixaiIWDR49Lvjem4xlPkRP1cXazDRY3roUak,3121
|
|
150
154
|
tests/tasks/test_top_k.py,sha256=YR_GS__DJsbDlQVaEEdJvNQ3fh1VmV5Nb3G7lb0r6YM,1779
|
|
151
155
|
tests/tasks/test_valuediff.py,sha256=_xQJGgxsXoy2NYk_Z6Hsw2FlVh6zk2nN_iUueyRN1e8,2046
|
|
152
|
-
recce_nightly-1.
|
|
153
|
-
recce_nightly-1.
|
|
154
|
-
recce_nightly-1.
|
|
155
|
-
recce_nightly-1.
|
|
156
|
-
recce_nightly-1.
|
|
156
|
+
recce_nightly-1.16.0.20250814.dist-info/METADATA,sha256=apeErc90TLIbChqRgH2pgqMChGOzJBZO6qrPLUMqm_A,9366
|
|
157
|
+
recce_nightly-1.16.0.20250814.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
158
|
+
recce_nightly-1.16.0.20250814.dist-info/entry_points.txt,sha256=oqoY_IiwIqXbgrIsPnlqUqao2eiIeP2dprowkOlmeyg,40
|
|
159
|
+
recce_nightly-1.16.0.20250814.dist-info/top_level.txt,sha256=6PKGVpf75idP0C6KEaldDzzZUauIxNu1ZDstau1pI4I,12
|
|
160
|
+
recce_nightly-1.16.0.20250814.dist-info/RECORD,,
|
tests/test_cli.py
CHANGED
|
@@ -6,7 +6,7 @@ from click.testing import CliRunner
|
|
|
6
6
|
from recce.cli import run as cli_command_run
|
|
7
7
|
from recce.cli import server as cli_command_server
|
|
8
8
|
from recce.core import RecceContext
|
|
9
|
-
from recce.state import
|
|
9
|
+
from recce.state import CloudStateLoader
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def test_cmd_version():
|
|
@@ -48,11 +48,11 @@ class TestCommandServer(TestCase):
|
|
|
48
48
|
@patch.object(RecceContext, "verify_required_artifacts")
|
|
49
49
|
@patch("recce.util.recce_cloud.get_recce_cloud_onboarding_state")
|
|
50
50
|
@patch("recce.cli.uvicorn.run")
|
|
51
|
-
@patch("recce.cli.
|
|
51
|
+
@patch("recce.cli.CloudStateLoader")
|
|
52
52
|
def test_cmd_server_with_cloud(
|
|
53
53
|
self, mock_state_loader_class, mock_run, mock_get_recce_cloud_onboarding_state, mock_verify_required_artifacts
|
|
54
54
|
):
|
|
55
|
-
mock_state_loader = MagicMock(spec=
|
|
55
|
+
mock_state_loader = MagicMock(spec=CloudStateLoader)
|
|
56
56
|
mock_state_loader.verify.return_value = True
|
|
57
57
|
mock_state_loader.review_mode = True
|
|
58
58
|
mock_get_recce_cloud_onboarding_state.return_value = "completed"
|
tests/test_core.py
CHANGED
|
@@ -1,6 +1,153 @@
|
|
|
1
1
|
# noinspection PyUnresolvedReferences
|
|
2
|
+
import os
|
|
3
|
+
import unittest
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from recce.core import RecceContext
|
|
7
|
+
from recce.models import Check, Run, RunType
|
|
8
|
+
from recce.state import ArtifactsRoot, FileStateLoader, RecceState
|
|
2
9
|
from tests.adapter.dbt_adapter.conftest import dbt_test_helper # noqa: F401
|
|
3
10
|
|
|
11
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRecceState(unittest.TestCase):
|
|
15
|
+
def test_load(self):
|
|
16
|
+
run = Run(type=RunType.QUERY, params=dict(sql_template="select * from users"))
|
|
17
|
+
check = Check(name="check 1", description="desc 1", type=run.type, params=run.params)
|
|
18
|
+
|
|
19
|
+
state = RecceState(runs=[run], checks=[check])
|
|
20
|
+
json_content = state.to_json()
|
|
21
|
+
new_state = RecceState.from_json(json_content)
|
|
22
|
+
|
|
23
|
+
run_loaded = new_state.runs[0]
|
|
24
|
+
check_loaded = new_state.checks[0]
|
|
25
|
+
|
|
26
|
+
assert run.run_id == run_loaded.run_id
|
|
27
|
+
assert check.check_id == check_loaded.check_id
|
|
28
|
+
|
|
29
|
+
def test_merge_checks(self):
|
|
30
|
+
check1 = Check(name="test1", description="", type="query")
|
|
31
|
+
check2 = Check(name="test2", description="", type="query", updated_at=datetime(2000, 1, 1))
|
|
32
|
+
check2_2 = Check(
|
|
33
|
+
name="test2_2", description="", type="query", updated_at=datetime(2020, 1, 1), check_id=check2.check_id
|
|
34
|
+
)
|
|
35
|
+
check3 = Check(name="test3", description="", type="query")
|
|
36
|
+
|
|
37
|
+
context = RecceContext()
|
|
38
|
+
state = RecceState(checks=[check1], runs=[])
|
|
39
|
+
context.import_state(state)
|
|
40
|
+
self.assertEqual(1, len(context.checks))
|
|
41
|
+
self.assertEqual(check1.name, context.checks[0].name)
|
|
42
|
+
|
|
43
|
+
context = RecceContext(checks=[check1, check2])
|
|
44
|
+
state = RecceState(checks=[check1, check2_2, check3], runs=[])
|
|
45
|
+
context.import_state(state)
|
|
46
|
+
self.assertEqual(3, len(context.checks))
|
|
47
|
+
self.assertEqual(check2_2.name, context.checks[1].name)
|
|
48
|
+
|
|
49
|
+
def test_merge_preset_checks(self):
|
|
50
|
+
check1 = Check(
|
|
51
|
+
name="test1",
|
|
52
|
+
description="test1",
|
|
53
|
+
type="query",
|
|
54
|
+
params=dict(foo="bar"),
|
|
55
|
+
updated_at=datetime(2000, 1, 1),
|
|
56
|
+
is_preset=True,
|
|
57
|
+
)
|
|
58
|
+
check2 = Check(
|
|
59
|
+
name="test2",
|
|
60
|
+
description="test2",
|
|
61
|
+
type="query",
|
|
62
|
+
params=dict(foo="bar"),
|
|
63
|
+
updated_at=datetime(2001, 1, 1),
|
|
64
|
+
is_preset=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
context = RecceContext(checks=[check1])
|
|
68
|
+
state = RecceState(checks=[check2], runs=[])
|
|
69
|
+
context.import_state(state)
|
|
70
|
+
self.assertEqual(1, len(context.checks))
|
|
71
|
+
self.assertEqual(check2.name, context.checks[0].name)
|
|
72
|
+
|
|
73
|
+
context = RecceContext(checks=[check2])
|
|
74
|
+
state = RecceState(checks=[check1], runs=[])
|
|
75
|
+
context.import_state(state)
|
|
76
|
+
self.assertEqual(1, len(context.checks))
|
|
77
|
+
self.assertEqual(check2.name, context.checks[0].name)
|
|
78
|
+
|
|
79
|
+
def test_revert_checks(self):
|
|
80
|
+
check1 = Check(name="test1", description="", type="query")
|
|
81
|
+
check2 = Check(name="test2", description="", type="query")
|
|
82
|
+
check2_2 = Check(name="test2_2", description="", type="query", check_id=check2.check_id)
|
|
83
|
+
check3 = Check(name="test3", description="", type="query")
|
|
84
|
+
|
|
85
|
+
context = RecceContext(checks=[check1, check2])
|
|
86
|
+
state = RecceState(checks=[check2_2, check3], runs=[])
|
|
87
|
+
context.import_state(state, merge=False)
|
|
88
|
+
self.assertEqual(2, len(context.checks))
|
|
89
|
+
self.assertEqual(check2_2.name, context.checks[0].name)
|
|
90
|
+
|
|
91
|
+
def test_merge_runs(self):
|
|
92
|
+
run1 = Run(type="query")
|
|
93
|
+
run2 = Run(type="query")
|
|
94
|
+
run3 = Run(type="query")
|
|
95
|
+
|
|
96
|
+
context = RecceContext(runs=[])
|
|
97
|
+
state = RecceState(runs=[run1])
|
|
98
|
+
context.import_state(state)
|
|
99
|
+
self.assertEqual(1, len(context.runs))
|
|
100
|
+
|
|
101
|
+
context = RecceContext(runs=[run1, run2])
|
|
102
|
+
state = RecceState(runs=[run2, run3])
|
|
103
|
+
context.import_state(state)
|
|
104
|
+
self.assertEqual(3, len(context.runs))
|
|
105
|
+
|
|
106
|
+
def test_merge_dbt_artifacts(self):
|
|
107
|
+
import json
|
|
108
|
+
import os
|
|
109
|
+
|
|
110
|
+
with open(os.path.join(current_dir, "manifest.json"), "r") as f:
|
|
111
|
+
manifest = json.load(f)
|
|
112
|
+
manifest["metadata"]["generated_at"] = "2000-01-01T00:00:00Z"
|
|
113
|
+
artifacts = ArtifactsRoot(
|
|
114
|
+
base=dict(
|
|
115
|
+
manifest=manifest,
|
|
116
|
+
),
|
|
117
|
+
current=dict(
|
|
118
|
+
manifest=manifest,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
from tests.adapter.dbt_adapter.dbt_test_helper import DbtTestHelper
|
|
123
|
+
|
|
124
|
+
adapter = DbtTestHelper().adapter
|
|
125
|
+
adapter.import_artifacts(artifacts)
|
|
126
|
+
self.assertNotEqual(adapter.base_manifest.metadata.invocation_id, manifest.get("metadata").get("invocation_id"))
|
|
127
|
+
|
|
128
|
+
manifest["metadata"]["generated_at"] = "2099-01-01T00:00:00Z"
|
|
129
|
+
adapter.import_artifacts(artifacts)
|
|
130
|
+
self.assertEqual(adapter.base_manifest.metadata.invocation_id, manifest.get("metadata").get("invocation_id"))
|
|
131
|
+
|
|
132
|
+
def test_state_loader(self):
|
|
133
|
+
# copy ./recce_state.json to temp and open
|
|
134
|
+
|
|
135
|
+
# use library to create a temp file in the context
|
|
136
|
+
import os
|
|
137
|
+
import shutil
|
|
138
|
+
import tempfile
|
|
139
|
+
|
|
140
|
+
with tempfile.NamedTemporaryFile() as f:
|
|
141
|
+
# copy ./recce_state.json to temp file
|
|
142
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
143
|
+
state_file = os.path.join(current_dir, "recce_state.json")
|
|
144
|
+
shutil.copy(state_file, f.name)
|
|
145
|
+
|
|
146
|
+
# load the state file
|
|
147
|
+
state_loader = FileStateLoader(state_file=f.name)
|
|
148
|
+
state = state_loader.load()
|
|
149
|
+
assert len(state.runs) == 17
|
|
150
|
+
|
|
4
151
|
|
|
5
152
|
def test_lineage_diff(dbt_test_helper):
|
|
6
153
|
sql_model1 = """
|
tests/test_server.py
CHANGED
|
@@ -31,9 +31,9 @@ def test_health():
|
|
|
31
31
|
|
|
32
32
|
def test_stateless(dbt_test_helper):
|
|
33
33
|
context = default_context()
|
|
34
|
-
from recce.state import
|
|
34
|
+
from recce.state import FileStateLoader
|
|
35
35
|
|
|
36
|
-
context.state_loader =
|
|
36
|
+
context.state_loader = FileStateLoader()
|
|
37
37
|
client = TestClient(app)
|
|
38
38
|
response = client.get("/api/info")
|
|
39
39
|
assert response.status_code == 200
|
|
@@ -44,9 +44,9 @@ def test_stateless(dbt_test_helper):
|
|
|
44
44
|
|
|
45
45
|
def test_file_mode(dbt_test_helper):
|
|
46
46
|
context = default_context()
|
|
47
|
-
from recce.state import
|
|
47
|
+
from recce.state import FileStateLoader
|
|
48
48
|
|
|
49
|
-
context.state_loader =
|
|
49
|
+
context.state_loader = FileStateLoader(state_file="/tmp/recce_state.json")
|
|
50
50
|
client = TestClient(app)
|
|
51
51
|
response = client.get("/api/info")
|
|
52
52
|
assert response.status_code == 200
|
|
@@ -63,9 +63,9 @@ def test_saveas_and_rename(dbt_test_helper, temp_folder):
|
|
|
63
63
|
state_file3 = os.path.join(temp_folder, "recce_state3.json")
|
|
64
64
|
os.makedirs(os.path.join(temp_folder, "dir.json"))
|
|
65
65
|
|
|
66
|
-
from recce.state import
|
|
66
|
+
from recce.state import FileStateLoader
|
|
67
67
|
|
|
68
|
-
context.state_loader =
|
|
68
|
+
context.state_loader = FileStateLoader(state_file=state_file)
|
|
69
69
|
client = TestClient(app)
|
|
70
70
|
|
|
71
71
|
response = client.post("/api/save", json={"filename": "recce_state2.json"})
|