codeflash 0.7.2__tar.gz → 0.7.3__tar.gz

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.
Files changed (66) hide show
  1. {codeflash-0.7.2 → codeflash-0.7.3}/PKG-INFO +1 -1
  2. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cli.py +1 -0
  3. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/instrument_existing_tests.py +3 -3
  4. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/discover_unit_tests.py +14 -4
  5. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/optimizer.py +6 -5
  6. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/parse_test_output.py +23 -10
  7. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/verification_utils.py +4 -2
  8. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/verifier.py +4 -15
  9. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/version.py +2 -2
  10. {codeflash-0.7.2 → codeflash-0.7.3}/pyproject.toml +1 -1
  11. {codeflash-0.7.2 → codeflash-0.7.3}/README.md +0 -0
  12. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/.env.example +0 -0
  13. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/LICENSE +0 -0
  14. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/__init__.py +0 -0
  15. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/__init__.py +0 -0
  16. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/aiservice.py +0 -0
  17. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/api/cfapi.py +0 -0
  18. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/__init__.py +0 -0
  19. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cli_common.py +0 -0
  20. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/cmd_init.py +0 -0
  21. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/console.py +0 -0
  22. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/logging_config.py +0 -0
  23. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/cli_cmds/workflows/codeflash-optimize.yaml +0 -0
  24. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/__init__.py +0 -0
  25. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_extractor.py +0 -0
  26. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_replacer.py +0 -0
  27. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/code_utils.py +0 -0
  28. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/compat.py +0 -0
  29. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/config_consts.py +0 -0
  30. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/config_parser.py +0 -0
  31. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/env_utils.py +0 -0
  32. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/formatter.py +0 -0
  33. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/git_utils.py +0 -0
  34. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/github_utils.py +0 -0
  35. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/remove_generated_tests.py +0 -0
  36. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/shell_utils.py +0 -0
  37. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/code_utils/time_utils.py +0 -0
  38. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/__init__.py +0 -0
  39. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/discovery/functions_to_optimize.py +0 -0
  40. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/github/PrComment.py +0 -0
  41. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/github/__init__.py +0 -0
  42. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/main.py +0 -0
  43. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/ExperimentMetadata.py +0 -0
  44. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/__init__.py +0 -0
  45. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/models/models.py +0 -0
  46. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/__init__.py +0 -0
  47. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/optimization/function_context.py +0 -0
  48. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/__init__.py +0 -0
  49. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/create_pr.py +0 -0
  50. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/critic.py +0 -0
  51. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/result/explanation.py +0 -0
  52. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/__init__.py +0 -0
  53. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/posthog_cf.py +0 -0
  54. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/telemetry/sentry.py +0 -0
  55. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracer.py +0 -0
  56. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/__init__.py +0 -0
  57. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/profile_stats.py +0 -0
  58. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/replay_test.py +0 -0
  59. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/tracing/tracing_utils.py +0 -0
  60. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/update_license_version.py +0 -0
  61. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/__init__.py +0 -0
  62. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/comparator.py +0 -0
  63. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/equivalence.py +0 -0
  64. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/pytest_plugin.py +0 -0
  65. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/test_results.py +0 -0
  66. {codeflash-0.7.2 → codeflash-0.7.3}/codeflash/verification/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codeflash
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Client for codeflash.ai - automatic code performance optimization, powered by AI
5
5
  Home-page: https://codeflash.ai
6
6
  License: BSL-1.1
@@ -170,6 +170,7 @@ def process_pyproject_config(args: Namespace) -> Namespace:
170
170
  # in this case, the ".." becomes outside project scope, causing issues with un-importable paths
171
171
  args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path)
172
172
  args.tests_root = Path(args.tests_root).resolve()
173
+ args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)
173
174
  return handle_optimize_all_arg_parsing(args)
174
175
 
175
176
 
@@ -323,7 +323,7 @@ def inject_profiling_into_existing_test(
323
323
  test_path: Path,
324
324
  call_positions: list[CodePosition],
325
325
  function_to_optimize: FunctionToOptimize,
326
- root_path: Path,
326
+ tests_project_root: Path,
327
327
  test_framework: str,
328
328
  ) -> tuple[bool, str | None]:
329
329
  with test_path.open(encoding="utf8") as f:
@@ -334,12 +334,12 @@ def inject_profiling_into_existing_test(
334
334
  logger.exception(f"Syntax error in code in file - {test_path}")
335
335
  return False, None
336
336
  # TODO: Pass the full name of function here, otherwise we can run into namespace clashes
337
- module_path = module_name_from_file_path(test_path, root_path)
337
+ test_module_path = module_name_from_file_path(test_path, tests_project_root)
338
338
  import_visitor = FunctionImportedAsVisitor(function_to_optimize)
339
339
  import_visitor.visit(tree)
340
340
  func = import_visitor.imported_as
341
341
 
342
- tree = InjectPerfOnly(func, module_path, test_framework, call_positions).visit(tree)
342
+ tree = InjectPerfOnly(func, test_module_path, test_framework, call_positions).visit(tree)
343
343
  new_imports = [
344
344
  ast.Import(names=[ast.alias(name="time")]),
345
345
  ast.Import(names=[ast.alias(name="gc")]),
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import re
5
+ import sys
5
6
  import unittest
6
7
  from collections import defaultdict
7
8
  from multiprocessing import Process, Queue
@@ -65,11 +66,15 @@ def run_pytest_discovery_new_process(queue: Queue, cwd: str, tests_root: str) ->
65
66
 
66
67
  os.chdir(cwd)
67
68
  collected_tests = []
69
+ pytest_rootdir: Path | None = None
68
70
  tests: list[TestsInFile] = []
71
+ sys.path.insert(1, str(cwd))
69
72
 
70
73
  class PytestCollectionPlugin:
71
74
  def pytest_collection_finish(self, session) -> None:
75
+ nonlocal pytest_rootdir
72
76
  collected_tests.extend(session.items)
77
+ pytest_rootdir = Path(session.config.rootdir)
73
78
 
74
79
  try:
75
80
  exitcode = pytest.main(
@@ -79,9 +84,9 @@ def run_pytest_discovery_new_process(queue: Queue, cwd: str, tests_root: str) ->
79
84
  except Exception as e:
80
85
  logger.exception(f"Failed to collect tests: {e!s}")
81
86
  exitcode = -1
82
- queue.put((exitcode, tests))
87
+ queue.put((exitcode, tests, pytest_rootdir))
83
88
  tests = parse_pytest_collection_results(collected_tests)
84
- queue.put((exitcode, tests))
89
+ queue.put((exitcode, tests, pytest_rootdir))
85
90
 
86
91
 
87
92
  def parse_pytest_collection_results(
@@ -116,10 +121,15 @@ def discover_tests_pytest(
116
121
  q: Queue = Queue()
117
122
  p: Process = Process(target=run_pytest_discovery_new_process, args=(q, project_root, tests_root))
118
123
  p.start()
119
- exitcode, tests = q.get()
124
+ exitcode, tests, pytest_rootdir = q.get()
120
125
  p.join()
121
- logger.debug(f"Pytest collection exit code: {exitcode}")
122
126
 
127
+ if exitcode != 0:
128
+ logger.warning(f"Failed to collect tests. Pytest Exit code: {exitcode}")
129
+ else:
130
+ logger.debug(f"Pytest collection exit code: {exitcode}")
131
+ if pytest_rootdir is not None:
132
+ cfg.tests_project_rootdir = pytest_rootdir
123
133
  file_to_test_map = defaultdict(list)
124
134
  for test in tests:
125
135
  if discover_only_these_tests and test.test_file not in discover_only_these_tests:
@@ -100,6 +100,7 @@ class Optimizer:
100
100
 
101
101
  self.test_cfg = TestConfig(
102
102
  tests_root=args.tests_root,
103
+ tests_project_rootdir=args.test_project_root,
103
104
  project_root_path=args.project_root,
104
105
  test_framework=args.test_framework,
105
106
  pytest_cmd=args.pytest_cmd,
@@ -751,11 +752,11 @@ class Optimizer:
751
752
  path_obj_test_file = Path(test_file)
752
753
  relevant_test_files_count += 1
753
754
  success, injected_test = inject_profiling_into_existing_test(
754
- path_obj_test_file,
755
- positions,
756
- function_to_optimize,
757
- self.args.project_root,
758
- self.args.test_framework,
755
+ test_path=path_obj_test_file,
756
+ call_positions=positions,
757
+ function_to_optimize=function_to_optimize,
758
+ tests_project_root=self.test_cfg.tests_project_rootdir,
759
+ test_framework=self.args.test_framework,
759
760
  )
760
761
  if not success:
761
762
  continue
@@ -67,7 +67,7 @@ def parse_test_return_values_bin(
67
67
  invocation_id_object = InvocationId.from_str_id(encoded_test_name, invocation_id)
68
68
  test_file_path = file_path_from_module_name(
69
69
  invocation_id_object.test_module_path,
70
- test_config.project_root_path,
70
+ test_config.tests_project_rootdir,
71
71
  )
72
72
 
73
73
  test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path)
@@ -111,7 +111,7 @@ def parse_sqlite_test_results(
111
111
  for val in data:
112
112
  try:
113
113
  test_module_path = val[0]
114
- test_file_path = file_path_from_module_name(test_module_path, test_config.project_root_path)
114
+ test_file_path = file_path_from_module_name(test_module_path, test_config.tests_project_rootdir)
115
115
  # TODO : this is because sqlite writes original file module path. Should make it consistent
116
116
  test_type = test_files.get_test_type_by_original_file_path(test_file_path)
117
117
  loop_index = val[4]
@@ -159,7 +159,11 @@ def parse_test_xml(
159
159
  f"Failed to parse {test_xml_file_path} as JUnitXml. Exception: {e}",
160
160
  )
161
161
  return test_results
162
-
162
+ base_dir = (
163
+ test_config.tests_project_rootdir
164
+ if test_config.test_framework == "pytest"
165
+ else test_config.project_root_path
166
+ )
163
167
  for suite in xml:
164
168
  for testcase in suite:
165
169
  class_name = testcase.classname
@@ -173,9 +177,14 @@ def parse_test_xml(
173
177
  # This means that the test failed to load, so we don't want to crash on it
174
178
  logger.info("Test failed to load, skipping it.")
175
179
  if run_result is not None:
176
- logger.info(
177
- f"Test log - STDOUT : {run_result.stdout.decode()} \n STDERR : {run_result.stderr.decode()}",
178
- )
180
+ if isinstance(run_result.stdout, str) and isinstance(run_result.stderr, str):
181
+ logger.info(
182
+ f"Test log - STDOUT : {run_result.stdout} \n STDERR : {run_result.stderr}",
183
+ )
184
+ else:
185
+ logger.info(
186
+ f"Test log - STDOUT : {run_result.stdout.decode()} \n STDERR : {run_result.stderr.decode()}",
187
+ )
179
188
  return test_results
180
189
 
181
190
  test_class_path = testcase.classname
@@ -185,18 +194,22 @@ def parse_test_xml(
185
194
  # TODO : This might not be true if the test is organized under a class
186
195
  test_file_path = file_path_from_module_name(
187
196
  test_class_path,
188
- test_config.project_root_path,
197
+ base_dir,
189
198
  )
190
199
  else:
191
- test_file_path = file_path_from_module_name(test_function, test_config.project_root_path)
200
+ test_file_path = file_path_from_module_name(
201
+ test_function,
202
+ base_dir,
203
+ )
192
204
  else:
193
- test_file_path = test_config.project_root_path / test_file_name
205
+ # TODO: not sure which root path fits better here
206
+ test_file_path = base_dir / test_file_name
194
207
  if not test_file_path.exists():
195
208
  logger.warning(f"Could not find the test for file name - {test_file_path} ")
196
209
  continue
197
210
  test_type = test_files.get_test_type_by_instrumented_file_path(test_file_path)
198
211
  assert test_type is not None, f"Test type not found for {test_file_path}"
199
- test_module_path = module_name_from_file_path(test_file_path, test_config.project_root_path)
212
+ test_module_path = module_name_from_file_path(test_file_path, test_config.tests_project_rootdir)
200
213
  result = testcase.is_passed # TODO: See for the cases of ERROR and SKIPPED
201
214
  test_class = None
202
215
  if class_name is not None and class_name.startswith(test_module_path):
@@ -1,5 +1,4 @@
1
1
  import ast
2
- import os
3
2
  from pathlib import Path
4
3
 
5
4
  from pydantic.dataclasses import dataclass
@@ -67,9 +66,12 @@ class ModifyInspiredTests(ast.NodeTransformer):
67
66
  return node
68
67
 
69
68
 
70
- @dataclass(frozen=True)
69
+ @dataclass
71
70
  class TestConfig:
72
71
  tests_root: Path
73
72
  project_root_path: Path
74
73
  test_framework: str
74
+ tests_project_rootdir: Path
75
+ # tests_project_rootdir corresponds to pytest rootdir,
76
+ # or for unittest - project_root_from_module_root(args.tests_root, pyproject_file_path)
75
77
  pytest_cmd: str = "pytest"
@@ -38,7 +38,7 @@ def generate_tests(
38
38
  module = importlib.import_module(str(module_path))
39
39
  generated_test_source = module.CACHED_TESTS
40
40
  instrumented_test_source = module.CACHED_INSTRUMENTED_TESTS
41
- temp_run_dir = get_run_tmp_file(Path(""))
41
+ temp_run_dir = get_run_tmp_file(Path())
42
42
  path = str(temp_run_dir).replace("\\", "\\\\") # Escape backslash for windows paths
43
43
  instrumented_test_source = instrumented_test_source.replace(
44
44
  "{codeflash_run_tmp_dir_client_side}",
@@ -46,12 +46,8 @@ def generate_tests(
46
46
  )
47
47
  logger.info(f"Using cached tests from {module_path}.CACHED_TESTS")
48
48
  else:
49
- test_module_path = Path(
50
- module_name_from_file_path(
51
- get_test_file_path(test_cfg.tests_root, function_to_optimize.function_name, 0),
52
- test_cfg.project_root_path,
53
- ),
54
- )
49
+ test_file_path = get_test_file_path(test_cfg.tests_root, function_to_optimize.function_name, 0)
50
+ test_module_path = Path(module_name_from_file_path(test_file_path, test_cfg.tests_project_rootdir))
55
51
  response = aiservice_client.generate_regression_tests(
56
52
  source_code_being_tested=source_code_being_tested,
57
53
  function_to_optimize=function_to_optimize,
@@ -65,7 +61,7 @@ def generate_tests(
65
61
  )
66
62
  if response and isinstance(response, tuple) and len(response) == 2:
67
63
  generated_test_source, instrumented_test_source = response
68
- temp_run_dir = get_run_tmp_file(Path(""))
64
+ temp_run_dir = get_run_tmp_file(Path())
69
65
  path = str(temp_run_dir).replace("\\", "\\\\")
70
66
  instrumented_test_source = instrumented_test_source.replace(
71
67
  "{codeflash_run_tmp_dir_client_side}",
@@ -77,13 +73,6 @@ def generate_tests(
77
73
  )
78
74
  return None
79
75
 
80
- # TODO: Add support for inspired tests
81
- # inspired_unit_tests = ""
82
-
83
- # merged_test_source = merge_unit_tests(
84
- # instrumented_test_source, inspired_unit_tests, test_cfg.test_framework
85
- # )
86
-
87
76
  return generated_test_source, instrumented_test_source
88
77
 
89
78
 
@@ -1,3 +1,3 @@
1
1
  # These version placeholders will be replaced by poetry-dynamic-versioning during `poetry build`.
2
- __version__ = "0.7.2"
3
- __version_tuple__ = (0, 7, 2)
2
+ __version__ = "0.7.3"
3
+ __version_tuple__ = (0, 7, 3)
@@ -1,7 +1,7 @@
1
1
  [tool]
2
2
  [tool.poetry]
3
3
  name = "codeflash"
4
- version = "0.7.2" # Determined by poetry-dynamic-versioning during `poetry build`
4
+ version = "0.7.3" # Determined by poetry-dynamic-versioning during `poetry build`
5
5
  description = "Client for codeflash.ai - automatic code performance optimization, powered by AI"
6
6
  license = "BSL-1.1"
7
7
  authors = ["CodeFlash Inc. <contact@codeflash.ai>"]
File without changes
File without changes
File without changes
File without changes