appmap 3.0.1__tar.gz → 4.0.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 (193) hide show
  1. {appmap-3.0.1 → appmap-4.0.0}/PKG-INFO +16 -8
  2. {appmap-3.0.1 → appmap-4.0.0}/README.md +1 -1
  3. {appmap-3.0.1 → appmap-4.0.0}/_appmap/__init__.py +1 -0
  4. {appmap-3.0.1 → appmap-4.0.0}/_appmap/configuration.py +3 -6
  5. {appmap-3.0.1 → appmap-4.0.0}/_appmap/event.py +20 -13
  6. {appmap-3.0.1 → appmap-4.0.0}/_appmap/importer.py +31 -21
  7. {appmap-3.0.1 → appmap-4.0.0}/_appmap/py_version_check.py +1 -1
  8. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/params.py +7 -0
  9. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_command.py +3 -3
  10. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_params.py +158 -4
  11. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_properties.py +1 -2
  12. {appmap-3.0.1 → appmap-4.0.0}/appmap/command/runner.py +6 -23
  13. {appmap-3.0.1 → appmap-4.0.0}/pyproject.toml +13 -4
  14. {appmap-3.0.1 → appmap-4.0.0}/.gitignore +0 -0
  15. {appmap-3.0.1 → appmap-4.0.0}/LICENSE +0 -0
  16. {appmap-3.0.1 → appmap-4.0.0}/_appmap/django.py +0 -0
  17. {appmap-3.0.1 → appmap-4.0.0}/_appmap/env.py +0 -0
  18. {appmap-3.0.1 → appmap-4.0.0}/_appmap/fastapi.py +0 -0
  19. {appmap-3.0.1 → appmap-4.0.0}/_appmap/flask.py +0 -0
  20. {appmap-3.0.1 → appmap-4.0.0}/_appmap/generation.py +0 -0
  21. {appmap-3.0.1 → appmap-4.0.0}/_appmap/instrument.py +0 -0
  22. {appmap-3.0.1 → appmap-4.0.0}/_appmap/labels.py +0 -0
  23. {appmap-3.0.1 → appmap-4.0.0}/_appmap/metadata.py +0 -0
  24. {appmap-3.0.1 → appmap-4.0.0}/_appmap/noappmap.py +0 -0
  25. {appmap-3.0.1 → appmap-4.0.0}/_appmap/recorder.py +0 -0
  26. {appmap-3.0.1 → appmap-4.0.0}/_appmap/recording.py +0 -0
  27. {appmap-3.0.1 → appmap-4.0.0}/_appmap/remote_recording.py +0 -0
  28. {appmap-3.0.1 → appmap-4.0.0}/_appmap/singleton.py +0 -0
  29. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/__init__.py +0 -0
  30. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/appmap_test_base.py +0 -0
  31. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/bin/server_runner +0 -0
  32. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/conftest.py +0 -0
  33. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
  34. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-broken.yml +0 -0
  35. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-class.yml +0 -0
  36. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-empty-path.yml +0 -0
  37. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
  38. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-func.yml +0 -0
  39. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-malformed-path.yml +0 -0
  40. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
  41. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap.yml +0 -0
  42. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap_testing/__init__.py +0 -0
  43. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
  44. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config/src/package/__init__.py +0 -0
  45. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config/test/__init__.py +0 -0
  46. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config/test/foo.py +0 -0
  47. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
  48. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
  49. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
  50. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
  51. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
  52. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-up/appmap.yml +0 -0
  53. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
  54. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
  55. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/appmap.yml +0 -0
  56. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
  57. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
  58. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
  59. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
  60. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/settings.py +0 -0
  61. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
  62. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/urls.py +0 -0
  63. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/init/sitecustomize.py +0 -0
  64. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/manage.py +0 -0
  65. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/pytest.ini +0 -0
  66. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/test/__init__.py +0 -0
  67. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/test/test_app.py +0 -0
  68. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/test/test_request.py +0 -0
  69. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
  70. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/example_class.py +0 -0
  71. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/expected.appmap.json +0 -0
  72. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/fastapi/appmap.yml +0 -0
  73. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
  74. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
  75. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
  76. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/fastapi/test_app.py +0 -0
  77. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask/appmap.yml +0 -0
  78. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask/flaskapp.py +0 -0
  79. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
  80. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask/templates/test.html +0 -0
  81. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask/test_app.py +0 -0
  82. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/appmap.yml +0 -0
  83. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/flaskapp.py +0 -0
  84. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/init/sitecustomize.py +0 -0
  85. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/test_app.py +0 -0
  86. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/package1/__init__.py +0 -0
  87. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/package1/package2/__init__.py +0 -0
  88. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/package1/package2/__main__.py +0 -0
  89. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/package1/package2/mod1.py +0 -0
  90. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/properties_class.py +0 -0
  91. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/appmap-no-test-cases.yml +0 -0
  92. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/appmap.yml +0 -0
  93. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy1-no-test-cases.appmap.json +0 -0
  94. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
  95. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy2-no-test-cases.appmap.json +0 -0
  96. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
  97. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
  98. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
  99. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
  100. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_xsucceeded.metadata.json +0 -0
  101. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/simple.py +0 -0
  102. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/tests/__init__.py +0 -0
  103. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/tests/test_noappmap.py +0 -0
  104. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest/tests/test_simple.py +0 -0
  105. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/appmap.yml +0 -0
  106. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/init/sitecustomize.py +0 -0
  107. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/test_instrumented.py +0 -0
  108. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/remote.appmap.json +0 -0
  109. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/.gitignore +0 -0
  110. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/appmap-no-test-cases.yml +0 -0
  111. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/appmap.yml +0 -0
  112. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/expected/pytest-no-test-cases.appmap.json +0 -0
  113. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
  114. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
  115. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/test/__init__.py +0 -0
  116. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/trial/test/test_deferred.py +0 -0
  117. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/appmap-no-test-cases.yml +0 -0
  118. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/appmap.yml +0 -0
  119. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
  120. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
  121. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
  122. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
  123. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
  124. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/unittest-no-test-cases.appmap.json +0 -0
  125. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
  126. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
  127. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/simple/__init__.py +0 -0
  128. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
  129. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
  130. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/helpers.py +0 -0
  131. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/normalize.py +0 -0
  132. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_configuration.py +0 -0
  133. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_describe_value.py +0 -0
  134. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_django.py +0 -0
  135. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_django_simplelazyobject.py +0 -0
  136. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_env.py +0 -0
  137. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_events.py +0 -0
  138. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_fastapi.py +0 -0
  139. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_flask.py +0 -0
  140. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_generation.py +0 -0
  141. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_http.py +0 -0
  142. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_importer.py +0 -0
  143. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_labels.py +0 -0
  144. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_metadata.py +0 -0
  145. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_recording.py +0 -0
  146. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_runner.py +0 -0
  147. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_sqlalchemy.py +0 -0
  148. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_test_frameworks.py +0 -0
  149. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/test_util.py +0 -0
  150. {appmap-3.0.1 → appmap-4.0.0}/_appmap/test/web_framework.py +0 -0
  151. {appmap-3.0.1 → appmap-4.0.0}/_appmap/testing_framework.py +0 -0
  152. {appmap-3.0.1 → appmap-4.0.0}/_appmap/trace_logger.py +0 -0
  153. {appmap-3.0.1 → appmap-4.0.0}/_appmap/unittest.py +0 -0
  154. {appmap-3.0.1 → appmap-4.0.0}/_appmap/utils.py +0 -0
  155. {appmap-3.0.1 → appmap-4.0.0}/_appmap/web_framework.py +0 -0
  156. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/LICENSE +0 -0
  157. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/__init__.py +0 -0
  158. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/arguments.py +0 -0
  159. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/decorators.py +0 -0
  160. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/importer.py +0 -0
  161. {appmap-3.0.1 → appmap-4.0.0}/_appmap/wrapt/wrappers.py +0 -0
  162. {appmap-3.0.1 → appmap-4.0.0}/appmap/__init__.py +0 -0
  163. {appmap-3.0.1 → appmap-4.0.0}/appmap/command/__init__.py +0 -0
  164. {appmap-3.0.1 → appmap-4.0.0}/appmap/command/appmap_agent_init.py +0 -0
  165. {appmap-3.0.1 → appmap-4.0.0}/appmap/command/appmap_agent_status.py +0 -0
  166. {appmap-3.0.1 → appmap-4.0.0}/appmap/command/appmap_agent_validate.py +0 -0
  167. {appmap-3.0.1 → appmap-4.0.0}/appmap/django.py +0 -0
  168. {appmap-3.0.1 → appmap-4.0.0}/appmap/fastapi.py +0 -0
  169. {appmap-3.0.1 → appmap-4.0.0}/appmap/flask.py +0 -0
  170. {appmap-3.0.1 → appmap-4.0.0}/appmap/http.py +0 -0
  171. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/__init__.py +0 -0
  172. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/crypto.yml +0 -0
  173. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/django.yml +0 -0
  174. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/flask.yml +0 -0
  175. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/formats.yml +0 -0
  176. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/http.yml +0 -0
  177. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/jobs.yml +0 -0
  178. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/jwt.yml +0 -0
  179. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/logger.yml +0 -0
  180. {appmap-3.0.1 → appmap-4.0.0}/appmap/labeling/random.yml +0 -0
  181. {appmap-3.0.1 → appmap-4.0.0}/appmap/pytest.py +0 -0
  182. {appmap-3.0.1 → appmap-4.0.0}/appmap/sqlalchemy.py +0 -0
  183. {appmap-3.0.1 → appmap-4.0.0}/appmap/unittest.py +0 -0
  184. {appmap-3.0.1 → appmap-4.0.0}/appmap/uvicorn.py +0 -0
  185. {appmap-3.0.1 → appmap-4.0.0}/appmap.pth +0 -0
  186. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/LICENSE +0 -0
  187. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/__init__.py +0 -0
  188. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/arguments.py +0 -0
  189. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/decorators.py +0 -0
  190. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/importer.py +0 -0
  191. {appmap-3.0.1 → appmap-4.0.0}/vendor/_appmap/wrapt/wrappers.py +0 -0
  192. {appmap-3.0.1 → appmap-4.0.0}/vendor/patches/wrapt.patch +0 -0
  193. {appmap-3.0.1 → appmap-4.0.0}/vendor/vendor.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: appmap
3
- Version: 3.0.1
3
+ Version: 4.0.0
4
4
  Summary: Create AppMap files by recording a Python application.
5
5
  Project-URL: Homepage, https://github.com/applandinc/appmap-python
6
6
  Author-email: Alan Potter <alan@app.land>, Viraj Kanwade <viraj.kanwade@forgeahead.io>, Rafał Rzepecki <rafal@app.land>
@@ -9,14 +9,24 @@ License-File: LICENSE
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Framework :: Django
11
11
  Classifier: Framework :: Django :: 3.2
12
+ Classifier: Framework :: Django :: 4.2
13
+ Classifier: Framework :: Django :: 5.2
14
+ Classifier: Framework :: Django :: 6.0
12
15
  Classifier: Framework :: Flask
13
16
  Classifier: Framework :: Pytest
14
17
  Classifier: Intended Audience :: Developers
15
18
  Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Classifier: Programming Language :: Python :: 3.15
16
26
  Classifier: Topic :: Software Development
17
27
  Classifier: Topic :: Software Development :: Debuggers
18
28
  Classifier: Topic :: Software Development :: Documentation
19
- Requires-Python: >=3.8
29
+ Requires-Python: >=3.11
20
30
  Requires-Dist: importlib-resources>=5.4.0
21
31
  Requires-Dist: inflection>=0.3.0
22
32
  Requires-Dist: packaging>=19.0
@@ -33,8 +43,7 @@ Requires-Dist: httpretty<2.0,>=1.0.5; extra == 'dev'
33
43
  Requires-Dist: httpx<1.0,>=0.27.0; extra == 'dev'
34
44
  Requires-Dist: incremental<24.7.0; extra == 'dev'
35
45
  Requires-Dist: isort<6.0,>=5.10.1; extra == 'dev'
36
- Requires-Dist: numpy<2.0,>=1.24.4; (python_version < '3.9') and extra == 'dev'
37
- Requires-Dist: numpy>=2.0; (python_version >= '3.9') and extra == 'dev'
46
+ Requires-Dist: numpy>=2.0; extra == 'dev'
38
47
  Requires-Dist: pprintpp<1.0,>=0.4.0; extra == 'dev'
39
48
  Requires-Dist: psutil<7.0,>=6.0.0; extra == 'dev'
40
49
  Requires-Dist: pyfakefs<6.0,>=5.3.5; extra == 'dev'
@@ -66,8 +75,7 @@ Requires-Dist: flask; extra == 'test'
66
75
  Requires-Dist: httpretty<2.0,>=1.0.5; extra == 'test'
67
76
  Requires-Dist: httpx<1.0,>=0.27.0; extra == 'test'
68
77
  Requires-Dist: incremental<24.7.0; extra == 'test'
69
- Requires-Dist: numpy<2.0,>=1.24.4; (python_version < '3.9') and extra == 'test'
70
- Requires-Dist: numpy>=2.0; (python_version >= '3.9') and extra == 'test'
78
+ Requires-Dist: numpy>=2.0; extra == 'test'
71
79
  Requires-Dist: psutil<7.0,>=6.0.0; extra == 'test'
72
80
  Requires-Dist: pyfakefs<6.0,>=5.3.5; extra == 'test'
73
81
  Requires-Dist: pytest-console-scripts<2.0,>=1.4.1; extra == 'test'
@@ -185,7 +193,7 @@ cd appmap-python
185
193
  APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest
186
194
 
187
195
  # Run tests with a specific Python version
188
- APPMAP_DISPLAY_PARAMS=true uv run --python 3.9 appmap-python pytest
196
+ APPMAP_DISPLAY_PARAMS=true uv run --python 3.11 appmap-python pytest
189
197
 
190
198
  # Run tests in parallel
191
199
  APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -n auto
@@ -96,7 +96,7 @@ cd appmap-python
96
96
  APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest
97
97
 
98
98
  # Run tests with a specific Python version
99
- APPMAP_DISPLAY_PARAMS=true uv run --python 3.9 appmap-python pytest
99
+ APPMAP_DISPLAY_PARAMS=true uv run --python 3.11 appmap-python pytest
100
100
 
101
101
  # Run tests in parallel
102
102
  APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -n auto
@@ -12,6 +12,7 @@ def initialize(**kwargs):
12
12
  importer.initialize()
13
13
  recorder.initialize()
14
14
  configuration.initialize() # needs to be initialized after recorder
15
+ importer.install() # needs the filters registered by configuration
15
16
  metadata.initialize()
16
17
  web_framework.initialize()
17
18
  recording.initialize()
@@ -185,13 +185,10 @@ class Config(metaclass=SingletonMeta):
185
185
  env = Env.current
186
186
  output_dir = env.output_dir
187
187
  root_dir = env.root_dir
188
- try:
188
+ # If output_dir isn't relative to root_dir, APPMAP_OUTPUT_DIR is set, and we've already
189
+ # logged a warning.
190
+ if output_dir.is_relative_to(root_dir):
189
191
  ret["appmap_dir"] = str(output_dir.relative_to(root_dir))
190
- except ValueError:
191
- # The only way we can get here is if APPMAP_OUTPUT_DIR is set, and we've already logged
192
- # a warning. (Note that PurePath.is_relative_to wasn't added till 3.9, so it can't be
193
- # used here.)
194
- pass
195
192
 
196
193
  return ret
197
194
 
@@ -156,10 +156,11 @@ class Event:
156
156
 
157
157
 
158
158
  class Param:
159
- __slots__ = ["name", "kind", "default", "default_class"]
159
+ __slots__ = ["name", "kind", "positional_only", "default", "default_class"]
160
160
 
161
161
  def __init__(self, sigp):
162
162
  self.name = sigp.name
163
+ self.positional_only = sigp.kind == Parameter.POSITIONAL_ONLY
163
164
  has_default = sigp.default is not Signature.empty
164
165
  if sigp.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD):
165
166
  self.kind = "opt" if has_default else "req"
@@ -275,28 +276,34 @@ class CallEvent(Event):
275
276
  # we're popping the front repeatedly, lists are better at this
276
277
  args = list(args)
277
278
 
279
+ # Names of keyword arguments bound to named parameters. Python
280
+ # doesn't include them in **kwargs, so neither do we.
281
+ bound = set()
278
282
  for p in params:
279
- if p.kind == "req":
280
- # A 'req' argument can be either keyword or
281
- # positional.
282
- if p.name in kwargs:
283
+ if p.kind in ("req", "opt"):
284
+ # These can be either keyword or positional, unless
285
+ # they're positional-only.
286
+ if not p.positional_only and p.name in kwargs:
283
287
  value = kwargs[p.name]
288
+ bound.add(p.name)
289
+ elif args:
290
+ value = args.pop(0)
291
+ elif p.kind == "opt":
292
+ value = p.default
284
293
  else:
285
- if args:
286
- value = args.pop(0)
287
- else:
288
- continue # required argument missing
289
- elif p.kind == "keyreq":
294
+ continue # required argument missing
295
+ elif p.kind in ("keyreq", "key"):
290
296
  if p.name in kwargs:
291
297
  value = kwargs[p.name]
298
+ bound.add(p.name)
299
+ elif p.kind == "key":
300
+ value = p.default
292
301
  else:
293
302
  continue # required argument missing
294
- elif p.kind in ("opt", "key"):
295
- value = kwargs.get(p.name, p.default)
296
303
  elif p.kind == "rest":
297
304
  value = tuple(args)
298
305
  elif p.kind == "keyrest":
299
- value = kwargs
306
+ value = {k: v for k, v in kwargs.items() if k not in bound} if bound else kwargs
300
307
  else:
301
308
  # If all the parameter types are handled, this
302
309
  # shouldn't ever happen...
@@ -166,7 +166,7 @@ class Importer:
166
166
  @classmethod
167
167
  def initialize(cls):
168
168
  cls.filter_stack = []
169
- cls.filter_chain = []
169
+ cls.filter_chain = None
170
170
  cls._skip_instrumenting = ("appmap", "_appmap")
171
171
  cls.instrument_properties = (
172
172
  Env.current.get("APPMAP_INSTRUMENT_PROPERTIES", "true").lower() == "true"
@@ -176,6 +176,10 @@ class Importer:
176
176
  def use_filter(cls, filter_class):
177
177
  cls.filter_stack.append(filter_class)
178
178
 
179
+ @classmethod
180
+ def build_filter_chain(cls):
181
+ cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
182
+
179
183
  @classmethod
180
184
  def instrument_function(cls, fn_name, filterableFn: FilterableFn, selected_functions=None):
181
185
  # Only instrument the function if it was specifically called out for the package
@@ -194,8 +198,10 @@ class Importer:
194
198
  return
195
199
 
196
200
  logger.trace("do_import, mod %s args %s kwargs %s", mod, args, kwargs)
197
- if not cls.filter_chain:
198
- cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
201
+ if cls.filter_chain is None:
202
+ # Only possible while _appmap is being (re)initialized, before install() has run.
203
+ logger.trace("no filter chain yet, not instrumenting %s", mod.__name__)
204
+ return
199
205
 
200
206
  def instrument_functions(filterable, selected_functions=None):
201
207
  # pylint: disable=too-many-locals
@@ -314,26 +320,18 @@ def wrapped_find_spec(find_spec, _, args, kwargs):
314
320
 
315
321
 
316
322
  def wrap_finder_find_spec(finder):
317
- # Prior to 3.11, it worked fine to just grab find_spec from the finder and wrap it. The
318
- # implementation of builtin finders must have changed with 3.11, because we now need the same
319
- # kind of workaround we use above for exec_module.
320
- if sys.version_info[1] < 11:
321
- find_spec = getattr(finder, "find_spec", None)
322
- if find_spec is None:
323
- logger.trace("no find_spec for finder %r", finder)
324
- return
325
-
323
+ # Builtin finders define find_spec as a classmethod or staticmethod, which wrapt has trouble
324
+ # identifying (see the comment in wrapped_find_spec above), so wrap the descriptor found in
325
+ # __dict__ instead.
326
+ find_spec = inspect.getattr_static(finder, "find_spec", None)
327
+ if find_spec is None:
328
+ logger.trace("no find_spec for finder %r", finder)
329
+ return
330
+
331
+ if isinstance(find_spec, (classmethod, staticmethod)):
326
332
  finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
327
333
  else:
328
- find_spec = inspect.getattr_static(finder, "find_spec", None)
329
- if find_spec is None:
330
- logger.trace("no find_spec for finder %r", finder)
331
- return
332
-
333
- if isinstance(find_spec, (classmethod, staticmethod)):
334
- finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
335
- else:
336
- finder.find_spec = wrap_finder_function(finder.find_spec, wrapped_find_spec)
334
+ finder.find_spec = wrap_finder_function(finder.find_spec, wrapped_find_spec)
337
335
 
338
336
 
339
337
  class MetapathObserver(MutableSequence):
@@ -362,6 +360,18 @@ class MetapathObserver(MutableSequence):
362
360
 
363
361
  def initialize():
364
362
  Importer.initialize()
363
+
364
+
365
+ def install():
366
+ """
367
+ Build the filter chain and hook the finders. Must run after the filters have been registered
368
+ (i.e. after configuration.initialize()).
369
+
370
+ The chain is built before the finders are hooked because building it can import modules (e.g.
371
+ since 3.13, importlib.metadata.files imports csv lazily). If the hooks were already in place,
372
+ those imports would need the chain that's still being built.
373
+ """
374
+ Importer.build_filter_chain()
365
375
  # If we're not enabled, there's no reason to hook the finders.
366
376
  if Env.current.enabled:
367
377
  logger.trace("sys.metapath: %s", sys.meta_path)
@@ -17,7 +17,7 @@ def _get_platform_version():
17
17
 
18
18
 
19
19
  def check_py_version():
20
- req = (3, 8)
20
+ req = (3, 11)
21
21
  actual = _get_platform_version()
22
22
  if _get_py_version() < req:
23
23
  raise AppMapPyVerException(
@@ -22,6 +22,9 @@ class C:
22
22
  def with_defaults(self, p1=1, p2=2):
23
23
  return "%s %s" % (p1, p2)
24
24
 
25
+ def mixed(self, a, b=None, c="dflt", *args, d, e="e", **kwargs):
26
+ return "%s %s %s %s %s %s %s" % (a, b, c, args, d, e, kwargs)
27
+
25
28
 
26
29
  if sys.version_info >= (3, 8):
27
30
  exec(
@@ -33,5 +36,9 @@ C.positional_only = positional_only
33
36
  def keyword_only(self, *, p1, p2):
34
37
  return '%s %s' % (p1, p2)
35
38
  C.keyword_only = keyword_only
39
+
40
+ def positional_only_kwargs(self, a, b="dflt", /, **kwargs):
41
+ return '%s %s %s' % (a, b, kwargs)
42
+ C.positional_only_kwargs = positional_only_kwargs
36
43
  """
37
44
  )
@@ -115,14 +115,14 @@ class TestAgentValidate:
115
115
  def test_python_version(self, capsys, mocker):
116
116
  mocker.patch(
117
117
  "_appmap.py_version_check._get_py_version",
118
- return_value=(3, 5),
118
+ return_value=(3, 10),
119
119
  )
120
120
  mocker.patch(
121
121
  "_appmap.py_version_check._get_platform_version",
122
- return_value="3.5",
122
+ return_value="3.10",
123
123
  )
124
124
 
125
- self.check_errors(capsys, 1, 1, r"Minimum Python version supported is \d\.\d, found")
125
+ self.check_errors(capsys, 1, 1, r"Minimum Python version supported is \d+\.\d+, found")
126
126
 
127
127
  def test_django_version(self, capsys, mocker):
128
128
  mocker.patch(
@@ -266,9 +266,6 @@ class TestInstanceMethods(TestMethodBase):
266
266
  },
267
267
  )
268
268
 
269
- @pytest.mark.skipif(
270
- sys.version_info < (3, 8), reason="positional-only added in 3.8"
271
- )
272
269
  @pytest.mark.parametrize("params", ["positional_only"], indirect=True)
273
270
  def test_positional_only(self, params):
274
271
  evt = params.C().positional_only(1, 2)
@@ -282,7 +279,6 @@ class TestInstanceMethods(TestMethodBase):
282
279
  evt, 1, {"class": "builtins.int", "kind": "req", "name": "p2", "value": "2"}
283
280
  )
284
281
 
285
- @pytest.mark.skipif(sys.version_info < (3, 8), reason="keyword-only added in 3.8")
286
282
  @pytest.mark.parametrize("params", ["keyword_only"], indirect=True)
287
283
  def test_keyword_only(self, params):
288
284
  evt = params.C().keyword_only(p2=2, p1=1)
@@ -325,3 +321,161 @@ class TestInstanceMethods(TestMethodBase):
325
321
  self.assert_parameter(
326
322
  evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
327
323
  )
324
+
325
+ @pytest.mark.parametrize("params", ["with_defaults"], indirect=True)
326
+ def test_defaults_positional(self, params):
327
+ evt = params.C().with_defaults(3, 4)
328
+ assert len(evt.parameters) == 2
329
+
330
+ self.assert_parameter(
331
+ evt, 0, {"class": "builtins.int", "kind": "opt", "name": "p1", "value": "3"}
332
+ )
333
+
334
+ self.assert_parameter(
335
+ evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
336
+ )
337
+
338
+ @pytest.mark.parametrize("params", ["with_defaults"], indirect=True)
339
+ def test_defaults_positional_and_keyword(self, params):
340
+ evt = params.C().with_defaults(3, p2=4)
341
+ assert len(evt.parameters) == 2
342
+
343
+ self.assert_parameter(
344
+ evt, 0, {"class": "builtins.int", "kind": "opt", "name": "p1", "value": "3"}
345
+ )
346
+
347
+ self.assert_parameter(
348
+ evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
349
+ )
350
+
351
+
352
+ class TestMixedParams(TestMethodBase):
353
+ """
354
+ C.mixed has the signature (self, a, b=None, c="dflt", *args, d, e="e", **kwargs),
355
+ covering every parameter kind.
356
+ """
357
+
358
+ def assert_values(self, evt, expected):
359
+ actual = {p["name"]: (p["kind"], p["value"]) for p in evt.parameters}
360
+ assert actual == expected
361
+
362
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
363
+ def test_positional_opt(self, params):
364
+ evt = params.C().mixed("A", "B", "C", d="D")
365
+ self.assert_values(
366
+ evt,
367
+ {
368
+ "a": ("req", "A"),
369
+ "b": ("opt", "B"),
370
+ "c": ("opt", "C"),
371
+ "args": ("rest", "()"),
372
+ "d": ("keyreq", "D"),
373
+ "e": ("key", "e"),
374
+ "kwargs": ("keyrest", "{}"),
375
+ },
376
+ )
377
+
378
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
379
+ def test_partial_positional_opt(self, params):
380
+ evt = params.C().mixed("A", "B", d="D")
381
+ self.assert_values(
382
+ evt,
383
+ {
384
+ "a": ("req", "A"),
385
+ "b": ("opt", "B"),
386
+ "c": ("opt", "dflt"),
387
+ "args": ("rest", "()"),
388
+ "d": ("keyreq", "D"),
389
+ "e": ("key", "e"),
390
+ "kwargs": ("keyrest", "{}"),
391
+ },
392
+ )
393
+
394
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
395
+ def test_keyword_opt(self, params):
396
+ evt = params.C().mixed("A", c="C", d="D")
397
+ self.assert_values(
398
+ evt,
399
+ {
400
+ "a": ("req", "A"),
401
+ "b": ("opt", "None"),
402
+ "c": ("opt", "C"),
403
+ "args": ("rest", "()"),
404
+ "d": ("keyreq", "D"),
405
+ "e": ("key", "e"),
406
+ "kwargs": ("keyrest", "{}"),
407
+ },
408
+ )
409
+
410
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
411
+ def test_rest_after_opt(self, params):
412
+ evt = params.C().mixed("A", "B", "C", "X", "Y", d="D")
413
+ self.assert_values(
414
+ evt,
415
+ {
416
+ "a": ("req", "A"),
417
+ "b": ("opt", "B"),
418
+ "c": ("opt", "C"),
419
+ "args": ("rest", "('X', 'Y')"),
420
+ "d": ("keyreq", "D"),
421
+ "e": ("key", "e"),
422
+ "kwargs": ("keyrest", "{}"),
423
+ },
424
+ )
425
+
426
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
427
+ def test_keyword_only(self, params):
428
+ evt = params.C().mixed("A", d="D", e="E", f="F")
429
+ self.assert_values(
430
+ evt,
431
+ {
432
+ "a": ("req", "A"),
433
+ "b": ("opt", "None"),
434
+ "c": ("opt", "dflt"),
435
+ "args": ("rest", "()"),
436
+ "d": ("keyreq", "D"),
437
+ "e": ("key", "E"),
438
+ "kwargs": ("keyrest", "{'f': 'F'}"),
439
+ },
440
+ )
441
+
442
+ @pytest.mark.parametrize("params", ["mixed"], indirect=True)
443
+ def test_keyword_req(self, params):
444
+ evt = params.C().mixed(a="A", d="D", f="F")
445
+ self.assert_values(
446
+ evt,
447
+ {
448
+ "a": ("req", "A"),
449
+ "b": ("opt", "None"),
450
+ "c": ("opt", "dflt"),
451
+ "args": ("rest", "()"),
452
+ "d": ("keyreq", "D"),
453
+ "e": ("key", "e"),
454
+ "kwargs": ("keyrest", "{'f': 'F'}"),
455
+ },
456
+ )
457
+
458
+ @pytest.mark.parametrize("params", ["positional_only_kwargs"], indirect=True)
459
+ def test_positional_only_name_in_kwargs(self, params):
460
+ # Keyword arguments named like positional-only parameters go to **kwargs
461
+ evt = params.C().positional_only_kwargs("A", "B", a="X", b="Y")
462
+ self.assert_values(
463
+ evt,
464
+ {
465
+ "a": ("req", "A"),
466
+ "b": ("opt", "B"),
467
+ "kwargs": ("keyrest", "{'a': 'X', 'b': 'Y'}"),
468
+ },
469
+ )
470
+
471
+ @pytest.mark.parametrize("params", ["positional_only_kwargs"], indirect=True)
472
+ def test_positional_only_default_name_in_kwargs(self, params):
473
+ evt = params.C().positional_only_kwargs("A", b="Y")
474
+ self.assert_values(
475
+ evt,
476
+ {
477
+ "a": ("req", "A"),
478
+ "b": ("opt", "dflt"),
479
+ "kwargs": ("keyrest", "{'b': 'Y'}"),
480
+ },
481
+ )
@@ -118,8 +118,7 @@ def test_operator_itemgetter(events):
118
118
  assert len(events) == 2
119
119
  assert events[0].to_dict() == DictIncluding({
120
120
  "event": "call",
121
- # operator.itemgetter.__module__ isn't available before 3.10
122
- # "defined_class": "operator",
121
+ "defined_class": "operator",
123
122
  "method_id": "itemgetter (get)",
124
123
  })
125
124
 
@@ -53,29 +53,12 @@ _parser.add_argument(
53
53
  default=argparse.SUPPRESS,
54
54
  )
55
55
 
56
- if sys.version_info >= (3, 9):
57
- _parser.add_argument(
58
- "--enable-log",
59
- help="create a log file",
60
- action=argparse.BooleanOptionalAction,
61
- default=False,
62
- )
63
- else:
64
- # You can see why BooleanOptionalAction was added. This is close, though not
65
- # really as good....
66
- _enable_log_group = _parser.add_mutually_exclusive_group()
67
- _enable_log_group.add_argument(
68
- "--enable-log",
69
- help="create a log file",
70
- dest="enable_log",
71
- action="store_true",
72
- )
73
- _enable_log_group.add_argument(
74
- "--no-enable-log",
75
- help="don't create a log file",
76
- dest="enable_log",
77
- action="store_false",
78
- )
56
+ _parser.add_argument(
57
+ "--enable-log",
58
+ help="create a log file",
59
+ action=argparse.BooleanOptionalAction,
60
+ default=False,
61
+ )
79
62
 
80
63
  _parser.add_argument(
81
64
  "command",
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "appmap"
3
- version = "3.0.1"
3
+ version = "4.0.0"
4
4
  description = "Create AppMap files by recording a Python application."
5
5
  readme = "README.md"
6
- requires-python = ">=3.8"
6
+ requires-python = ">=3.11"
7
7
  license = { text = "MIT" }
8
8
  authors = [
9
9
  { name = "Alan Potter", email = "alan@app.land" },
@@ -14,10 +14,20 @@ classifiers = [
14
14
  "Development Status :: 4 - Beta",
15
15
  "Framework :: Django",
16
16
  "Framework :: Django :: 3.2",
17
+ "Framework :: Django :: 4.2",
18
+ "Framework :: Django :: 5.2",
19
+ "Framework :: Django :: 6.0",
17
20
  "Framework :: Flask",
18
21
  "Framework :: Pytest",
19
22
  "Intended Audience :: Developers",
20
23
  "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "Programming Language :: Python :: 3.15",
21
31
  "Topic :: Software Development",
22
32
  "Topic :: Software Development :: Debuggers",
23
33
  "Topic :: Software Development :: Documentation",
@@ -60,8 +70,7 @@ test = [
60
70
  "Flask",
61
71
  "sqlalchemy",
62
72
  "pytest-django>=4.7,<5.0",
63
- "numpy>=1.24.4,<2.0; python_version < '3.9'",
64
- "numpy>=2.0; python_version >= '3.9'",
73
+ "numpy>=2.0",
65
74
  ]
66
75
  dev = [
67
76
  "appmap[test]",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes