shakeout 0.1.0__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 (364) hide show
  1. shakeout-0.1.0/.github/workflows/ci.yml +120 -0
  2. shakeout-0.1.0/.github/workflows/docs.yml +47 -0
  3. shakeout-0.1.0/.github/workflows/release.yml +77 -0
  4. shakeout-0.1.0/.gitignore +12 -0
  5. shakeout-0.1.0/.pre-commit-config.yaml +43 -0
  6. shakeout-0.1.0/CHANGELOG.md +17 -0
  7. shakeout-0.1.0/LICENSE +21 -0
  8. shakeout-0.1.0/PKG-INFO +72 -0
  9. shakeout-0.1.0/README.md +44 -0
  10. shakeout-0.1.0/docs/assets/logo.svg +8 -0
  11. shakeout-0.1.0/docs/concepts/how-it-works.md +35 -0
  12. shakeout-0.1.0/docs/concepts/import-rewriting.md +28 -0
  13. shakeout-0.1.0/docs/concepts/index.md +7 -0
  14. shakeout-0.1.0/docs/concepts/side-effects.md +47 -0
  15. shakeout-0.1.0/docs/concepts/soundness.md +27 -0
  16. shakeout-0.1.0/docs/concepts/targets.md +31 -0
  17. shakeout-0.1.0/docs/getting-started/features.md +27 -0
  18. shakeout-0.1.0/docs/getting-started/first-bundle.md +79 -0
  19. shakeout-0.1.0/docs/getting-started/index.md +7 -0
  20. shakeout-0.1.0/docs/getting-started/installation.md +36 -0
  21. shakeout-0.1.0/docs/guides/aws-lambda.md +55 -0
  22. shakeout-0.1.0/docs/guides/cli-tools.md +32 -0
  23. shakeout-0.1.0/docs/guides/dependencies.md +38 -0
  24. shakeout-0.1.0/docs/guides/dynamic-code.md +48 -0
  25. shakeout-0.1.0/docs/guides/index.md +9 -0
  26. shakeout-0.1.0/docs/guides/tracebacks.md +53 -0
  27. shakeout-0.1.0/docs/index.md +74 -0
  28. shakeout-0.1.0/docs/project/contributing.md +32 -0
  29. shakeout-0.1.0/docs/project/design.md +430 -0
  30. shakeout-0.1.0/docs/reference/cli.md +57 -0
  31. shakeout-0.1.0/docs/reference/configuration.md +39 -0
  32. shakeout-0.1.0/docs/reference/diagnostics.md +26 -0
  33. shakeout-0.1.0/docs/reference/index.md +7 -0
  34. shakeout-0.1.0/docs/reference/pragmas.md +22 -0
  35. shakeout-0.1.0/docs/reference/python-api.md +25 -0
  36. shakeout-0.1.0/docs/stylesheets/extra.css +69 -0
  37. shakeout-0.1.0/mkdocs.yml +99 -0
  38. shakeout-0.1.0/pyproject.toml +194 -0
  39. shakeout-0.1.0/scripts/check.sh +30 -0
  40. shakeout-0.1.0/scripts/matrix.sh +7 -0
  41. shakeout-0.1.0/scripts/mutation.sh +19 -0
  42. shakeout-0.1.0/src/shakeout/__init__.py +3 -0
  43. shakeout-0.1.0/src/shakeout/__main__.py +5 -0
  44. shakeout-0.1.0/src/shakeout/_compat.py +19 -0
  45. shakeout-0.1.0/src/shakeout/analysis/__init__.py +1 -0
  46. shakeout-0.1.0/src/shakeout/analysis/classes.py +115 -0
  47. shakeout-0.1.0/src/shakeout/analysis/effects.py +168 -0
  48. shakeout-0.1.0/src/shakeout/analysis/registry.py +220 -0
  49. shakeout-0.1.0/src/shakeout/analysis/scopes.py +515 -0
  50. shakeout-0.1.0/src/shakeout/api.py +65 -0
  51. shakeout-0.1.0/src/shakeout/cli.py +250 -0
  52. shakeout-0.1.0/src/shakeout/config.py +88 -0
  53. shakeout-0.1.0/src/shakeout/emit/__init__.py +188 -0
  54. shakeout-0.1.0/src/shakeout/emit/archive.py +141 -0
  55. shakeout-0.1.0/src/shakeout/emit/deps.py +98 -0
  56. shakeout-0.1.0/src/shakeout/emit/minify.py +105 -0
  57. shakeout-0.1.0/src/shakeout/emit/single.py +63 -0
  58. shakeout-0.1.0/src/shakeout/emit/tree.py +59 -0
  59. shakeout-0.1.0/src/shakeout/graph.py +249 -0
  60. shakeout-0.1.0/src/shakeout/model.py +136 -0
  61. shakeout-0.1.0/src/shakeout/options.py +58 -0
  62. shakeout-0.1.0/src/shakeout/project.py +545 -0
  63. shakeout-0.1.0/src/shakeout/py.typed +0 -0
  64. shakeout-0.1.0/src/shakeout/report.py +48 -0
  65. shakeout-0.1.0/src/shakeout/resolve.py +135 -0
  66. shakeout-0.1.0/src/shakeout/result.py +185 -0
  67. shakeout-0.1.0/src/shakeout/runtime.py +234 -0
  68. shakeout-0.1.0/src/shakeout/trace.py +94 -0
  69. shakeout-0.1.0/src/shakeout/uvstage.py +95 -0
  70. shakeout-0.1.0/tests/__init__.py +0 -0
  71. shakeout-0.1.0/tests/cases/basics/annotations.case +20 -0
  72. shakeout-0.1.0/tests/cases/basics/annotations.golden +9 -0
  73. shakeout-0.1.0/tests/cases/basics/argv_and_exit.case +9 -0
  74. shakeout-0.1.0/tests/cases/basics/argv_and_exit.golden +5 -0
  75. shakeout-0.1.0/tests/cases/basics/closure_globals.case +20 -0
  76. shakeout-0.1.0/tests/cases/basics/closure_globals.golden +8 -0
  77. shakeout-0.1.0/tests/cases/basics/comments_preserved.case +15 -0
  78. shakeout-0.1.0/tests/cases/basics/comments_preserved.golden +6 -0
  79. shakeout-0.1.0/tests/cases/basics/default_args.case +15 -0
  80. shakeout-0.1.0/tests/cases/basics/default_args.golden +7 -0
  81. shakeout-0.1.0/tests/cases/basics/docstring_entry.case +9 -0
  82. shakeout-0.1.0/tests/cases/basics/docstring_entry.golden +6 -0
  83. shakeout-0.1.0/tests/cases/basics/empty_entry.case +3 -0
  84. shakeout-0.1.0/tests/cases/basics/empty_entry.golden +1 -0
  85. shakeout-0.1.0/tests/cases/basics/future_annotations.case +19 -0
  86. shakeout-0.1.0/tests/cases/basics/future_annotations.golden +9 -0
  87. shakeout-0.1.0/tests/cases/basics/main_guard_entry.case +10 -0
  88. shakeout-0.1.0/tests/cases/basics/main_guard_entry.golden +3 -0
  89. shakeout-0.1.0/tests/cases/basics/main_guard_library.case +20 -0
  90. shakeout-0.1.0/tests/cases/basics/main_guard_library.golden +7 -0
  91. shakeout-0.1.0/tests/cases/basics/recursion.case +27 -0
  92. shakeout-0.1.0/tests/cases/basics/recursion.golden +9 -0
  93. shakeout-0.1.0/tests/cases/basics/redefinition.case +14 -0
  94. shakeout-0.1.0/tests/cases/basics/redefinition.golden +6 -0
  95. shakeout-0.1.0/tests/cases/basics/same_line_statements.case +11 -0
  96. shakeout-0.1.0/tests/cases/basics/same_line_statements.golden +6 -0
  97. shakeout-0.1.0/tests/cases/basics/transitive.case +27 -0
  98. shakeout-0.1.0/tests/cases/basics/transitive.golden +9 -0
  99. shakeout-0.1.0/tests/cases/basics/unused_class.case +17 -0
  100. shakeout-0.1.0/tests/cases/basics/unused_class.golden +6 -0
  101. shakeout-0.1.0/tests/cases/basics/unused_constants.case +14 -0
  102. shakeout-0.1.0/tests/cases/basics/unused_constants.golden +9 -0
  103. shakeout-0.1.0/tests/cases/basics/unused_function.case +15 -0
  104. shakeout-0.1.0/tests/cases/basics/unused_function.golden +6 -0
  105. shakeout-0.1.0/tests/cases/basics/unused_in_entry.case +17 -0
  106. shakeout-0.1.0/tests/cases/basics/unused_in_entry.golden +5 -0
  107. shakeout-0.1.0/tests/cases/classes/assume_pure_bases_option.case +16 -0
  108. shakeout-0.1.0/tests/cases/classes/assume_pure_bases_option.golden +8 -0
  109. shakeout-0.1.0/tests/cases/classes/base_from_unknown_external.case +11 -0
  110. shakeout-0.1.0/tests/cases/classes/base_from_unknown_external.golden +5 -0
  111. shakeout-0.1.0/tests/cases/classes/class_attribute_reference.case +15 -0
  112. shakeout-0.1.0/tests/cases/classes/class_attribute_reference.golden +7 -0
  113. shakeout-0.1.0/tests/cases/classes/class_body_side_effect.case +10 -0
  114. shakeout-0.1.0/tests/cases/classes/class_body_side_effect.golden +5 -0
  115. shakeout-0.1.0/tests/cases/classes/class_decorator_registry.case +19 -0
  116. shakeout-0.1.0/tests/cases/classes/class_decorator_registry.golden +7 -0
  117. shakeout-0.1.0/tests/cases/classes/class_keyword_args.case +18 -0
  118. shakeout-0.1.0/tests/cases/classes/class_keyword_args.golden +7 -0
  119. shakeout-0.1.0/tests/cases/classes/class_scope_not_visible_in_methods.case +16 -0
  120. shakeout-0.1.0/tests/cases/classes/class_scope_not_visible_in_methods.golden +6 -0
  121. shakeout-0.1.0/tests/cases/classes/dataclass_fields.case +21 -0
  122. shakeout-0.1.0/tests/cases/classes/dataclass_fields.golden +8 -0
  123. shakeout-0.1.0/tests/cases/classes/init_subclass_inherited_two_levels.case +24 -0
  124. shakeout-0.1.0/tests/cases/classes/init_subclass_inherited_two_levels.golden +10 -0
  125. shakeout-0.1.0/tests/cases/classes/init_subclass_registry.case +25 -0
  126. shakeout-0.1.0/tests/cases/classes/init_subclass_registry.golden +10 -0
  127. shakeout-0.1.0/tests/cases/classes/metaclass_registry.case +31 -0
  128. shakeout-0.1.0/tests/cases/classes/metaclass_registry.golden +11 -0
  129. shakeout-0.1.0/tests/cases/classes/method_references.case +22 -0
  130. shakeout-0.1.0/tests/cases/classes/method_references.golden +7 -0
  131. shakeout-0.1.0/tests/cases/classes/plain_inheritance_removed.case +24 -0
  132. shakeout-0.1.0/tests/cases/classes/plain_inheritance_removed.golden +8 -0
  133. shakeout-0.1.0/tests/cases/classes/stdlib_bases_removed.case +39 -0
  134. shakeout-0.1.0/tests/cases/classes/stdlib_bases_removed.golden +15 -0
  135. shakeout-0.1.0/tests/cases/dynamic/dunder_import_constant.case +8 -0
  136. shakeout-0.1.0/tests/cases/dynamic/dunder_import_constant.golden +5 -0
  137. shakeout-0.1.0/tests/cases/dynamic/eval_exec.case +14 -0
  138. shakeout-0.1.0/tests/cases/dynamic/eval_exec.golden +7 -0
  139. shakeout-0.1.0/tests/cases/dynamic/getattr_on_module.case +17 -0
  140. shakeout-0.1.0/tests/cases/dynamic/getattr_on_module.golden +8 -0
  141. shakeout-0.1.0/tests/cases/dynamic/globals_lookup.case +21 -0
  142. shakeout-0.1.0/tests/cases/dynamic/globals_lookup.golden +10 -0
  143. shakeout-0.1.0/tests/cases/dynamic/import_module_computed_with_keep.case +13 -0
  144. shakeout-0.1.0/tests/cases/dynamic/import_module_computed_with_keep.golden +8 -0
  145. shakeout-0.1.0/tests/cases/dynamic/import_module_constant.case +17 -0
  146. shakeout-0.1.0/tests/cases/dynamic/import_module_constant.golden +9 -0
  147. shakeout-0.1.0/tests/cases/dynamic/import_module_fully_computed.case +16 -0
  148. shakeout-0.1.0/tests/cases/dynamic/import_module_fully_computed.golden +6 -0
  149. shakeout-0.1.0/tests/cases/dynamic/import_module_prefix_glob.case +17 -0
  150. shakeout-0.1.0/tests/cases/dynamic/import_module_prefix_glob.golden +11 -0
  151. shakeout-0.1.0/tests/cases/dynamic/keep_symbol_option.case +16 -0
  152. shakeout-0.1.0/tests/cases/dynamic/keep_symbol_option.golden +6 -0
  153. shakeout-0.1.0/tests/cases/dynamic/module_passed_as_value.case +19 -0
  154. shakeout-0.1.0/tests/cases/dynamic/module_passed_as_value.golden +7 -0
  155. shakeout-0.1.0/tests/cases/dynamic/star_import.case +14 -0
  156. shakeout-0.1.0/tests/cases/dynamic/star_import.golden +6 -0
  157. shakeout-0.1.0/tests/cases/dynamic/star_import_respects_all.case +16 -0
  158. shakeout-0.1.0/tests/cases/dynamic/star_import_respects_all.golden +7 -0
  159. shakeout-0.1.0/tests/cases/dynamic/sys_modules_self.case +21 -0
  160. shakeout-0.1.0/tests/cases/dynamic/sys_modules_self.golden +10 -0
  161. shakeout-0.1.0/tests/cases/dynamic/vars_module.case +11 -0
  162. shakeout-0.1.0/tests/cases/dynamic/vars_module.golden +8 -0
  163. shakeout-0.1.0/tests/cases/effects/augmented_assignment.case +9 -0
  164. shakeout-0.1.0/tests/cases/effects/augmented_assignment.golden +6 -0
  165. shakeout-0.1.0/tests/cases/effects/conditional_definitions.case +15 -0
  166. shakeout-0.1.0/tests/cases/effects/conditional_definitions.golden +7 -0
  167. shakeout-0.1.0/tests/cases/effects/decorator_registry.case +28 -0
  168. shakeout-0.1.0/tests/cases/effects/decorator_registry.golden +12 -0
  169. shakeout-0.1.0/tests/cases/effects/decorator_with_args_registry.case +27 -0
  170. shakeout-0.1.0/tests/cases/effects/decorator_with_args_registry.golden +8 -0
  171. shakeout-0.1.0/tests/cases/effects/global_created_by_function.case +12 -0
  172. shakeout-0.1.0/tests/cases/effects/global_created_by_function.golden +6 -0
  173. shakeout-0.1.0/tests/cases/effects/global_written_by_function.case +15 -0
  174. shakeout-0.1.0/tests/cases/effects/global_written_by_function.golden +7 -0
  175. shakeout-0.1.0/tests/cases/effects/impure_initializer_kept.case +14 -0
  176. shakeout-0.1.0/tests/cases/effects/impure_initializer_kept.golden +6 -0
  177. shakeout-0.1.0/tests/cases/effects/keep_pragma.case +10 -0
  178. shakeout-0.1.0/tests/cases/effects/keep_pragma.golden +5 -0
  179. shakeout-0.1.0/tests/cases/effects/module_mutates_other_module.case +13 -0
  180. shakeout-0.1.0/tests/cases/effects/module_mutates_other_module.golden +9 -0
  181. shakeout-0.1.0/tests/cases/effects/mutation_of_global_container.case +10 -0
  182. shakeout-0.1.0/tests/cases/effects/mutation_of_global_container.golden +6 -0
  183. shakeout-0.1.0/tests/cases/effects/pure_call_initializers.case +19 -0
  184. shakeout-0.1.0/tests/cases/effects/pure_call_initializers.golden +15 -0
  185. shakeout-0.1.0/tests/cases/effects/pure_call_option.case +14 -0
  186. shakeout-0.1.0/tests/cases/effects/pure_call_option.golden +6 -0
  187. shakeout-0.1.0/tests/cases/effects/pure_decorators_removed.case +36 -0
  188. shakeout-0.1.0/tests/cases/effects/pure_decorators_removed.golden +13 -0
  189. shakeout-0.1.0/tests/cases/effects/pure_pragma.case +13 -0
  190. shakeout-0.1.0/tests/cases/effects/pure_pragma.golden +6 -0
  191. shakeout-0.1.0/tests/cases/effects/sorted_with_callback_is_impure.case +14 -0
  192. shakeout-0.1.0/tests/cases/effects/sorted_with_callback_is_impure.golden +6 -0
  193. shakeout-0.1.0/tests/cases/effects/stdlib_registration.case +15 -0
  194. shakeout-0.1.0/tests/cases/effects/stdlib_registration.golden +8 -0
  195. shakeout-0.1.0/tests/cases/effects/third_party_import_is_effect.case +12 -0
  196. shakeout-0.1.0/tests/cases/effects/third_party_import_is_effect.golden +6 -0
  197. shakeout-0.1.0/tests/cases/errors/chained_exceptions.case +16 -0
  198. shakeout-0.1.0/tests/cases/errors/chained_exceptions.golden +6 -0
  199. shakeout-0.1.0/tests/cases/errors/error_at_import_time.case +11 -0
  200. shakeout-0.1.0/tests/cases/errors/error_at_import_time.golden +6 -0
  201. shakeout-0.1.0/tests/cases/errors/format_exc_has_source.case +15 -0
  202. shakeout-0.1.0/tests/cases/errors/format_exc_has_source.golden +7 -0
  203. shakeout-0.1.0/tests/cases/errors/inspect_getsource.case +15 -0
  204. shakeout-0.1.0/tests/cases/errors/inspect_getsource.golden +8 -0
  205. shakeout-0.1.0/tests/cases/errors/removed_name_imported_by_generated_code.case +20 -0
  206. shakeout-0.1.0/tests/cases/errors/removed_name_imported_by_generated_code.golden +8 -0
  207. shakeout-0.1.0/tests/cases/errors/removed_symbol_attribute_error.case +22 -0
  208. shakeout-0.1.0/tests/cases/errors/removed_symbol_attribute_error.golden +9 -0
  209. shakeout-0.1.0/tests/cases/errors/traceback_through_modules.case +33 -0
  210. shakeout-0.1.0/tests/cases/errors/traceback_through_modules.golden +14 -0
  211. shakeout-0.1.0/tests/cases/errors/warnings_location.case +19 -0
  212. shakeout-0.1.0/tests/cases/errors/warnings_location.golden +10 -0
  213. shakeout-0.1.0/tests/cases/fuzz/kept_import_requires_names.case +43 -0
  214. shakeout-0.1.0/tests/cases/fuzz/kept_import_requires_names.golden +16 -0
  215. shakeout-0.1.0/tests/cases/fuzz/whole_module_imports.case +74 -0
  216. shakeout-0.1.0/tests/cases/fuzz/whole_module_imports.golden +21 -0
  217. shakeout-0.1.0/tests/cases/imports/alias.case +20 -0
  218. shakeout-0.1.0/tests/cases/imports/alias.golden +8 -0
  219. shakeout-0.1.0/tests/cases/imports/attribute_access.case +15 -0
  220. shakeout-0.1.0/tests/cases/imports/attribute_access.golden +6 -0
  221. shakeout-0.1.0/tests/cases/imports/circular.case +28 -0
  222. shakeout-0.1.0/tests/cases/imports/circular.golden +11 -0
  223. shakeout-0.1.0/tests/cases/imports/circular_from_import.case +18 -0
  224. shakeout-0.1.0/tests/cases/imports/circular_from_import.golden +10 -0
  225. shakeout-0.1.0/tests/cases/imports/dotted_import.case +17 -0
  226. shakeout-0.1.0/tests/cases/imports/dotted_import.golden +8 -0
  227. shakeout-0.1.0/tests/cases/imports/dotted_import_as.case +16 -0
  228. shakeout-0.1.0/tests/cases/imports/dotted_import_as.golden +7 -0
  229. shakeout-0.1.0/tests/cases/imports/entry_imported_by_name.case +6 -0
  230. shakeout-0.1.0/tests/cases/imports/entry_imported_by_name.golden +6 -0
  231. shakeout-0.1.0/tests/cases/imports/from_package_import_submodule.case +16 -0
  232. shakeout-0.1.0/tests/cases/imports/from_package_import_submodule.golden +7 -0
  233. shakeout-0.1.0/tests/cases/imports/import_unused_but_module_needed_elsewhere.case +17 -0
  234. shakeout-0.1.0/tests/cases/imports/import_unused_but_module_needed_elsewhere.golden +9 -0
  235. shakeout-0.1.0/tests/cases/imports/local_import.case +25 -0
  236. shakeout-0.1.0/tests/cases/imports/local_import.golden +7 -0
  237. shakeout-0.1.0/tests/cases/imports/local_import_shadows_global.case +19 -0
  238. shakeout-0.1.0/tests/cases/imports/local_import_shadows_global.golden +6 -0
  239. shakeout-0.1.0/tests/cases/imports/missing_external_dependency.case +6 -0
  240. shakeout-0.1.0/tests/cases/imports/missing_external_dependency.golden +4 -0
  241. shakeout-0.1.0/tests/cases/imports/reexport_chain.case +24 -0
  242. shakeout-0.1.0/tests/cases/imports/reexport_chain.golden +13 -0
  243. shakeout-0.1.0/tests/cases/imports/relative_levels.case +24 -0
  244. shakeout-0.1.0/tests/cases/imports/relative_levels.golden +17 -0
  245. shakeout-0.1.0/tests/cases/imports/rewrite_all_aliases_unused.case +20 -0
  246. shakeout-0.1.0/tests/cases/imports/rewrite_all_aliases_unused.golden +7 -0
  247. shakeout-0.1.0/tests/cases/imports/rewrite_relative_and_submodule.case +22 -0
  248. shakeout-0.1.0/tests/cases/imports/rewrite_relative_and_submodule.golden +12 -0
  249. shakeout-0.1.0/tests/cases/imports/rewrite_unused_aliases.case +18 -0
  250. shakeout-0.1.0/tests/cases/imports/rewrite_unused_aliases.golden +7 -0
  251. shakeout-0.1.0/tests/cases/imports/same_name_different_packages.case +25 -0
  252. shakeout-0.1.0/tests/cases/imports/same_name_different_packages.golden +12 -0
  253. shakeout-0.1.0/tests/cases/imports/side_effect_module_kept.case +13 -0
  254. shakeout-0.1.0/tests/cases/imports/side_effect_module_kept.golden +6 -0
  255. shakeout-0.1.0/tests/cases/imports/stdlib_external.case +18 -0
  256. shakeout-0.1.0/tests/cases/imports/stdlib_external.golden +9 -0
  257. shakeout-0.1.0/tests/cases/imports/submodule_attribute_after_import.case +12 -0
  258. shakeout-0.1.0/tests/cases/imports/submodule_attribute_after_import.golden +7 -0
  259. shakeout-0.1.0/tests/cases/imports/try_except_import.case +12 -0
  260. shakeout-0.1.0/tests/cases/imports/try_except_import.golden +6 -0
  261. shakeout-0.1.0/tests/cases/imports/type_checking_block.case +21 -0
  262. shakeout-0.1.0/tests/cases/imports/type_checking_block.golden +9 -0
  263. shakeout-0.1.0/tests/cases/imports/unused_import_pure_module_dropped.case +11 -0
  264. shakeout-0.1.0/tests/cases/imports/unused_import_pure_module_dropped.golden +5 -0
  265. shakeout-0.1.0/tests/cases/packages/data_file_via_file.case +22 -0
  266. shakeout-0.1.0/tests/cases/packages/data_file_via_file.golden +9 -0
  267. shakeout-0.1.0/tests/cases/packages/dunder_all_kept.case +14 -0
  268. shakeout-0.1.0/tests/cases/packages/dunder_all_kept.golden +7 -0
  269. shakeout-0.1.0/tests/cases/packages/init_side_effects_order.case +14 -0
  270. shakeout-0.1.0/tests/cases/packages/init_side_effects_order.golden +8 -0
  271. shakeout-0.1.0/tests/cases/packages/module_getattr.case +10 -0
  272. shakeout-0.1.0/tests/cases/packages/module_getattr.golden +5 -0
  273. shakeout-0.1.0/tests/cases/packages/name_and_file.case +12 -0
  274. shakeout-0.1.0/tests/cases/packages/name_and_file.golden +10 -0
  275. shakeout-0.1.0/tests/cases/packages/namespace_package.case +15 -0
  276. shakeout-0.1.0/tests/cases/packages/namespace_package.golden +7 -0
  277. shakeout-0.1.0/tests/cases/packages/pickle_roundtrip.case +15 -0
  278. shakeout-0.1.0/tests/cases/packages/pickle_roundtrip.golden +8 -0
  279. shakeout-0.1.0/tests/cases/packages/pure_parent_kept_for_child.case +14 -0
  280. shakeout-0.1.0/tests/cases/packages/pure_parent_kept_for_child.golden +7 -0
  281. shakeout-0.1.0/tests/cases/scoping/class_in_function.case +17 -0
  282. shakeout-0.1.0/tests/cases/scoping/class_in_function.golden +6 -0
  283. shakeout-0.1.0/tests/cases/scoping/comprehension_first_iter_is_outer.case +13 -0
  284. shakeout-0.1.0/tests/cases/scoping/comprehension_first_iter_is_outer.golden +6 -0
  285. shakeout-0.1.0/tests/cases/scoping/comprehension_target_vs_global.case +14 -0
  286. shakeout-0.1.0/tests/cases/scoping/comprehension_target_vs_global.golden +6 -0
  287. shakeout-0.1.0/tests/cases/scoping/del_local.case +15 -0
  288. shakeout-0.1.0/tests/cases/scoping/del_local.golden +6 -0
  289. shakeout-0.1.0/tests/cases/scoping/except_as_and_with_as.case +20 -0
  290. shakeout-0.1.0/tests/cases/scoping/except_as_and_with_as.golden +9 -0
  291. shakeout-0.1.0/tests/cases/scoping/global_in_nested_function.case +19 -0
  292. shakeout-0.1.0/tests/cases/scoping/global_in_nested_function.golden +6 -0
  293. shakeout-0.1.0/tests/cases/scoping/global_read_after_local_branch.case +13 -0
  294. shakeout-0.1.0/tests/cases/scoping/global_read_after_local_branch.golden +6 -0
  295. shakeout-0.1.0/tests/cases/scoping/local_shadows_global.case +15 -0
  296. shakeout-0.1.0/tests/cases/scoping/local_shadows_global.golden +6 -0
  297. shakeout-0.1.0/tests/cases/scoping/nonlocal.case +22 -0
  298. shakeout-0.1.0/tests/cases/scoping/nonlocal.golden +8 -0
  299. shakeout-0.1.0/tests/cases/scoping/param_shadows_global.case +13 -0
  300. shakeout-0.1.0/tests/cases/scoping/param_shadows_global.golden +6 -0
  301. shakeout-0.1.0/tests/cases/scoping/walrus_in_comprehension.case +14 -0
  302. shakeout-0.1.0/tests/cases/scoping/walrus_in_comprehension.golden +6 -0
  303. shakeout-0.1.0/tests/cases/syntax/async_code.case +22 -0
  304. shakeout-0.1.0/tests/cases/syntax/async_code.golden +9 -0
  305. shakeout-0.1.0/tests/cases/syntax/crlf_source.case +22 -0
  306. shakeout-0.1.0/tests/cases/syntax/crlf_source.golden +7 -0
  307. shakeout-0.1.0/tests/cases/syntax/decorated_with_lambda_default.case +13 -0
  308. shakeout-0.1.0/tests/cases/syntax/decorated_with_lambda_default.golden +8 -0
  309. shakeout-0.1.0/tests/cases/syntax/encoding_and_unicode.case +15 -0
  310. shakeout-0.1.0/tests/cases/syntax/encoding_and_unicode.golden +6 -0
  311. shakeout-0.1.0/tests/cases/syntax/except_star.case +13 -0
  312. shakeout-0.1.0/tests/cases/syntax/except_star.golden +5 -0
  313. shakeout-0.1.0/tests/cases/syntax/form_feed_line_numbers.case +15 -0
  314. shakeout-0.1.0/tests/cases/syntax/form_feed_line_numbers.golden +6 -0
  315. shakeout-0.1.0/tests/cases/syntax/fstring_nested.case +15 -0
  316. shakeout-0.1.0/tests/cases/syntax/fstring_nested.golden +7 -0
  317. shakeout-0.1.0/tests/cases/syntax/match_statement.case +21 -0
  318. shakeout-0.1.0/tests/cases/syntax/match_statement.golden +7 -0
  319. shakeout-0.1.0/tests/cases/syntax/pep695_generics.case +22 -0
  320. shakeout-0.1.0/tests/cases/syntax/pep695_generics.golden +9 -0
  321. shakeout-0.1.0/tests/cases/syntax/template_strings.case +14 -0
  322. shakeout-0.1.0/tests/cases/syntax/template_strings.golden +6 -0
  323. shakeout-0.1.0/tests/conftest.py +17 -0
  324. shakeout-0.1.0/tests/corpus/drivers/attrs_driver.py +21 -0
  325. shakeout-0.1.0/tests/corpus/drivers/click_driver.py +21 -0
  326. shakeout-0.1.0/tests/corpus/drivers/dateutil_driver.py +7 -0
  327. shakeout-0.1.0/tests/corpus/drivers/idna_driver.py +8 -0
  328. shakeout-0.1.0/tests/corpus/drivers/jinja_driver.py +8 -0
  329. shakeout-0.1.0/tests/corpus/drivers/packaging_driver.py +8 -0
  330. shakeout-0.1.0/tests/corpus/drivers/rich_driver.py +12 -0
  331. shakeout-0.1.0/tests/corpus/drivers/six_driver.py +4 -0
  332. shakeout-0.1.0/tests/corpus/drivers/tomli_driver.py +8 -0
  333. shakeout-0.1.0/tests/corpus/metrics.json +74 -0
  334. shakeout-0.1.0/tests/corpus/overrides.toml +6 -0
  335. shakeout-0.1.0/tests/corpus/requirements.txt +15 -0
  336. shakeout-0.1.0/tests/corpus/test_corpus.py +128 -0
  337. shakeout-0.1.0/tests/fuzz/__init__.py +0 -0
  338. shakeout-0.1.0/tests/fuzz/generator.py +207 -0
  339. shakeout-0.1.0/tests/fuzz/test_fuzz.py +77 -0
  340. shakeout-0.1.0/tests/harness/__init__.py +0 -0
  341. shakeout-0.1.0/tests/harness/cases.py +171 -0
  342. shakeout-0.1.0/tests/harness/project.py +22 -0
  343. shakeout-0.1.0/tests/harness/run.py +69 -0
  344. shakeout-0.1.0/tests/harness/soundness.py +82 -0
  345. shakeout-0.1.0/tests/harness/tracebacks.py +85 -0
  346. shakeout-0.1.0/tests/harness/tracer_main.py +107 -0
  347. shakeout-0.1.0/tests/test_cases.py +194 -0
  348. shakeout-0.1.0/tests/test_cli.py +103 -0
  349. shakeout-0.1.0/tests/test_docs.py +61 -0
  350. shakeout-0.1.0/tests/test_features.py +356 -0
  351. shakeout-0.1.0/tests/test_harness.py +197 -0
  352. shakeout-0.1.0/tests/test_self_hosting.py +86 -0
  353. shakeout-0.1.0/tests/unit/__init__.py +0 -0
  354. shakeout-0.1.0/tests/unit/helpers.py +25 -0
  355. shakeout-0.1.0/tests/unit/test_classes.py +89 -0
  356. shakeout-0.1.0/tests/unit/test_config_and_cli.py +192 -0
  357. shakeout-0.1.0/tests/unit/test_effects.py +180 -0
  358. shakeout-0.1.0/tests/unit/test_emit_properties.py +143 -0
  359. shakeout-0.1.0/tests/unit/test_minify.py +132 -0
  360. shakeout-0.1.0/tests/unit/test_runtime.py +183 -0
  361. shakeout-0.1.0/tests/unit/test_scopes.py +130 -0
  362. shakeout-0.1.0/tests/unit/test_scopes_symtable.py +136 -0
  363. shakeout-0.1.0/uv.lock +3244 -0
  364. shakeout-0.1.0/vulture_whitelist.py +39 -0
@@ -0,0 +1,120 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "17 4 * * *" # nightly tier
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ lint:
20
+ name: lint and types
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 15
23
+ steps:
24
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
+ with:
26
+ persist-credentials: false
27
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
28
+ - run: uv lock --check
29
+ - run: uv sync --locked --python 3.14
30
+ - run: uv run ruff format --check .
31
+ - run: uv run ruff check --output-format=github .
32
+ - run: uv run pyright
33
+ - run: uv run mypy
34
+ - run: uv run codespell src tests/*.py tests/harness tests/unit tests/fuzz/*.py docs README.md
35
+ - run: uv run vulture src vulture_whitelist.py --min-confidence 60
36
+ - run: uv run validate-pyproject pyproject.toml
37
+ - run: uv run actionlint
38
+ - run: uv run zizmor --persona=pedantic --quiet .github/workflows
39
+
40
+ test:
41
+ name: test (${{ matrix.os }}, ${{ matrix.python }})
42
+ runs-on: ${{ matrix.os }}
43
+ timeout-minutes: 30
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ os: [ubuntu-latest, macos-latest]
48
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
49
+ steps:
50
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
51
+ with:
52
+ persist-credentials: false
53
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
54
+ - run: uv sync --locked --python "$PYTHON"
55
+ env:
56
+ PYTHON: ${{ matrix.python }}
57
+ - name: tests (branch coverage gate on one interpreter)
58
+ env:
59
+ PYTHON: ${{ matrix.python }}
60
+ OS: ${{ matrix.os }}
61
+ run: |
62
+ if [ "$PYTHON" = "3.14" ] && [ "$OS" = "ubuntu-latest" ]; then
63
+ uv run pytest -n auto --cov --cov-report=term-missing:skip-covered
64
+ else
65
+ uv run pytest -n auto
66
+ fi
67
+
68
+ package:
69
+ name: package and docs
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 15
72
+ steps:
73
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
74
+ with:
75
+ persist-credentials: false
76
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
77
+ - run: uv sync --locked --python 3.14 --group docs
78
+ - run: uv run mkdocs build --strict --site-dir build/site
79
+ - run: uv build
80
+ - run: uv run twine check --strict dist/*
81
+ - run: uv run check-wheel-contents dist/*.whl
82
+ - run: uv run --isolated --no-project --with dist/*.whl shakeout --version
83
+
84
+ audit:
85
+ name: dependency audit
86
+ runs-on: ubuntu-latest
87
+ timeout-minutes: 10
88
+ steps:
89
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
90
+ with:
91
+ persist-credentials: false
92
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
93
+ - run: uv sync --locked --python 3.14
94
+ - run: uv export --frozen --all-groups --no-hashes --no-emit-project -o requirements.txt
95
+ - run: uv run pip-audit --strict --progress-spinner off -r requirements.txt
96
+
97
+ nightly:
98
+ name: nightly (corpus, long fuzz, mutation)
99
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
100
+ runs-on: ubuntu-latest
101
+ timeout-minutes: 120
102
+ steps:
103
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
104
+ with:
105
+ persist-credentials: false
106
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
107
+ - run: uv sync --locked --python 3.14
108
+ - name: corpus and 1500-program fuzz campaign
109
+ run: uv run pytest -n auto -m nightly
110
+ - name: mutation testing of the analysis core
111
+ run: scripts/mutation.sh
112
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
113
+ if: always()
114
+ with:
115
+ name: nightly-results
116
+ path: |
117
+ tests/corpus/metrics.json
118
+ tests/fuzz/failures/
119
+ mutants/
120
+ if-no-files-found: ignore
@@ -0,0 +1,47 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths: ["docs/**", "mkdocs.yml", ".github/workflows/docs.yml"]
7
+ workflow_dispatch:
8
+
9
+ permissions: {}
10
+
11
+ concurrency:
12
+ group: pages
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ build:
17
+ name: build site
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ permissions:
21
+ contents: read
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ persist-credentials: false
26
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
27
+ with:
28
+ enable-cache: false
29
+ - run: uv run --locked --group docs mkdocs build --strict --site-dir build/site
30
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
31
+ with:
32
+ path: build/site
33
+
34
+ deploy:
35
+ name: deploy to GitHub Pages
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 10
39
+ environment:
40
+ name: github-pages
41
+ url: ${{ steps.deployment.outputs.page_url }}
42
+ permissions:
43
+ pages: write # publish the site
44
+ id-token: write # prove the deployment's origin to Pages
45
+ steps:
46
+ - id: deployment
47
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,77 @@
1
+ name: release
2
+
3
+ # Push a tag like v0.1.0 to publish. PyPI trusted publishing (OIDC): no API tokens are stored.
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false # never interrupt a publish
13
+
14
+ jobs:
15
+ build:
16
+ name: build distributions
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 10
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
26
+ with:
27
+ enable-cache: false # never restore caches into a release build
28
+ - name: tag matches the package version
29
+ run: |
30
+ version=$(uv version --short)
31
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
32
+ echo "tag $GITHUB_REF_NAME does not match version $version" >&2
33
+ exit 1
34
+ fi
35
+ - run: uv build
36
+ - run: uvx twine check --strict dist/*
37
+ - run: uvx check-wheel-contents dist/*.whl
38
+ - run: uv run --isolated --no-project --with dist/*.whl shakeout --version
39
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+ if-no-files-found: error
44
+
45
+ pypi:
46
+ name: publish to PyPI
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ timeout-minutes: 10
50
+ environment:
51
+ name: pypi
52
+ url: https://pypi.org/project/shakeout/
53
+ permissions:
54
+ id-token: write # trusted publishing
55
+ steps:
56
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
57
+ with:
58
+ name: dist
59
+ path: dist/
60
+ - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
61
+
62
+ github-release:
63
+ name: GitHub release
64
+ needs: pypi
65
+ runs-on: ubuntu-latest
66
+ timeout-minutes: 10
67
+ permissions:
68
+ contents: write # create the GitHub release
69
+ steps:
70
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
71
+ with:
72
+ name: dist
73
+ path: dist/
74
+ - name: create release with the distributions attached
75
+ env:
76
+ GH_TOKEN: ${{ github.token }}
77
+ run: gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes --verify-tag
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .hypothesis/
6
+ .ruff_cache/
7
+ /build/
8
+ /dist/
9
+ tests/fuzz/failures/
10
+ .corpus/
11
+ .venv-*/
12
+ mutants/
@@ -0,0 +1,43 @@
1
+ # Uses the tool versions pinned in uv.lock. Install: uv run pre-commit install
2
+ repos:
3
+ - repo: local
4
+ hooks:
5
+ - id: ruff-format
6
+ name: ruff format
7
+ entry: uv run ruff format
8
+ language: system
9
+ types_or: [python, pyi]
10
+ - id: ruff
11
+ name: ruff check
12
+ entry: uv run ruff check --fix
13
+ language: system
14
+ types_or: [python, pyi]
15
+ - id: pyright
16
+ name: pyright
17
+ entry: uv run pyright
18
+ language: system
19
+ pass_filenames: false
20
+ types: [python]
21
+ - id: mypy
22
+ name: mypy
23
+ entry: uv run mypy
24
+ language: system
25
+ pass_filenames: false
26
+ types: [python]
27
+ - id: codespell
28
+ name: codespell
29
+ entry: uv run codespell
30
+ language: system
31
+ types_or: [python, markdown]
32
+ exclude: ^tests/cases/
33
+ - id: uv-lock
34
+ name: uv lock --check
35
+ entry: uv lock --check
36
+ language: system
37
+ pass_filenames: false
38
+ files: ^(pyproject\.toml|uv\.lock)$
39
+ - id: actionlint
40
+ name: actionlint
41
+ entry: uv run actionlint
42
+ language: system
43
+ files: ^\.github/workflows/
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-11-08)
4
+
5
+ First release.
6
+
7
+ - Static, sound tree shaking of first-party code (and opted-in third-party packages) across
8
+ module boundaries, with esbuild-style rewriting of kept imports.
9
+ - Targets: `single` (one runnable or importable `.py`), `pyz` (zipapp), `tree`, and `lambda`
10
+ (reproducible AWS Lambda zip with `--export` handlers).
11
+ - Exact tracebacks from bundles: original file names, line numbers and source lines; runtime notes
12
+ that name the `keep` setting when removed code is reached; `shakeout trace`.
13
+ - Dependencies: vendoring of unshaken distributions, `include` to shake them, `--uv` staging from
14
+ `uv.lock` for a target Python and platform.
15
+ - `--minify` with exact line numbers, `--strip-docstrings`.
16
+ - CLI: `build`, `report`, `why`, `trace`; configuration in `[tool.shakeout]`.
17
+ - CPython 3.10 to 3.15.
shakeout-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mischa Spiegelmock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.5
2
+ Name: shakeout
3
+ Version: 0.1.0
4
+ Summary: Static tree-shaking bundler for Python
5
+ Project-URL: Homepage, https://github.com/revmischa/shakeout
6
+ Project-URL: Documentation, https://revmischa.github.io/shakeout/
7
+ Project-URL: Repository, https://github.com/revmischa/shakeout
8
+ Project-URL: Issues, https://github.com/revmischa/shakeout/issues
9
+ Project-URL: Changelog, https://github.com/revmischa/shakeout/blob/main/CHANGELOG.md
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: aws-lambda,bundler,dead-code,tree-shaking,zipapp
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3.15
23
+ Classifier: Topic :: Software Development :: Build Tools
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: tomli>=2; python_version < '3.11'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # shakeout
30
+
31
+ A static, tree-shaking bundler for Python. Give it an entry script: it finds every module the
32
+ program can import, removes top-level code nothing can reach, and writes a self-contained
33
+ artifact. That can be one runnable `.py`, a zipapp, a pruned directory or a reproducible AWS
34
+ Lambda zip. Tracebacks from bundles name the original files and lines.
35
+
36
+ ```console
37
+ $ uvx shakeout build app/main.py -o dist # one runnable file
38
+ $ uvx shakeout build handler.py -t lambda --export handler # Lambda package
39
+ $ uvx shakeout report app/main.py # what is kept, and why
40
+ $ uvx shakeout why app/main.py pkg.core:helper # the chain that keeps a symbol
41
+ ```
42
+
43
+ **Documentation:** https://revmischa.github.io/shakeout/ (sources in [`docs/`](docs/); the
44
+ design and roadmap are in [docs/project/design.md](docs/project/design.md)).
45
+
46
+ ## Guarantees
47
+
48
+ - **Sound by default.** Anything the analysis can't prove unused is kept: registration
49
+ decorators, `__init_subclass__` and metaclass registries, modules that use `globals()`, `eval`
50
+ or `sys.modules`, module objects passed around as values, star imports.
51
+ - **Exact tracebacks without source maps.** Modules keep their original file names and line
52
+ numbers, and their sources are registered with `linecache`. Tracebacks, `warnings`,
53
+ `inspect.getsource` and error trackers show real files, lines, carets and code, even in
54
+ Lambda.
55
+ - **Self-explaining failures.** Reaching a removed name at runtime raises the usual exception
56
+ with a note that names the `keep` setting to add.
57
+
58
+ ## How it is tested
59
+
60
+ | Tier | What it checks |
61
+ |---|---|
62
+ | Case catalog (`tests/cases/**/*.case`) | 126 multi-file programs: expected removals, behavioral equivalence of every target (stdout, exit code, traceback frames), a no-shake build, reviewed golden reports |
63
+ | Soundness oracle | runs the original under `sys.monitoring`; fails if any function that executed was removed |
64
+ | Differential checks | scope analysis vs. CPython's `symtable`, and minified vs. original bytecode, over the whole stdlib |
65
+ | Fuzzing | Hypothesis-generated multi-module programs; equivalence + soundness |
66
+ | Mutation testing | 100% of mutants in the effects rules, class analysis and shaker are killed (floor 99%) |
67
+ | Real-world corpus (nightly) | attrs, click, dateutil, idna, jinja2, packaging, rich, six, tomli |
68
+ | Self-hosting | shakeout bundles itself byte-identically and contains no dead code |
69
+
70
+ Every gate CI runs is in `scripts/check.sh`: `ruff` with all rules, strict `pyright` and `mypy`,
71
+ 96% branch coverage, vulture, codespell, packaging checks, pip-audit, zizmor and actionlint, and
72
+ a strict docs build. CPython 3.10 to 3.15 are supported (`scripts/matrix.sh`).
@@ -0,0 +1,44 @@
1
+ # shakeout
2
+
3
+ A static, tree-shaking bundler for Python. Give it an entry script: it finds every module the
4
+ program can import, removes top-level code nothing can reach, and writes a self-contained
5
+ artifact. That can be one runnable `.py`, a zipapp, a pruned directory or a reproducible AWS
6
+ Lambda zip. Tracebacks from bundles name the original files and lines.
7
+
8
+ ```console
9
+ $ uvx shakeout build app/main.py -o dist # one runnable file
10
+ $ uvx shakeout build handler.py -t lambda --export handler # Lambda package
11
+ $ uvx shakeout report app/main.py # what is kept, and why
12
+ $ uvx shakeout why app/main.py pkg.core:helper # the chain that keeps a symbol
13
+ ```
14
+
15
+ **Documentation:** https://revmischa.github.io/shakeout/ (sources in [`docs/`](docs/); the
16
+ design and roadmap are in [docs/project/design.md](docs/project/design.md)).
17
+
18
+ ## Guarantees
19
+
20
+ - **Sound by default.** Anything the analysis can't prove unused is kept: registration
21
+ decorators, `__init_subclass__` and metaclass registries, modules that use `globals()`, `eval`
22
+ or `sys.modules`, module objects passed around as values, star imports.
23
+ - **Exact tracebacks without source maps.** Modules keep their original file names and line
24
+ numbers, and their sources are registered with `linecache`. Tracebacks, `warnings`,
25
+ `inspect.getsource` and error trackers show real files, lines, carets and code, even in
26
+ Lambda.
27
+ - **Self-explaining failures.** Reaching a removed name at runtime raises the usual exception
28
+ with a note that names the `keep` setting to add.
29
+
30
+ ## How it is tested
31
+
32
+ | Tier | What it checks |
33
+ |---|---|
34
+ | Case catalog (`tests/cases/**/*.case`) | 126 multi-file programs: expected removals, behavioral equivalence of every target (stdout, exit code, traceback frames), a no-shake build, reviewed golden reports |
35
+ | Soundness oracle | runs the original under `sys.monitoring`; fails if any function that executed was removed |
36
+ | Differential checks | scope analysis vs. CPython's `symtable`, and minified vs. original bytecode, over the whole stdlib |
37
+ | Fuzzing | Hypothesis-generated multi-module programs; equivalence + soundness |
38
+ | Mutation testing | 100% of mutants in the effects rules, class analysis and shaker are killed (floor 99%) |
39
+ | Real-world corpus (nightly) | attrs, click, dateutil, idna, jinja2, packaging, rich, six, tomli |
40
+ | Self-hosting | shakeout bundles itself byte-identically and contains no dead code |
41
+
42
+ Every gate CI runs is in `scripts/check.sh`: `ruff` with all rules, strict `pyright` and `mypy`,
43
+ 96% branch coverage, vulture, codespell, packaging checks, pip-audit, zizmor and actionlint, and
44
+ a strict docs build. CPython 3.10 to 3.15 are supported (`scripts/matrix.sh`).
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" aria-label="shakeout">
2
+ <path d="M32 6 L54 44 H10 Z" fill="currentColor" opacity="0.18"/>
3
+ <path d="M32 6 L54 44 H10 Z" stroke="currentColor" stroke-width="4" stroke-linejoin="round"/>
4
+ <path d="M32 44 V58" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
5
+ <circle cx="19" cy="54" r="3" fill="currentColor" opacity="0.55"/>
6
+ <circle cx="46" cy="56" r="2.5" fill="currentColor" opacity="0.4"/>
7
+ <circle cx="40" cy="50" r="2" fill="currentColor" opacity="0.3"/>
8
+ </svg>
@@ -0,0 +1,35 @@
1
+ # How shaking works
2
+
3
+ ```text
4
+ resolve ─► parse ─► analyze ─► link ─► shake ─► emit
5
+ modules AST scopes, cross- reach- chunks with
6
+ (no exec) effects module ability original lines
7
+ ```
8
+
9
+ 1. **Resolve.** Starting from the entry script, shakeout follows every `import`, including ones
10
+ inside functions, and locates modules the way Python would, without executing anything.
11
+ Modules under your source roots are bundled. The standard library and other packages are
12
+ external.
13
+ 2. **Parse and analyze.** Each module is split into top-level statements. For each one,
14
+ shakeout records the names it binds, the names it reads, using exact Python scoping rules,
15
+ the modules it may import, and whether running it has [side effects](side-effects.md).
16
+ 3. **Link.** References like `pkg.sub.func` are followed through imports and re-exports to the
17
+ statement that defines them.
18
+ 4. **Shake.** Starting from the entry point, every statement with side effects in an imported
19
+ module, and every export or keep, is marked live, then everything live code references.
20
+ A module that nothing live imports is dropped entirely.
21
+ 5. **Emit.** Kept statements are written with their original line numbers, and kept imports are
22
+ [rewritten](import-rewriting.md) to drop unused names. Removed names get a small
23
+ `__getattr__` that explains them if they are reached at runtime.
24
+
25
+ ## Per-module isolation
26
+
27
+ Unlike JavaScript bundlers, shakeout doesn't merge modules into one namespace ("scope
28
+ hoisting"). Every module keeps its own globals, `__name__`, import order and circular-import
29
+ behavior. That costs a little size and buys exact semantics and honest tracebacks.
30
+
31
+ ## What is shaken
32
+
33
+ The unit of removal is a top-level statement: a function, class, assignment or import.
34
+ Methods inside a kept class stay. Whether an instance's method is called can't be decided
35
+ statically.
@@ -0,0 +1,28 @@
1
+ # Import rewriting
2
+
3
+ A kept `from m import a, b` fails at runtime if `b` no longer exists. Keeping `b` just for
4
+ that would keep most of a library's public API alive. Instead, shakeout rewrites kept imports,
5
+ like esbuild does:
6
+
7
+ ```python
8
+ from lib import used, unused # before
9
+ from lib import used # shakeout: was 'from lib import used, unused' # after
10
+ ```
11
+
12
+ If none of the names are used but the import is kept for its side effects, it becomes a plain
13
+ import:
14
+
15
+ ```python
16
+ import lib as _shakeout_import_1 # shakeout: was 'from lib import a, b'
17
+ ```
18
+
19
+ The rewritten line occupies the same number of lines as the original, and it carries a comment
20
+ saying what it was, so a traceback through it explains itself.
21
+
22
+ ## Not rewritten
23
+
24
+ - Imports of modules outside the bundle: a missing name must still raise `ImportError`.
25
+ - Imports in modules kept whole (for example because they call `globals()`).
26
+ - Plain `import x`. It never requires attributes to exist, so only names of side-effect-free
27
+ bundled modules are dropped, and traceback text stays identical.
28
+ - A `from pkg import sub` where `sub` is a submodule with side effects.
@@ -0,0 +1,7 @@
1
+ # Concepts
2
+
3
+ - [How shaking works](how-it-works.md): the pipeline from entry point to artifact.
4
+ - [Side effects](side-effects.md): which statements may be removed, and why most can't.
5
+ - [Import rewriting](import-rewriting.md): how kept imports shed unused names.
6
+ - [Targets](targets.md): single file, zipapp, tree and Lambda.
7
+ - [Soundness and testing](soundness.md): what shakeout guarantees and how that is checked.
@@ -0,0 +1,47 @@
1
+ # Side effects
2
+
3
+ Importing a Python module runs it. A statement may only be removed if running it does nothing
4
+ observable, which means it only creates bindings in its own module, and if nothing reachable uses
5
+ those bindings.
6
+
7
+ ## Removable when unused
8
+
9
+ | Statement | Condition |
10
+ |---|---|
11
+ | `def` / `async def` | No decorators, or only known-pure ones (`dataclass`, `lru_cache`, `property`, `contextmanager`, ...), and pure default values |
12
+ | `class` | Pure decorators, no class keywords, a pure body, and bases that don't register subclasses |
13
+ | `x = ...` | Plain name targets and a pure value: literals, names, operators, lambdas, and calls to known-pure constructors like `TypeVar`, `namedtuple`, `re.compile`, `frozenset`, `field` |
14
+ | `import` | Every module it loads is side-effect free: the standard library, or bundled modules with no side effects |
15
+ | `if TYPE_CHECKING:` | Always |
16
+
17
+ ## Always kept
18
+
19
+ Calls at module level, loops, `try`, `with`, attribute or item stores (`config.X = 1`), augmented
20
+ assignment, `del`, `__future__` imports, module docstrings, dunders like `__all__` and
21
+ `__getattr__`, and anything marked `# shakeout: keep`.
22
+
23
+ ## Classes that register
24
+
25
+ Defining a subclass runs `__init_subclass__` and metaclass code, which is how plugin registries
26
+ work. A class is *registering* if it defines `__init_subclass__`, has a metaclass, or inherits from
27
+ a registering class. Subclasses of registering classes are always kept. Bases imported from
28
+ packages outside the bundle are assumed to register too, unless they are known to be safe
29
+ (`Exception`, `Enum`, `Protocol`, `NamedTuple`, `ABC`, the `collections.abc` types, ...).
30
+ `assume-pure-bases = true` trusts all unknown bases.
31
+
32
+ ## Effects travel through imports
33
+
34
+ A module has side effects if it runs a side-effecting statement or imports a module that has
35
+ them. An `import` of such a module is kept even when its names are unused, because removing it
36
+ would skip those effects.
37
+
38
+ ## Assumptions
39
+
40
+ These are documented so you can judge them:
41
+
42
+ - Operators, attribute reads and subscripts on module-level values don't call user code with
43
+ side effects.
44
+ - The known-pure callables don't call back into user code. Builtins that do, like `sorted(...,
45
+ key=f)` and `map`, are not on the list.
46
+ - Importing standard-library modules has no observable effects, except for a short denylist
47
+ such as `this`, `antigravity` and `turtle`.
@@ -0,0 +1,27 @@
1
+ # Soundness and testing
2
+
3
+ **Sound** means: if the original program would run a piece of code, the bundle still contains it.
4
+ A bundler that removes too much breaks programs silently, so shakeout's test suite exists
5
+ mainly to make that impossible to miss.
6
+
7
+ | Check | What it catches |
8
+ |---|---|
9
+ | Case catalog (126 multi-file programs) | Expected removals, plus byte-identical stdout, exit codes and traceback frames for every target, with and without shaking and minification |
10
+ | Soundness oracle | Runs the *original* program under `sys.monitoring` and fails if any function that executed was removed, even if output happened to match |
11
+ | `symtable` differential | Scope analysis against CPython's own symbol table on every standard-library file |
12
+ | Minify equivalence | Minified standard-library sources compile to identical instructions on identical lines |
13
+ | Fuzzing | Hypothesis generates multi-module programs with registries, plugins, cycles, local imports and `globals()`, and checks equivalence and soundness |
14
+ | Real-world corpus | attrs, click, dateutil, idna, jinja2, packaging, rich, six and tomli, shaken and compared |
15
+ | Mutation testing | Mutants of the effects rules, class analysis and reachability engine must be caught. The current kill rate is 100% |
16
+ | Self-hosting | shakeout bundles itself and the bundle produces byte-identical output. Shaking shakeout from its API removes nothing |
17
+
18
+ Every bug these found became a permanent regression case. The list is in the
19
+ [design document](../project/design.md).
20
+
21
+ ## Known limits
22
+
23
+ - Code reached only through strings (settings files, entry points, generated code) needs `keep`.
24
+ - Data files next to modules are only shipped in vendored distributions. Reading `__file__` in
25
+ your own modules produces a warning.
26
+ - Dropping a side-effect-free module can change the order in which other modules initialize.
27
+ This matters only for order-dependent circular imports.
@@ -0,0 +1,31 @@
1
+ # Targets
2
+
3
+ | Target | Output | Runs as | Contains third-party code |
4
+ |---|---|---|---|
5
+ | `single` | `<entry>.py` (+ `<entry>.manifest.json`) | `python main.py`, or `import` it with `--export` | Only `include`d pure-Python packages |
6
+ | `pyz` | `<entry>.pyz` | `python main.pyz` or `./main.pyz` | Yes, vendored |
7
+ | `tree` | a directory, one file per module | `python dist/main.py` | Yes, vendored |
8
+ | `lambda` | `<entry>.zip` and `package/` | AWS Lambda handler `<entry>.<export>` | Yes, vendored |
9
+
10
+ ## single
11
+
12
+ A small runtime (about 200 lines) installs an in-memory importer and runs the entry in the real
13
+ `__main__` namespace. The program sees exactly the globals it would see unbundled, and `pickle`
14
+ of classes defined in the entry works. Each module is stored as chunks tagged with their original
15
+ start line and padded back to the original line numbers when compiled.
16
+
17
+ ## tree
18
+
19
+ Every module is written to its import path with removed statements replaced by blank lines. No
20
+ runtime is involved: tracebacks name real files.
21
+
22
+ ## pyz
23
+
24
+ A `tree` packed into a reproducible zipapp. Its `__main__.py` registers archived sources with
25
+ `linecache` (CPython's zipimport only does this lazily for tracebacks and splits lines
26
+ incorrectly when files contain form feeds) and runs the entry with `runpy`.
27
+
28
+ ## lambda
29
+
30
+ A `tree` with vendored dependencies, zipped reproducibly (fixed timestamps, sorted entries). It
31
+ requires `exports`, and the manifest lists the handler names.