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/resources/sqlite.py CHANGED
@@ -675,17 +675,97 @@ class SyncQueryBuilder:
675
675
 
676
676
 
677
677
  class SQLiteResource(Resource):
678
- def __init__(self, resource: ResourceModel, client: "SyncWrapper"):
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
- cleaned_code = re.sub(r"from fleet import.*verifier.*\n", "", cleaned_code)
290
- cleaned_code = re.sub(r"import.*verifier.*\n", "", cleaned_code)
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()
@@ -37,7 +37,7 @@ class FunctionBundler:
37
37
  ) -> bytes:
38
38
  """Create a function bundle with statically extracted code."""
39
39
 
40
- # logger.info(f"Creating function bundle for {func.__name__}")
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
- # logger.debug(f"Functions called in verifier: {called_functions}")
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
- # logger.debug(f"Found same-module dependency: {func_name}")
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
- # logger.warning(f"Could not unparse AST node: {e}")
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
- # logger.debug(f"Extracted {len(extracted_code)} items from {file_path}")
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
- # logger.warning(f"Failed to extract functions from {file_path}: {e}")
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
- # logger.debug(f"Mapped {mod} -> {package_with_version}")
467
+ logger.debug(f"Mapped {mod} -> {package_with_version}")
468
468
  except imd.PackageNotFoundError:
469
469
  # Skip stdlib or local modules
470
- # logger.debug(f"Skipping {mod} (stdlib or local)")
470
+ logger.debug(f"Skipping {mod} (stdlib or local)")
471
471
  continue
472
472
 
473
473
  package_list = list(packages)
474
- # logger.debug(f"Final package list: {package_list}")
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
- # else:
515
- # logger.debug(
516
- # f"Skipping auto-detected {req}, using explicit version instead"
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
- # logger.debug(
569
- # f"Extracted same-module dependency: {dep_name}"
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
- # logger.debug(f"Created extracted file: {relative_path}")
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
- # logger.error(f"Failed to build function bundle: {e}")
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
- # logger.debug(f"Created __init__.py: {current}")
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
- # logger.debug(f"Created function bundle ({bundle_size:,} bytes)")
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
- # logger.warning(f"Failed to extract function {function_name}: {e}")
666
- pass
665
+ logger.warning(f"Failed to extract function {function_name}: {e}")
667
666
 
668
667
  return None
669
668
 
@@ -49,7 +49,7 @@ class SyncVerifierFunction:
49
49
  )
50
50
 
51
51
  except Exception as e:
52
- # logger.error(f"Error in verifier {self.key}: {e}")
52
+ logger.error(f"Error in verifier {self.key}: {e}")
53
53
  # Return error score 0
54
54
  return 0.0
55
55
 
@@ -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
- # logger.debug(
94
- # f"Created bundle from raw code for {self.key} with SHA: {self._bundle_sha}"
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
- # logger.debug(
104
- # f"Created bundle for {self.key} with SHA: {self._bundle_sha}"
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
- # logger.debug(f"Using server-side bundle {bundle_sha[:8]}...")
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
- # logger.info(f"Bundle {bundle_sha[:8]}... found on server")
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
- # logger.warning(f"Failed to check bundle existence: {e}")
129
- pass
128
+ logger.warning(f"Failed to check bundle existence: {e}")
130
129
 
131
130
  # Bundle not found on server - upload needed
132
- # logger.info(f"Bundle {bundle_sha[:8]}... needs to be uploaded")
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
- # logger.error(f"Error in verifier {self.key}: {e}")
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
- # logger.warning(f"Could not convert result to float: {result}")
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
- # logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
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
- # logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
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
- # logger.info(f"Bundle {bundle_sha[:8]}... already cached for {self.key}")
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
- # logger.info(
289
- # f"Bundle {bundle_sha[:8]}... not found on server, uploading..."
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
- # logger.error(f"Error in remote execution of {self.key}: {e}")
303
+ logger.error(f"Error in remote execution of {self.key}: {e}")
305
304
  raise
306
305
 
307
306
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.69
3
+ Version: 0.2.69b3
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -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=B1xY5NjyE6nj98ZiOv6RQTflU00y7XzDYhK2RlT8Q2w,9205
26
- fleet/client.py,sha256=3mxNqif0Ijog-PQRJrLyg4VEyY-c68DHr-xq8sQMqdg,33926
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=iwwvN2o1IWT-9zxmAMBHfT12rfX2Hm0DkfUlrPTs298,17956
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=YWxKjyT-jk1Jh5FmLfKRUig0n1Ubyr4Be-DMfl75hYM,9225
35
- fleet/_async/client.py,sha256=8zLGgi-Bfl0M7ZUUC4Q3FpXbGe0XK389TE1EB7b9QSs,34440
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=8d65DBMhmHNaLer1FhttUWK4WLS1SU5Hx9_nALMwM4Y,18002
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=kcrmLZciQxvPSfTtbEq5LIbhscwDHg6WIiNcPyM4L9w,6085
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=up_umepfyX9PDFsnmEMJLjsj7bLa6a3wizZOgMGkK1Q,27409
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=9aWWXFsovBPcndE06IATn5jaeli5fRORAYeenF9heN0,26264
52
- fleet/_async/verifiers/verifier.py,sha256=Zvok2Mog09l885StW429Rg_8_4bd-gaYUGIgpILeb_I,14207
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=XM_Qmd7pUzC3-dCMTh6orTEsGeWJXbKueBlvcWcSUuI,5897
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=bR6d1zYQ4cMAlmZIJ7jqmY9-N-GokXaDhUyGKTWHsfY,26811
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=9aWWXFsovBPcndE06IATn5jaeli5fRORAYeenF9heN0,26264
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=RuTjjDijbicNfMSjA7HcTpKueEki5dzNOdTuHS7UoZs,3262
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=npnTBB-A1Cl66gNOGPR6UaybvcDy6C6_hWchyIJeDyc,14252
72
- fleet_python-0.2.69.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
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.69.dist-info/METADATA,sha256=W0uTnvlkRrRBfTzSYTi0Tb8qxCFFOoD0Hw1GUwFgIAA,3304
78
- fleet_python-0.2.69.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
- fleet_python-0.2.69.dist-info/top_level.txt,sha256=qb1zIbtEktyhRFZdqVytwg54l64qtoZL0wjHB4bUg3c,29
80
- fleet_python-0.2.69.dist-info/RECORD,,
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,,
@@ -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"])