fleet-python 0.2.69__py3-none-any.whl → 0.2.69b3__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 fleet-python might be problematic. Click here for more details.
- fleet/_async/base.py +3 -4
- fleet/_async/client.py +194 -38
- fleet/_async/instance/client.py +19 -4
- fleet/_async/resources/sqlite.py +150 -1
- fleet/_async/tasks.py +5 -2
- fleet/_async/verifiers/bundler.py +21 -22
- fleet/_async/verifiers/verifier.py +19 -20
- fleet/base.py +3 -4
- fleet/client.py +212 -44
- fleet/instance/client.py +20 -5
- fleet/resources/sqlite.py +143 -1
- fleet/tasks.py +5 -2
- fleet/verifiers/bundler.py +21 -22
- fleet/verifiers/decorator.py +1 -1
- fleet/verifiers/verifier.py +19 -20
- {fleet_python-0.2.69.dist-info → fleet_python-0.2.69b3.dist-info}/METADATA +1 -1
- {fleet_python-0.2.69.dist-info → fleet_python-0.2.69b3.dist-info}/RECORD +24 -20
- tests/test_app_method.py +85 -0
- tests/test_instance_dispatch.py +607 -0
- tests/test_sqlite_resource_dual_mode.py +263 -0
- tests/test_sqlite_shared_memory_behavior.py +117 -0
- {fleet_python-0.2.69.dist-info → fleet_python-0.2.69b3.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.69.dist-info → fleet_python-0.2.69b3.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.69.dist-info → fleet_python-0.2.69b3.dist-info}/top_level.txt +0 -0
fleet/resources/sqlite.py
CHANGED
|
@@ -675,17 +675,97 @@ class SyncQueryBuilder:
|
|
|
675
675
|
|
|
676
676
|
|
|
677
677
|
class SQLiteResource(Resource):
|
|
678
|
-
def __init__(
|
|
678
|
+
def __init__(
|
|
679
|
+
self,
|
|
680
|
+
resource: ResourceModel,
|
|
681
|
+
client: Optional["SyncWrapper"] = None,
|
|
682
|
+
db_path: Optional[str] = None,
|
|
683
|
+
):
|
|
679
684
|
super().__init__(resource)
|
|
680
685
|
self.client = client
|
|
686
|
+
self.db_path = db_path
|
|
687
|
+
self._mode = "direct" if db_path else "http"
|
|
688
|
+
|
|
689
|
+
@property
|
|
690
|
+
def mode(self) -> str:
|
|
691
|
+
"""Return the mode of this resource: 'direct' (local file) or 'http' (remote API)."""
|
|
692
|
+
return self._mode
|
|
681
693
|
|
|
682
694
|
def describe(self) -> DescribeResponse:
|
|
683
695
|
"""Describe the SQLite database schema."""
|
|
696
|
+
if self._mode == "direct":
|
|
697
|
+
return self._describe_direct()
|
|
698
|
+
else:
|
|
699
|
+
return self._describe_http()
|
|
700
|
+
|
|
701
|
+
def _describe_http(self) -> DescribeResponse:
|
|
702
|
+
"""Describe database schema via HTTP API."""
|
|
684
703
|
response = self.client.request(
|
|
685
704
|
"GET", f"/resources/sqlite/{self.resource.name}/describe"
|
|
686
705
|
)
|
|
687
706
|
return DescribeResponse(**response.json())
|
|
688
707
|
|
|
708
|
+
def _describe_direct(self) -> DescribeResponse:
|
|
709
|
+
"""Describe database schema from local file or in-memory database."""
|
|
710
|
+
try:
|
|
711
|
+
# Check if we need URI mode (for shared memory databases)
|
|
712
|
+
use_uri = 'mode=memory' in self.db_path
|
|
713
|
+
conn = sqlite3.connect(self.db_path, uri=use_uri)
|
|
714
|
+
cursor = conn.cursor()
|
|
715
|
+
|
|
716
|
+
# Get all tables
|
|
717
|
+
cursor.execute(
|
|
718
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
|
719
|
+
)
|
|
720
|
+
table_names = [row[0] for row in cursor.fetchall()]
|
|
721
|
+
|
|
722
|
+
tables = []
|
|
723
|
+
for table_name in table_names:
|
|
724
|
+
# Get table info
|
|
725
|
+
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
726
|
+
columns = cursor.fetchall()
|
|
727
|
+
|
|
728
|
+
# Get CREATE TABLE SQL
|
|
729
|
+
cursor.execute(
|
|
730
|
+
f"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
|
|
731
|
+
(table_name,)
|
|
732
|
+
)
|
|
733
|
+
sql_row = cursor.fetchone()
|
|
734
|
+
create_sql = sql_row[0] if sql_row else ""
|
|
735
|
+
|
|
736
|
+
table_schema = {
|
|
737
|
+
"name": table_name,
|
|
738
|
+
"sql": create_sql,
|
|
739
|
+
"columns": [
|
|
740
|
+
{
|
|
741
|
+
"name": col[1],
|
|
742
|
+
"type": col[2],
|
|
743
|
+
"notnull": bool(col[3]),
|
|
744
|
+
"default_value": col[4],
|
|
745
|
+
"primary_key": col[5] > 0,
|
|
746
|
+
}
|
|
747
|
+
for col in columns
|
|
748
|
+
],
|
|
749
|
+
}
|
|
750
|
+
tables.append(table_schema)
|
|
751
|
+
|
|
752
|
+
conn.close()
|
|
753
|
+
|
|
754
|
+
return DescribeResponse(
|
|
755
|
+
success=True,
|
|
756
|
+
resource_name=self.resource.name,
|
|
757
|
+
tables=tables,
|
|
758
|
+
message="Schema retrieved from local file",
|
|
759
|
+
)
|
|
760
|
+
except Exception as e:
|
|
761
|
+
return DescribeResponse(
|
|
762
|
+
success=False,
|
|
763
|
+
resource_name=self.resource.name,
|
|
764
|
+
tables=None,
|
|
765
|
+
error=str(e),
|
|
766
|
+
message=f"Failed to describe database: {str(e)}",
|
|
767
|
+
)
|
|
768
|
+
|
|
689
769
|
def query(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
|
|
690
770
|
return self._query(query, args, read_only=True)
|
|
691
771
|
|
|
@@ -695,6 +775,15 @@ class SQLiteResource(Resource):
|
|
|
695
775
|
def _query(
|
|
696
776
|
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
697
777
|
) -> QueryResponse:
|
|
778
|
+
if self._mode == "direct":
|
|
779
|
+
return self._query_direct(query, args, read_only)
|
|
780
|
+
else:
|
|
781
|
+
return self._query_http(query, args, read_only)
|
|
782
|
+
|
|
783
|
+
def _query_http(
|
|
784
|
+
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
785
|
+
) -> QueryResponse:
|
|
786
|
+
"""Execute query via HTTP API."""
|
|
698
787
|
request = QueryRequest(query=query, args=args, read_only=read_only)
|
|
699
788
|
response = self.client.request(
|
|
700
789
|
"POST",
|
|
@@ -703,6 +792,59 @@ class SQLiteResource(Resource):
|
|
|
703
792
|
)
|
|
704
793
|
return QueryResponse(**response.json())
|
|
705
794
|
|
|
795
|
+
def _query_direct(
|
|
796
|
+
self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
|
|
797
|
+
) -> QueryResponse:
|
|
798
|
+
"""Execute query directly on local SQLite file or in-memory database."""
|
|
799
|
+
try:
|
|
800
|
+
# Check if we need URI mode (for shared memory databases)
|
|
801
|
+
use_uri = 'mode=memory' in self.db_path
|
|
802
|
+
conn = sqlite3.connect(self.db_path, uri=use_uri)
|
|
803
|
+
cursor = conn.cursor()
|
|
804
|
+
|
|
805
|
+
# Execute the query
|
|
806
|
+
if args:
|
|
807
|
+
cursor.execute(query, args)
|
|
808
|
+
else:
|
|
809
|
+
cursor.execute(query)
|
|
810
|
+
|
|
811
|
+
# For write operations, commit the transaction
|
|
812
|
+
if not read_only:
|
|
813
|
+
conn.commit()
|
|
814
|
+
|
|
815
|
+
# Get column names if available
|
|
816
|
+
columns = [desc[0] for desc in cursor.description] if cursor.description else []
|
|
817
|
+
|
|
818
|
+
# Fetch results for SELECT queries
|
|
819
|
+
rows = []
|
|
820
|
+
rows_affected = 0
|
|
821
|
+
last_insert_id = None
|
|
822
|
+
|
|
823
|
+
if cursor.description: # SELECT query
|
|
824
|
+
rows = cursor.fetchall()
|
|
825
|
+
else: # INSERT/UPDATE/DELETE
|
|
826
|
+
rows_affected = cursor.rowcount
|
|
827
|
+
last_insert_id = cursor.lastrowid if cursor.lastrowid else None
|
|
828
|
+
|
|
829
|
+
conn.close()
|
|
830
|
+
|
|
831
|
+
return QueryResponse(
|
|
832
|
+
success=True,
|
|
833
|
+
columns=columns if columns else None,
|
|
834
|
+
rows=rows if rows else None,
|
|
835
|
+
rows_affected=rows_affected if rows_affected > 0 else None,
|
|
836
|
+
last_insert_id=last_insert_id,
|
|
837
|
+
message="Query executed successfully",
|
|
838
|
+
)
|
|
839
|
+
except Exception as e:
|
|
840
|
+
return QueryResponse(
|
|
841
|
+
success=False,
|
|
842
|
+
columns=None,
|
|
843
|
+
rows=None,
|
|
844
|
+
error=str(e),
|
|
845
|
+
message=f"Query failed: {str(e)}",
|
|
846
|
+
)
|
|
847
|
+
|
|
706
848
|
def table(self, table_name: str) -> SyncQueryBuilder:
|
|
707
849
|
"""Create a query builder for the specified table."""
|
|
708
850
|
return SyncQueryBuilder(self, table_name)
|
fleet/tasks.py
CHANGED
|
@@ -286,8 +286,11 @@ def verifier_from_string(
|
|
|
286
286
|
# Remove lines like: @verifier(key="...")
|
|
287
287
|
cleaned_code = re.sub(r"@verifier\([^)]*\)\s*\n", "", verifier_func)
|
|
288
288
|
# Also remove the verifier import if present
|
|
289
|
-
|
|
290
|
-
cleaned_code = re.sub(r"import.*verifier
|
|
289
|
+
# Use MULTILINE flag to match beginning of lines with ^
|
|
290
|
+
cleaned_code = re.sub(r"^from fleet\.verifiers.*import.*verifier.*$\n?", "", cleaned_code, flags=re.MULTILINE)
|
|
291
|
+
cleaned_code = re.sub(r"^from fleet import verifier.*$\n?", "", cleaned_code, flags=re.MULTILINE)
|
|
292
|
+
cleaned_code = re.sub(r"^import fleet\.verifiers.*$\n?", "", cleaned_code, flags=re.MULTILINE)
|
|
293
|
+
cleaned_code = re.sub(r"^import fleet$\n?", "", cleaned_code, flags=re.MULTILINE)
|
|
291
294
|
|
|
292
295
|
# Create a globals namespace with all required imports
|
|
293
296
|
exec_globals = globals().copy()
|
fleet/verifiers/bundler.py
CHANGED
|
@@ -37,7 +37,7 @@ class FunctionBundler:
|
|
|
37
37
|
) -> bytes:
|
|
38
38
|
"""Create a function bundle with statically extracted code."""
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
logger.info(f"Creating function bundle for {func.__name__}")
|
|
41
41
|
|
|
42
42
|
# 1. Parse the main function and find dependencies
|
|
43
43
|
mod_file = Path(func.__code__.co_filename)
|
|
@@ -115,7 +115,7 @@ class FunctionBundler:
|
|
|
115
115
|
|
|
116
116
|
# Find function calls within the verifier function
|
|
117
117
|
called_functions = self._extract_function_calls(main_func_ast)
|
|
118
|
-
|
|
118
|
+
logger.debug(f"Functions called in verifier: {called_functions}")
|
|
119
119
|
|
|
120
120
|
# Find all functions defined in the module
|
|
121
121
|
module_functions = {}
|
|
@@ -128,7 +128,7 @@ class FunctionBundler:
|
|
|
128
128
|
for func_name in called_functions:
|
|
129
129
|
if func_name in module_functions and func_name != func.__name__:
|
|
130
130
|
same_module_deps.append(func_name)
|
|
131
|
-
|
|
131
|
+
logger.debug(f"Found same-module dependency: {func_name}")
|
|
132
132
|
|
|
133
133
|
# Separate local and external imports
|
|
134
134
|
local_imports = {}
|
|
@@ -292,7 +292,7 @@ class FunctionBundler:
|
|
|
292
292
|
code = ast.unparse(node)
|
|
293
293
|
extracted_code.append(code)
|
|
294
294
|
except Exception as e:
|
|
295
|
-
|
|
295
|
+
logger.warning(f"Could not unparse AST node: {e}")
|
|
296
296
|
# Fallback to original source extraction
|
|
297
297
|
lines = content.split("\n")
|
|
298
298
|
start_line = node.lineno - 1
|
|
@@ -305,11 +305,11 @@ class FunctionBundler:
|
|
|
305
305
|
extracted_code.append(code)
|
|
306
306
|
|
|
307
307
|
result = "\n\n".join(extracted_code)
|
|
308
|
-
|
|
308
|
+
logger.debug(f"Extracted {len(extracted_code)} items from {file_path}")
|
|
309
309
|
return result
|
|
310
310
|
|
|
311
311
|
except Exception as e:
|
|
312
|
-
|
|
312
|
+
logger.warning(f"Failed to extract functions from {file_path}: {e}")
|
|
313
313
|
# Fallback to including the entire file
|
|
314
314
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
315
315
|
return f.read()
|
|
@@ -464,14 +464,14 @@ class FunctionBundler:
|
|
|
464
464
|
version = dist.version # Get the installed version
|
|
465
465
|
package_with_version = f"{package_name}=={version}"
|
|
466
466
|
packages.add(package_with_version)
|
|
467
|
-
|
|
467
|
+
logger.debug(f"Mapped {mod} -> {package_with_version}")
|
|
468
468
|
except imd.PackageNotFoundError:
|
|
469
469
|
# Skip stdlib or local modules
|
|
470
|
-
|
|
470
|
+
logger.debug(f"Skipping {mod} (stdlib or local)")
|
|
471
471
|
continue
|
|
472
472
|
|
|
473
473
|
package_list = list(packages)
|
|
474
|
-
|
|
474
|
+
logger.debug(f"Final package list: {package_list}")
|
|
475
475
|
return package_list
|
|
476
476
|
|
|
477
477
|
def _merge_requirements(
|
|
@@ -511,10 +511,10 @@ class FunctionBundler:
|
|
|
511
511
|
if pkg_name not in seen_packages:
|
|
512
512
|
final_requirements.append(req)
|
|
513
513
|
seen_packages.add(pkg_name)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
514
|
+
else:
|
|
515
|
+
logger.debug(
|
|
516
|
+
f"Skipping auto-detected {req}, using explicit version instead"
|
|
517
|
+
)
|
|
518
518
|
|
|
519
519
|
# Always ensure fleet-python is included
|
|
520
520
|
if "fleet-python" not in seen_packages:
|
|
@@ -565,9 +565,9 @@ class FunctionBundler:
|
|
|
565
565
|
)
|
|
566
566
|
if dep_src:
|
|
567
567
|
same_module_code += f"\n{dep_src}\n"
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
568
|
+
logger.debug(
|
|
569
|
+
f"Extracted same-module dependency: {dep_name}"
|
|
570
|
+
)
|
|
571
571
|
|
|
572
572
|
# Create verifier.py with the main function
|
|
573
573
|
verifier_file = build_dir / "verifier.py"
|
|
@@ -586,7 +586,7 @@ class FunctionBundler:
|
|
|
586
586
|
{code}
|
|
587
587
|
"""
|
|
588
588
|
dest_path.write_text(extracted_content)
|
|
589
|
-
|
|
589
|
+
logger.debug(f"Created extracted file: {relative_path}")
|
|
590
590
|
|
|
591
591
|
# Ensure __init__.py files exist
|
|
592
592
|
self._ensure_init_files(Path(relative_path), build_dir)
|
|
@@ -595,7 +595,7 @@ class FunctionBundler:
|
|
|
595
595
|
return self._create_zip_bundle(build_dir)
|
|
596
596
|
|
|
597
597
|
except Exception as e:
|
|
598
|
-
|
|
598
|
+
logger.error(f"Failed to build function bundle: {e}")
|
|
599
599
|
raise RuntimeError(f"Function bundle creation failed: {e}")
|
|
600
600
|
|
|
601
601
|
def _ensure_init_files(self, rel_path: Path, build_dir: Path):
|
|
@@ -607,7 +607,7 @@ class FunctionBundler:
|
|
|
607
607
|
if not init_file.exists():
|
|
608
608
|
init_file.parent.mkdir(parents=True, exist_ok=True)
|
|
609
609
|
init_file.write_text("# Auto-generated __init__.py")
|
|
610
|
-
|
|
610
|
+
logger.debug(f"Created __init__.py: {current}")
|
|
611
611
|
current = current.parent
|
|
612
612
|
|
|
613
613
|
def _create_zip_bundle(self, build_dir: Path) -> bytes:
|
|
@@ -621,7 +621,7 @@ class FunctionBundler:
|
|
|
621
621
|
zf.write(file_path, arcname)
|
|
622
622
|
|
|
623
623
|
bundle_size = len(zip_buffer.getvalue())
|
|
624
|
-
|
|
624
|
+
logger.debug(f"Created function bundle ({bundle_size:,} bytes)")
|
|
625
625
|
return zip_buffer.getvalue()
|
|
626
626
|
|
|
627
627
|
def _extract_function_source(
|
|
@@ -662,8 +662,7 @@ class FunctionBundler:
|
|
|
662
662
|
return "\n".join(func_lines)
|
|
663
663
|
|
|
664
664
|
except Exception as e:
|
|
665
|
-
|
|
666
|
-
pass
|
|
665
|
+
logger.warning(f"Failed to extract function {function_name}: {e}")
|
|
667
666
|
|
|
668
667
|
return None
|
|
669
668
|
|
fleet/verifiers/decorator.py
CHANGED
fleet/verifiers/verifier.py
CHANGED
|
@@ -90,9 +90,9 @@ class SyncVerifierFunction:
|
|
|
90
90
|
|
|
91
91
|
self._bundle_data = zip_buffer.getvalue()
|
|
92
92
|
self._bundle_sha = _get_bundle_sha(self._bundle_data)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
logger.debug(
|
|
94
|
+
f"Created bundle from raw code for {self.key} with SHA: {self._bundle_sha}"
|
|
95
|
+
)
|
|
96
96
|
else:
|
|
97
97
|
# Try to create bundle from function source
|
|
98
98
|
try:
|
|
@@ -100,9 +100,9 @@ class SyncVerifierFunction:
|
|
|
100
100
|
self.func, self.extra_requirements, self.verifier_id
|
|
101
101
|
)
|
|
102
102
|
self._bundle_sha = _get_bundle_sha(self._bundle_data)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
logger.debug(
|
|
104
|
+
f"Created bundle for {self.key} with SHA: {self._bundle_sha}"
|
|
105
|
+
)
|
|
106
106
|
except OSError as e:
|
|
107
107
|
# Can't create bundle - no source and no raw code
|
|
108
108
|
raise OSError(f"Cannot create bundle for {self.key}: {e}")
|
|
@@ -115,21 +115,20 @@ class SyncVerifierFunction:
|
|
|
115
115
|
|
|
116
116
|
# If bundle_data is empty, we're using server-side bundle
|
|
117
117
|
if not bundle_data:
|
|
118
|
-
|
|
118
|
+
logger.debug(f"Using server-side bundle {bundle_sha[:8]}...")
|
|
119
119
|
return bundle_sha, False # No upload needed, server has it
|
|
120
120
|
|
|
121
121
|
# Always check if bundle exists on server
|
|
122
122
|
try:
|
|
123
123
|
exists = env.check_bundle_exists(bundle_sha)
|
|
124
124
|
if exists.success:
|
|
125
|
-
|
|
125
|
+
logger.info(f"Bundle {bundle_sha[:8]}... found on server")
|
|
126
126
|
return bundle_sha, False # Found on server, no upload needed
|
|
127
127
|
except Exception as e:
|
|
128
|
-
|
|
129
|
-
pass
|
|
128
|
+
logger.warning(f"Failed to check bundle existence: {e}")
|
|
130
129
|
|
|
131
130
|
# Bundle not found on server - upload needed
|
|
132
|
-
|
|
131
|
+
logger.info(f"Bundle {bundle_sha[:8]}... needs to be uploaded")
|
|
133
132
|
return bundle_sha, True # Upload needed
|
|
134
133
|
|
|
135
134
|
def __call__(self, env: "SyncEnv", *args, **kwargs) -> float:
|
|
@@ -159,7 +158,7 @@ class SyncVerifierFunction:
|
|
|
159
158
|
)
|
|
160
159
|
|
|
161
160
|
except Exception as e:
|
|
162
|
-
|
|
161
|
+
logger.error(f"Error in verifier {self.key}: {e}")
|
|
163
162
|
# Return error score 0
|
|
164
163
|
return 0.0
|
|
165
164
|
|
|
@@ -191,7 +190,7 @@ class SyncVerifierFunction:
|
|
|
191
190
|
try:
|
|
192
191
|
return float(result)
|
|
193
192
|
except (ValueError, TypeError):
|
|
194
|
-
|
|
193
|
+
logger.warning(f"Could not convert result to float: {result}")
|
|
195
194
|
return 0.0
|
|
196
195
|
|
|
197
196
|
def _raise_remote_error(self, error_info: Dict[str, Any]):
|
|
@@ -250,7 +249,7 @@ Remote traceback:
|
|
|
250
249
|
|
|
251
250
|
if needs_upload:
|
|
252
251
|
# Need to upload bundle to S3
|
|
253
|
-
|
|
252
|
+
logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
|
|
254
253
|
bundle_data, _ = self._get_or_create_bundle()
|
|
255
254
|
|
|
256
255
|
response = env.execute_verifier_remote(
|
|
@@ -264,12 +263,12 @@ Remote traceback:
|
|
|
264
263
|
needs_upload=True,
|
|
265
264
|
)
|
|
266
265
|
|
|
267
|
-
|
|
266
|
+
logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
|
|
268
267
|
return response
|
|
269
268
|
|
|
270
269
|
else:
|
|
271
270
|
# Bundle already available - execute without upload
|
|
272
|
-
|
|
271
|
+
logger.info(f"Bundle {bundle_sha[:8]}... already cached for {self.key}")
|
|
273
272
|
response = env.execute_verifier_remote(
|
|
274
273
|
bundle_data=b"", # Empty bundle since it's cached
|
|
275
274
|
bundle_sha=bundle_sha,
|
|
@@ -285,9 +284,9 @@ Remote traceback:
|
|
|
285
284
|
except Exception as e:
|
|
286
285
|
# Check if error indicates bundle not found and retry with upload
|
|
287
286
|
if self._is_bundle_not_found_error(e) and not needs_upload:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
logger.info(
|
|
288
|
+
f"Bundle {bundle_sha[:8]}... not found on server, uploading..."
|
|
289
|
+
)
|
|
291
290
|
bundle_data, _ = self._get_or_create_bundle()
|
|
292
291
|
response = env.execute_verifier_remote(
|
|
293
292
|
bundle_data=bundle_data,
|
|
@@ -301,7 +300,7 @@ Remote traceback:
|
|
|
301
300
|
)
|
|
302
301
|
return response
|
|
303
302
|
else:
|
|
304
|
-
|
|
303
|
+
logger.error(f"Error in remote execution of {self.key}: {e}")
|
|
305
304
|
raise
|
|
306
305
|
|
|
307
306
|
|
|
@@ -22,59 +22,63 @@ examples/query_builder_example.py,sha256=-cOMfWGNifYfYEt_Ds73XpwATZvFDL6F4KTkVxd
|
|
|
22
22
|
examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
|
|
23
23
|
examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
|
|
24
24
|
fleet/__init__.py,sha256=yC4HIcbtPAPOgI0lLri3l8nbXkNee9JOihKAc7SXYkY,4201
|
|
25
|
-
fleet/base.py,sha256=
|
|
26
|
-
fleet/client.py,sha256=
|
|
25
|
+
fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
|
|
26
|
+
fleet/client.py,sha256=lNVbAzq23p6-x66YduSdm4ZZC8yJreM82QVIKvDknuQ,40046
|
|
27
27
|
fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
|
|
28
28
|
fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
29
29
|
fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
|
|
30
30
|
fleet/models.py,sha256=GUL61DOD4XDT588T1a-EV7R1kh1w4q_JRLnX1rEI5ek,13911
|
|
31
|
-
fleet/tasks.py,sha256=
|
|
31
|
+
fleet/tasks.py,sha256=TWKenYlIJ8eo8TjMkTwfNL7ZRFgopnRXTCclPBMufgE,18281
|
|
32
32
|
fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
|
|
33
33
|
fleet/_async/__init__.py,sha256=5oOTmh16UsPWL2gDKKWkj2j5WGNeUhMzbQFWjX21jsc,8310
|
|
34
|
-
fleet/_async/base.py,sha256=
|
|
35
|
-
fleet/_async/client.py,sha256=
|
|
34
|
+
fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
|
|
35
|
+
fleet/_async/client.py,sha256=xjDlAFWYez1SHKcUnYw-iLQsQ3gEeKiWw3qDX9gnNHs,40029
|
|
36
36
|
fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
37
37
|
fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
|
|
38
38
|
fleet/_async/models.py,sha256=-3xv2QyoHsvYcWmdKLf9Z93md8XB17DBeJCxdRCB3bo,13571
|
|
39
|
-
fleet/_async/tasks.py,sha256=
|
|
39
|
+
fleet/_async/tasks.py,sha256=C3t15WXfAn_DY5UXtf4S27OoAs4U61GO1LirWFHr7AM,18327
|
|
40
40
|
fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
fleet/_async/env/client.py,sha256=Qv0QiW--ZMAMnz_IPYbYtokGP08x4PvSmkwde8rPCNk,2093
|
|
42
42
|
fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
|
|
43
43
|
fleet/_async/instance/base.py,sha256=3qUBuUR8OVS36LzdP6KyZzngtwPKYO09HoY6Ekxp-KA,1625
|
|
44
|
-
fleet/_async/instance/client.py,sha256=
|
|
44
|
+
fleet/_async/instance/client.py,sha256=Nrnoez4J3UnbSJoOfDw8vwb00p_A5RNOy9ROwUE4B_4,6838
|
|
45
45
|
fleet/_async/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
fleet/_async/resources/base.py,sha256=UfrenxUqcpL8SgYGOo8o8HgRvv2-ZO5G2Cdo91ofEdg,664
|
|
47
47
|
fleet/_async/resources/browser.py,sha256=oldoSiymJ1lJkADhpUG81ViOBDNyppX1jSoEwe9-W94,1369
|
|
48
48
|
fleet/_async/resources/mcp.py,sha256=TLEsLiFhfVfZFs0Fu_uDPm-h4FPdvqgQblYqs-PTHhc,1720
|
|
49
|
-
fleet/_async/resources/sqlite.py,sha256=
|
|
49
|
+
fleet/_async/resources/sqlite.py,sha256=zRTbqW0qLt8EO9UTIxtcbrmwPV3cGK3rCaX1oSFsd_E,33036
|
|
50
50
|
fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMTSOwxI,465
|
|
51
|
-
fleet/_async/verifiers/bundler.py,sha256=
|
|
52
|
-
fleet/_async/verifiers/verifier.py,sha256=
|
|
51
|
+
fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
52
|
+
fleet/_async/verifiers/verifier.py,sha256=IiHX028s6ux0kb2FR0Z5zJangl_IDh6cemXsUN2ktUU,14152
|
|
53
53
|
fleet/env/__init__.py,sha256=sSjD6vk8LzC_pxoXuRc8-ACqeX4PLm1FBWnWxpOhUS8,812
|
|
54
54
|
fleet/env/client.py,sha256=l2vI_BBrSXmyk2un3F1-QDcL-iv2OdBxTdYOdOioF0Q,1881
|
|
55
55
|
fleet/instance/__init__.py,sha256=CyWUkbGAK-DBPw4DC4AnCW-MqqheGhZMA5QSRVu-ws4,479
|
|
56
56
|
fleet/instance/base.py,sha256=OYqzBwZFfTX9wlBGSG5gljqj98NbiJeKIfFJ3uj5I4s,1587
|
|
57
|
-
fleet/instance/client.py,sha256=
|
|
57
|
+
fleet/instance/client.py,sha256=MUUkMV5i0-DPK8awBpRYfc3wy8kVq7IGQ_Re7td1e60,6639
|
|
58
58
|
fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
|
|
59
59
|
fleet/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
fleet/resources/base.py,sha256=AXZzT0_yWHkT497q3yekfr0xsD4cPGMCC6y7C43TIkk,663
|
|
61
61
|
fleet/resources/browser.py,sha256=hRNM0YMsVQUAraZGNi_B-KXxLpuddy4ntoEDFSw7czU,1295
|
|
62
62
|
fleet/resources/mcp.py,sha256=c6O4vVJnXANuHMGMe4IPxgp4zBEbFaGm6_d9e6j8Myc,1695
|
|
63
|
-
fleet/resources/sqlite.py,sha256=
|
|
63
|
+
fleet/resources/sqlite.py,sha256=BZE31mMOuRIT0vB4ppy6bO-QczmlYytHQkgo9nCZIew,31837
|
|
64
64
|
fleet/verifiers/__init__.py,sha256=GntS8qc3xv8mm-cku1t3xjvOll5jcc5FuiVqQgR4Y6Q,458
|
|
65
|
-
fleet/verifiers/bundler.py,sha256=
|
|
65
|
+
fleet/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
66
66
|
fleet/verifiers/code.py,sha256=A1i_UabZspbyj1awzKVQ_HRxgMO3fU7NbkxYyTrp7So,48
|
|
67
67
|
fleet/verifiers/db.py,sha256=LAh1HambBInH_D9q9E2Z41YNkCOI9JJfpWPFqztjpfQ,27922
|
|
68
|
-
fleet/verifiers/decorator.py,sha256=
|
|
68
|
+
fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,3260
|
|
69
69
|
fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
|
|
70
70
|
fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
|
|
71
|
-
fleet/verifiers/verifier.py,sha256=
|
|
72
|
-
fleet_python-0.2.
|
|
71
|
+
fleet/verifiers/verifier.py,sha256=_lcxXVm8e0xRrK2gNJy9up7pW1zOkPRY5n5lQ85S8jg,14197
|
|
72
|
+
fleet_python-0.2.69b3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
73
73
|
scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
|
|
74
74
|
scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
|
|
75
75
|
tests/__init__.py,sha256=Re1SdyxH8NfyL1kjhi7SQkGP1mYeWB-D6UALqdIMd8I,35
|
|
76
|
+
tests/test_app_method.py,sha256=kg2IiL75cH-HmsTMS3_wDL39dAesgfv_AT6jVthd5J4,3159
|
|
77
|
+
tests/test_instance_dispatch.py,sha256=CvU4C3LBIqsYZdEsEFfontGjyxAZfVYyXnGwxyIvXOc,23065
|
|
78
|
+
tests/test_sqlite_resource_dual_mode.py,sha256=Mh8jBd-xsIGDYFsOACKKK_5DXMUYlFFS7W-jaY6AjG4,8734
|
|
79
|
+
tests/test_sqlite_shared_memory_behavior.py,sha256=fKx_1BmLS3b8x-9pMgjMycpnaHWY8P-2ZuXEspx6Sbw,4082
|
|
76
80
|
tests/test_verifier_from_string.py,sha256=Lxi3TpFHFb-hG4-UhLKZJkqo84ax9YJY8G6beO-1erM,13581
|
|
77
|
-
fleet_python-0.2.
|
|
78
|
-
fleet_python-0.2.
|
|
79
|
-
fleet_python-0.2.
|
|
80
|
-
fleet_python-0.2.
|
|
81
|
+
fleet_python-0.2.69b3.dist-info/METADATA,sha256=M4OF4x_-pFl_h9xAh7ERomXkssTvlrH5HxDPq-C75Ng,3306
|
|
82
|
+
fleet_python-0.2.69b3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
83
|
+
fleet_python-0.2.69b3.dist-info/top_level.txt,sha256=qb1zIbtEktyhRFZdqVytwg54l64qtoZL0wjHB4bUg3c,29
|
|
84
|
+
fleet_python-0.2.69b3.dist-info/RECORD,,
|
tests/test_app_method.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Unit tests for SyncEnv.app() method URL handling."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import Mock, patch
|
|
5
|
+
from fleet.client import Fleet
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestAppMethod:
|
|
9
|
+
"""Test SyncEnv.app() method with different URL formats."""
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def fleet_client(self):
|
|
13
|
+
"""Create a Fleet client with mocked HTTP client."""
|
|
14
|
+
with patch("fleet.client.default_httpx_client") as mock_client:
|
|
15
|
+
mock_client.return_value = Mock()
|
|
16
|
+
client = Fleet(api_key="test_key")
|
|
17
|
+
client.client.request = Mock()
|
|
18
|
+
return client
|
|
19
|
+
|
|
20
|
+
def test_app_with_existing_app_path(self, fleet_client):
|
|
21
|
+
"""Test app() with URL that already has an app path like /sentry."""
|
|
22
|
+
# Create instance with a URL that has an existing app path
|
|
23
|
+
env = fleet_client.instance("https://example.com/sentry/api/v1/env")
|
|
24
|
+
|
|
25
|
+
# Access jira app
|
|
26
|
+
jira_client = env.app("jira")
|
|
27
|
+
|
|
28
|
+
# Check the constructed URL
|
|
29
|
+
assert jira_client.base_url == "https://example.com/jira/api/v1/env", \
|
|
30
|
+
f"Expected https://example.com/jira/api/v1/env, got {jira_client.base_url}"
|
|
31
|
+
|
|
32
|
+
def test_app_without_app_path(self, fleet_client):
|
|
33
|
+
"""Test app() with URL that has no app path (just /api/v1/env)."""
|
|
34
|
+
# Create instance with a URL without an app path
|
|
35
|
+
env = fleet_client.instance("https://example.com/api/v1/env")
|
|
36
|
+
|
|
37
|
+
# Access jira app
|
|
38
|
+
jira_client = env.app("jira")
|
|
39
|
+
|
|
40
|
+
# Check the constructed URL
|
|
41
|
+
assert jira_client.base_url == "https://example.com/jira/api/v1/env", \
|
|
42
|
+
f"Expected https://example.com/jira/api/v1/env, got {jira_client.base_url}"
|
|
43
|
+
|
|
44
|
+
def test_app_with_different_app_names(self, fleet_client):
|
|
45
|
+
"""Test app() with multiple different app names."""
|
|
46
|
+
env = fleet_client.instance("https://example.com/api/v1/env")
|
|
47
|
+
|
|
48
|
+
jira = env.app("jira")
|
|
49
|
+
sentry = env.app("sentry")
|
|
50
|
+
github = env.app("github")
|
|
51
|
+
|
|
52
|
+
assert jira.base_url == "https://example.com/jira/api/v1/env"
|
|
53
|
+
assert sentry.base_url == "https://example.com/sentry/api/v1/env"
|
|
54
|
+
assert github.base_url == "https://example.com/github/api/v1/env"
|
|
55
|
+
|
|
56
|
+
def test_app_caching(self, fleet_client):
|
|
57
|
+
"""Test that app() caches InstanceClient instances."""
|
|
58
|
+
env = fleet_client.instance("https://example.com/api/v1/env")
|
|
59
|
+
|
|
60
|
+
# Call app("jira") twice
|
|
61
|
+
jira1 = env.app("jira")
|
|
62
|
+
jira2 = env.app("jira")
|
|
63
|
+
|
|
64
|
+
# Should return the same cached instance
|
|
65
|
+
assert jira1 is jira2
|
|
66
|
+
|
|
67
|
+
def test_app_with_localhost(self, fleet_client):
|
|
68
|
+
"""Test app() with localhost URLs."""
|
|
69
|
+
env = fleet_client.instance("http://localhost:8080/api/v1/env")
|
|
70
|
+
|
|
71
|
+
jira = env.app("jira")
|
|
72
|
+
|
|
73
|
+
assert jira.base_url == "http://localhost:8080/jira/api/v1/env"
|
|
74
|
+
|
|
75
|
+
def test_app_with_port(self, fleet_client):
|
|
76
|
+
"""Test app() with URLs that include port numbers."""
|
|
77
|
+
env = fleet_client.instance("https://example.com:9000/api/v1/env")
|
|
78
|
+
|
|
79
|
+
jira = env.app("jira")
|
|
80
|
+
|
|
81
|
+
assert jira.base_url == "https://example.com:9000/jira/api/v1/env"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
pytest.main([__file__, "-v"])
|