appmap 2.1.5__tar.gz → 2.1.7__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 (183) hide show
  1. {appmap-2.1.5 → appmap-2.1.7}/PKG-INFO +4 -5
  2. {appmap-2.1.5 → appmap-2.1.7}/README.md +3 -4
  3. {appmap-2.1.5 → appmap-2.1.7}/_appmap/env.py +14 -13
  4. {appmap-2.1.5 → appmap-2.1.7}/_appmap/event.py +0 -1
  5. {appmap-2.1.5 → appmap-2.1.7}/_appmap/importer.py +1 -1
  6. {appmap-2.1.5 → appmap-2.1.7}/_appmap/instrument.py +0 -1
  7. {appmap-2.1.5 → appmap-2.1.7}/_appmap/recording.py +1 -0
  8. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/conftest.py +2 -0
  9. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/settings.py +13 -0
  10. appmap-2.1.7/_appmap/test/data/django/test/test_request.py +8 -0
  11. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_django.py +3 -3
  12. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_events.py +1 -2
  13. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_properties.py +1 -1
  14. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_recording.py +38 -23
  15. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_test_frameworks.py +14 -0
  16. {appmap-2.1.5 → appmap-2.1.7}/appmap/__init__.py +6 -1
  17. {appmap-2.1.5 → appmap-2.1.7}/appmap/pytest.py +8 -1
  18. {appmap-2.1.5 → appmap-2.1.7}/pyproject.toml +1 -1
  19. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/wrappers.py +40 -21
  20. {appmap-2.1.5 → appmap-2.1.7}/LICENSE +0 -0
  21. {appmap-2.1.5 → appmap-2.1.7}/_appmap/__init__.py +0 -0
  22. {appmap-2.1.5 → appmap-2.1.7}/_appmap/configuration.py +0 -0
  23. {appmap-2.1.5 → appmap-2.1.7}/_appmap/django.py +0 -0
  24. {appmap-2.1.5 → appmap-2.1.7}/_appmap/fastapi.py +0 -0
  25. {appmap-2.1.5 → appmap-2.1.7}/_appmap/flask.py +0 -0
  26. {appmap-2.1.5 → appmap-2.1.7}/_appmap/generation.py +0 -0
  27. {appmap-2.1.5 → appmap-2.1.7}/_appmap/labels.py +0 -0
  28. {appmap-2.1.5 → appmap-2.1.7}/_appmap/metadata.py +0 -0
  29. {appmap-2.1.5 → appmap-2.1.7}/_appmap/noappmap.py +0 -0
  30. {appmap-2.1.5 → appmap-2.1.7}/_appmap/py_version_check.py +0 -0
  31. {appmap-2.1.5 → appmap-2.1.7}/_appmap/recorder.py +0 -0
  32. {appmap-2.1.5 → appmap-2.1.7}/_appmap/remote_recording.py +0 -0
  33. {appmap-2.1.5 → appmap-2.1.7}/_appmap/singleton.py +0 -0
  34. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/__init__.py +0 -0
  35. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/appmap_test_base.py +0 -0
  36. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/bin/server_runner +0 -0
  37. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
  38. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-broken.yml +0 -0
  39. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-class.yml +0 -0
  40. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-empty-path.yml +0 -0
  41. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
  42. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-func.yml +0 -0
  43. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-malformed-path.yml +0 -0
  44. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
  45. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap.yml +0 -0
  46. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap_testing/__init__.py +0 -0
  47. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
  48. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config/src/package/__init__.py +0 -0
  49. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config/test/__init__.py +0 -0
  50. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config/test/foo.py +0 -0
  51. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
  52. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
  53. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
  54. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
  55. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
  56. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-up/appmap.yml +0 -0
  57. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
  58. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
  59. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/appmap.yml +0 -0
  60. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
  61. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
  62. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
  63. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
  64. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
  65. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/djangoapp/urls.py +0 -0
  66. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/init/sitecustomize.py +0 -0
  67. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/manage.py +0 -0
  68. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/pytest.ini +0 -0
  69. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/test/__init__.py +0 -0
  70. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/test/test_app.py +0 -0
  71. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
  72. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/example_class.py +0 -0
  73. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/expected.appmap.json +0 -0
  74. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/fastapi/appmap.yml +0 -0
  75. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
  76. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
  77. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
  78. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/fastapi/test_app.py +0 -0
  79. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask/appmap.yml +0 -0
  80. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask/flaskapp.py +0 -0
  81. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
  82. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask/templates/test.html +0 -0
  83. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask/test_app.py +0 -0
  84. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask-instrumented/appmap.yml +0 -0
  85. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask-instrumented/flaskapp.py +0 -0
  86. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask-instrumented/init/sitecustomize.py +0 -0
  87. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/flask-instrumented/test_app.py +0 -0
  88. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/package1/__init__.py +0 -0
  89. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/package1/package2/__init__.py +0 -0
  90. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/package1/package2/__main__.py +0 -0
  91. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/package1/package2/mod1.py +0 -0
  92. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/params.py +0 -0
  93. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/properties_class.py +0 -0
  94. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/appmap-no-test-cases.yml +0 -0
  95. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/appmap.yml +0 -0
  96. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/pytest-numpy1-no-test-cases.appmap.json +0 -0
  97. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
  98. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/pytest-numpy2-no-test-cases.appmap.json +0 -0
  99. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
  100. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
  101. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
  102. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
  103. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/simple.py +0 -0
  104. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/tests/__init__.py +0 -0
  105. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/tests/test_noappmap.py +0 -0
  106. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest/tests/test_simple.py +0 -0
  107. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest-instrumented/appmap.yml +0 -0
  108. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest-instrumented/init/sitecustomize.py +0 -0
  109. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/pytest-instrumented/test_instrumented.py +0 -0
  110. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/remote.appmap.json +0 -0
  111. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/.gitignore +0 -0
  112. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/appmap-no-test-cases.yml +0 -0
  113. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/appmap.yml +0 -0
  114. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/expected/pytest-no-test-cases.appmap.json +0 -0
  115. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
  116. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
  117. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/test/__init__.py +0 -0
  118. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/trial/test/test_deferred.py +0 -0
  119. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/appmap-no-test-cases.yml +0 -0
  120. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/appmap.yml +0 -0
  121. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
  122. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
  123. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
  124. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
  125. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
  126. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/unittest-no-test-cases.appmap.json +0 -0
  127. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
  128. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
  129. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/simple/__init__.py +0 -0
  130. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
  131. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
  132. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/helpers.py +0 -0
  133. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/normalize.py +0 -0
  134. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_command.py +0 -0
  135. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_configuration.py +0 -0
  136. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_describe_value.py +0 -0
  137. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_django_simplelazyobject.py +0 -0
  138. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_env.py +0 -0
  139. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_fastapi.py +0 -0
  140. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_flask.py +0 -0
  141. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_generation.py +0 -0
  142. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_http.py +0 -0
  143. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_importer.py +0 -0
  144. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_labels.py +0 -0
  145. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_metadata.py +0 -0
  146. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_params.py +0 -0
  147. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_runner.py +0 -0
  148. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_sqlalchemy.py +0 -0
  149. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/test_util.py +0 -0
  150. {appmap-2.1.5 → appmap-2.1.7}/_appmap/test/web_framework.py +0 -0
  151. {appmap-2.1.5 → appmap-2.1.7}/_appmap/testing_framework.py +0 -0
  152. {appmap-2.1.5 → appmap-2.1.7}/_appmap/trace_logger.py +0 -0
  153. {appmap-2.1.5 → appmap-2.1.7}/_appmap/unittest.py +0 -0
  154. {appmap-2.1.5 → appmap-2.1.7}/_appmap/utils.py +0 -0
  155. {appmap-2.1.5 → appmap-2.1.7}/_appmap/web_framework.py +0 -0
  156. {appmap-2.1.5 → appmap-2.1.7}/appmap/command/__init__.py +0 -0
  157. {appmap-2.1.5 → appmap-2.1.7}/appmap/command/appmap_agent_init.py +0 -0
  158. {appmap-2.1.5 → appmap-2.1.7}/appmap/command/appmap_agent_status.py +0 -0
  159. {appmap-2.1.5 → appmap-2.1.7}/appmap/command/appmap_agent_validate.py +0 -0
  160. {appmap-2.1.5 → appmap-2.1.7}/appmap/command/runner.py +0 -0
  161. {appmap-2.1.5 → appmap-2.1.7}/appmap/django.py +0 -0
  162. {appmap-2.1.5 → appmap-2.1.7}/appmap/fastapi.py +0 -0
  163. {appmap-2.1.5 → appmap-2.1.7}/appmap/flask.py +0 -0
  164. {appmap-2.1.5 → appmap-2.1.7}/appmap/http.py +0 -0
  165. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/__init__.py +0 -0
  166. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/crypto.yml +0 -0
  167. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/django.yml +0 -0
  168. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/flask.yml +0 -0
  169. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/formats.yml +0 -0
  170. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/http.yml +0 -0
  171. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/jobs.yml +0 -0
  172. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/jwt.yml +0 -0
  173. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/logger.yml +0 -0
  174. {appmap-2.1.5 → appmap-2.1.7}/appmap/labeling/random.yml +0 -0
  175. {appmap-2.1.5 → appmap-2.1.7}/appmap/sqlalchemy.py +0 -0
  176. {appmap-2.1.5 → appmap-2.1.7}/appmap/unittest.py +0 -0
  177. {appmap-2.1.5 → appmap-2.1.7}/appmap/uvicorn.py +0 -0
  178. {appmap-2.1.5 → appmap-2.1.7}/appmap.pth +0 -0
  179. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/LICENSE +0 -0
  180. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/__init__.py +0 -0
  181. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/arguments.py +0 -0
  182. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/decorators.py +0 -0
  183. {appmap-2.1.5 → appmap-2.1.7}/vendor/_appmap/wrapt/importer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: appmap
3
- Version: 2.1.5
3
+ Version: 2.1.7
4
4
  Summary: Create AppMap files by recording a Python application.
5
5
  Home-page: https://github.com/applandinc/appmap-python
6
6
  License: MIT
@@ -121,16 +121,15 @@ Note that you must install the dependencies contained in
121
121
  [requirements-dev.txt](requirements-dev.txt) before running tests. See the explanation in
122
122
  [pyproject.toml](pyproject.toml) for details.
123
123
 
124
- Additionally, the tests currently require that you set `APPMAP=true`. You can
125
- either run `pytest` with `appmap-python` (see [tox.ini](tox.ini)), or you can explicitly
126
- set the environment variable.
124
+ Additionally, the tests currently require that you set `APPMAP=true` and
125
+ `APPMAP_DISPLAY_PARAMS=true`.
127
126
 
128
127
  [pytest](https://docs.pytest.org/en/stable/) for testing:
129
128
 
130
129
  ```
131
130
  % cd appmap-python
132
131
  % pip install -r requirements-test.txt
133
- % poetry run pytest
132
+ % APPMAP=true APPMAP_DISPLAY_PARAMS=true poetry run pytest
134
133
  ```
135
134
 
136
135
  ### tox
@@ -90,16 +90,15 @@ Note that you must install the dependencies contained in
90
90
  [requirements-dev.txt](requirements-dev.txt) before running tests. See the explanation in
91
91
  [pyproject.toml](pyproject.toml) for details.
92
92
 
93
- Additionally, the tests currently require that you set `APPMAP=true`. You can
94
- either run `pytest` with `appmap-python` (see [tox.ini](tox.ini)), or you can explicitly
95
- set the environment variable.
93
+ Additionally, the tests currently require that you set `APPMAP=true` and
94
+ `APPMAP_DISPLAY_PARAMS=true`.
96
95
 
97
96
  [pytest](https://docs.pytest.org/en/stable/) for testing:
98
97
 
99
98
  ```
100
99
  % cd appmap-python
101
100
  % pip install -r requirements-test.txt
102
- % poetry run pytest
101
+ % APPMAP=true APPMAP_DISPLAY_PARAMS=true poetry run pytest
103
102
  ```
104
103
 
105
104
  ### tox
@@ -1,5 +1,6 @@
1
1
  """Initialize from the environment"""
2
2
 
3
+ from functools import cached_property
3
4
  import logging
4
5
  import logging.config
5
6
  import os
@@ -39,13 +40,13 @@ class Env(metaclass=SingletonMeta):
39
40
 
40
41
  self.log_file_creation_failed = False
41
42
  self._configure_logging()
42
- # This uses _APPMAP, rather than APPMAP, to control whether instrumentation is enabled. The
43
- # tests use this split to make it easier to control recording.
44
- enabled = self._env.get("_APPMAP", "false")
45
- self._enabled = enabled is None or enabled.lower() != "false"
46
-
47
- self._root_dir = str(self._cwd) + "/"
48
- self._root_dir_len = len(self._root_dir)
43
+ # This uses the underscore-decorated, rather than the undecorated variants, to control
44
+ # whether these settings are enabled. The tests use this split to make it easier to control
45
+ # them.
46
+ enabled = self._env.get("_APPMAP", None)
47
+ self._enabled = enabled is not None and enabled.lower() != "false"
48
+ display_params = self._env.get("_APPMAP_DISPLAY_PARAMS", None)
49
+ self._display_params = display_params is not None and display_params.lower() != "false"
49
50
 
50
51
  logger = logging.getLogger(__name__)
51
52
  # The user shouldn't set APPMAP_OUTPUT_DIR, but some tests depend on being able to use it.
@@ -69,13 +70,13 @@ class Env(metaclass=SingletonMeta):
69
70
  def delete(self, name):
70
71
  del self._env[name]
71
72
 
72
- @property
73
+ @cached_property
73
74
  def root_dir(self):
74
- return self._root_dir
75
+ return str(self._cwd) + "/"
75
76
 
76
- @property
77
+ @cached_property
77
78
  def root_dir_len(self):
78
- return self._root_dir_len
79
+ return len(self.root_dir)
79
80
 
80
81
  @property
81
82
  def output_dir(self):
@@ -123,7 +124,7 @@ class Env(metaclass=SingletonMeta):
123
124
  if value:
124
125
  self.set(key, value)
125
126
 
126
- @property
127
+ @cached_property
127
128
  def is_appmap_repo(self):
128
129
  return os.path.exists("appmap/__init__.py") and os.path.exists(
129
130
  "_appmap/__init__.py"
@@ -131,7 +132,7 @@ class Env(metaclass=SingletonMeta):
131
132
 
132
133
  @property
133
134
  def display_params(self):
134
- return self.get("APPMAP_DISPLAY_PARAMS", "true").lower() == "true"
135
+ return self._display_params
135
136
 
136
137
  def getLogger(self, name) -> trace_logger.TraceLogger:
137
138
  return cast(trace_logger.TraceLogger, logging.getLogger(name))
@@ -6,7 +6,6 @@ import threading
6
6
  from functools import lru_cache, partial
7
7
  from inspect import Parameter, Signature
8
8
  from itertools import chain
9
- import types
10
9
 
11
10
  from .env import Env
12
11
  from .recorder import Recorder
@@ -5,7 +5,7 @@ import types
5
5
  from abc import ABC, abstractmethod
6
6
  from collections import namedtuple
7
7
  from collections.abc import MutableSequence
8
- from functools import partial, reduce
8
+ from functools import reduce
9
9
 
10
10
  from _appmap import wrapt
11
11
 
@@ -119,7 +119,6 @@ def call_instrumented(f, instance, args, kwargs):
119
119
  def instrument(filterable):
120
120
  """return an instrumented function"""
121
121
  logger.debug("hooking %s", filterable.fqname)
122
- fn = filterable.obj
123
122
 
124
123
  make_call_event = event.CallEvent.make(filterable)
125
124
  params = CallEvent.make_params(filterable)
@@ -62,6 +62,7 @@ class NoopRecording:
62
62
 
63
63
  def __init__(self, exit_hook=None):
64
64
  self.exit_hook = exit_hook
65
+ self.events = []
65
66
 
66
67
  def start(self):
67
68
  pass
@@ -68,6 +68,8 @@ def pytest_runtest_setup(item):
68
68
  elif appmap_enabled is None:
69
69
  env.pop("_APPMAP", None)
70
70
 
71
+ env["_APPMAP_DISPLAY_PARAMS"] = env.get("APPMAP_DISPLAY_PARAMS", "true")
72
+
71
73
  _appmap.initialize(env=env) # pylint: disable=protected-access
72
74
 
73
75
  # Some tests want yaml instrumented, others don't.
@@ -1,3 +1,9 @@
1
+ from pathlib import Path
2
+
3
+
4
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
5
+ BASE_DIR = Path(__file__).resolve().parent.parent
6
+
1
7
  # If the SECRET_KEY isn't defined we get the misleading error message
2
8
  # CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
3
9
  SECRET_KEY = "3*+d^_kjnr2gz)4q2m(&&^%$p4fj5dk3%lz4pl3g4m-%6!gf&)"
@@ -10,3 +16,10 @@ ROOT_URLCONF = "djangoapp.urls"
10
16
 
11
17
  # Turn off deprecation warning
12
18
  USE_TZ = True
19
+
20
+ DATABASES = {
21
+ "default": {
22
+ "ENGINE": "django.db.backends.sqlite3",
23
+ "NAME": BASE_DIR / "db.sqlite3",
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ from django.test import TestCase
2
+ from django.test import Client
3
+
4
+
5
+ class TestRequest(TestCase):
6
+ def test_request_test(self):
7
+ resp = self.client.get("/test")
8
+ assert resp.status_code == 200
@@ -198,7 +198,7 @@ class TestDjangoApp:
198
198
  # To really check middleware reset, the tests must run in order,
199
199
  # so disable randomly.
200
200
  result = pytester.runpytest("-svv", "-p", "no:randomly")
201
- result.assert_outcomes(passed=5, failed=0, errors=0)
201
+ result.assert_outcomes(passed=6, failed=0, errors=0)
202
202
  # Look for the http_server_request event in test_app's appmap. If
203
203
  # middleware reset is broken, it won't be there.
204
204
  appmap_file = pytester.path / "tmp" / "appmap" / "pytest" / "test_request.appmap.json"
@@ -212,7 +212,7 @@ class TestDjangoApp:
212
212
  def test_disabled(self, pytester, monkeypatch):
213
213
  monkeypatch.setenv("_APPMAP", "false")
214
214
  result = pytester.runpytest("-svv", "-p", "no:randomly", "-k", "test_request")
215
- result.assert_outcomes(passed=2, failed=0, errors=0)
215
+ result.assert_outcomes(passed=3, failed=0, errors=0)
216
216
  assert not (pytester.path / "tmp").exists()
217
217
 
218
218
  def test_disabled_for_process(self, pytester, monkeypatch):
@@ -222,7 +222,7 @@ class TestDjangoApp:
222
222
 
223
223
  # There are two tests for remote recording. They should both fail,
224
224
  # because process recording should disable remote recording.
225
- result.assert_outcomes(passed=3, failed=2, errors=0)
225
+ result.assert_outcomes(passed=4, failed=2, errors=0)
226
226
 
227
227
  assert (pytester.path / "tmp" / "appmap" / "process").exists()
228
228
  assert not (pytester.path / "tmp" / "appmap" / "requests").exists()
@@ -9,7 +9,6 @@ from threading import Thread
9
9
  import pytest
10
10
 
11
11
  import appmap
12
- from _appmap.env import Env
13
12
  from _appmap.event import _EventIds
14
13
 
15
14
 
@@ -84,8 +83,8 @@ class TestEvents:
84
83
  actual_value = r.events[0].parameters[0]["value"]
85
84
  assert re.fullmatch(expected_re, actual_value)
86
85
 
86
+ @pytest.mark.appmap_enabled(env={"APPMAP_DISPLAY_PARAMS": "false"})
87
87
  def test_when_display_disabled(self, mocker):
88
- Env.current.set("APPMAP_DISPLAY_PARAMS", "false")
89
88
  r = appmap.Recording()
90
89
  with r:
91
90
  from example_class import ExampleClass # pylint: disable=import-outside-toplevel
@@ -145,4 +145,4 @@ def test_functools_partial(events):
145
145
  from properties_class import PropertiesClass
146
146
 
147
147
  PropertiesClass.static_partial_method()
148
- assert len(events) > 0
148
+ assert len(events) > 0
@@ -9,6 +9,7 @@ from threading import Thread
9
9
  import pytest
10
10
 
11
11
  import appmap
12
+ from _appmap.configuration import Config
12
13
  from _appmap.event import Event
13
14
  from _appmap.recorder import Recorder, ThreadRecorder
14
15
  from _appmap.wrapt import FunctionWrapper
@@ -16,6 +17,17 @@ from _appmap.wrapt import FunctionWrapper
16
17
  from .normalize import normalize_appmap, remove_line_numbers
17
18
 
18
19
 
20
+ def _call_modfunc(q):
21
+ r = appmap.Recording()
22
+ with r:
23
+ f = q.get()
24
+ f()
25
+ events = r.events
26
+ assert len(events) == 2
27
+ assert events[0].event == "call"
28
+ assert events[0].method_id == "modfunc"
29
+
30
+
19
31
  @pytest.mark.appmap_enabled
20
32
  @pytest.mark.usefixtures("with_data_dir")
21
33
  class TestRecordingWhenEnabled:
@@ -47,11 +59,12 @@ class TestRecordingWhenEnabled:
47
59
  ), f"expected path {expected_path}"
48
60
 
49
61
  def test_recording_clears(self):
50
- from example_class import ( # pyright: ignore[reportMissingImports] pylint: disable=import-error
51
- ExampleClass,
52
- )
62
+ # pylint: disable=import-error
63
+ from example_class import ExampleClass # pyright: ignore[reportMissingImports]
64
+ # pylint: enable=import-error
53
65
 
54
- with appmap.Recording():
66
+ rec = appmap.Recording()
67
+ with rec:
55
68
  ExampleClass.static_method()
56
69
 
57
70
  # fresh recording shouldn't contain previous traces
@@ -68,9 +81,9 @@ class TestRecordingWhenEnabled:
68
81
  assert rec.events[2].method_id == "instance_method"
69
82
 
70
83
  def test_recording_shallow(self):
71
- from example_class import ( # pyright: ignore[reportMissingImports] pylint: disable=import-error
72
- ExampleClass,
73
- )
84
+ # pylint: disable=import-error
85
+ from example_class import ExampleClass # pyright: ignore[reportMissingImports]
86
+ # pylint: enable=import-error
74
87
 
75
88
  rec = appmap.Recording()
76
89
  with rec:
@@ -82,9 +95,9 @@ class TestRecordingWhenEnabled:
82
95
  assert len(rec.events) == 8
83
96
 
84
97
  def test_recording_wrapped(self):
85
- from example_class import ( # pyright: ignore[reportMissingImports] pylint: disable=import-error
86
- ExampleClass,
87
- )
98
+ # pylint: disable=import-error
99
+ from example_class import ExampleClass # pyright: ignore[reportMissingImports]
100
+ # pylint: enable=import-error
88
101
 
89
102
  rec = appmap.Recording()
90
103
  with rec:
@@ -117,21 +130,23 @@ class TestRecordingWhenEnabled:
117
130
  f1 = deepcopy(modfunc)
118
131
  f1()
119
132
 
120
- def test_can_pickle(self):
121
- import pickle
133
+ def test_can_pickle(self, monkeypatch):
134
+ # Make sure subprocesses see whatever config is set for us.
135
+ monkeypatch.setenv("APPMAP_CONFIG", str(Config.current._file)) # pylint: disable=protected-access
122
136
 
123
- from example_class import ( # pyright: ignore[reportMissingImports] pylint: disable=import-error
124
- modfunc,
125
- )
137
+ from multiprocessing import Process, Queue
126
138
 
127
- rec = appmap.Recording()
128
- with rec:
129
- assert isinstance(modfunc, FunctionWrapper)
130
- f = pickle.loads(pickle.dumps(modfunc))
131
- f()
132
- evt = rec.events[-2]
133
- assert evt.event == "call"
134
- assert evt.method_id == "modfunc"
139
+ # pylint: disable=import-error
140
+ from example_class import modfunc # pyright: ignore[reportMissingImports]
141
+ # pylint: enable=import-error
142
+
143
+ assert isinstance(modfunc, FunctionWrapper), "modfunc isn't instrumented?"
144
+
145
+ q = Queue()
146
+ q.put(modfunc)
147
+ p = Process(target=_call_modfunc, args=(q,))
148
+ p.start()
149
+ p.join()
135
150
 
136
151
 
137
152
  @pytest.mark.appmap_enabled
@@ -122,6 +122,20 @@ class TestPytestRunnerPytest(_TestTestRunner):
122
122
  verify_expected_appmap(testdir, f"-numpy{numpy_version.major}-no-test-cases")
123
123
  verify_expected_metadata(testdir)
124
124
 
125
+ @pytest.mark.example_dir("django")
126
+ def test_pytest_django(testdir):
127
+ result = testdir.runpytest("-svv", "-k", "test_request_test")
128
+ result.assert_outcomes(passed=1)
129
+ # django.test.TestCase is a subclass of unittest.TestCase, so recorder type is unittest
130
+ assert (
131
+ testdir.path
132
+ / "tmp"
133
+ / "appmap"
134
+ / "unittest"
135
+ / "test_test_request_TestRequest_test_request_test.appmap.json"
136
+ ).exists()
137
+ assert not (testdir.path / "tmp" / "appmap" / "requests").exists()
138
+
125
139
 
126
140
  @pytest.mark.example_dir("trial")
127
141
  class TestPytestRunnerTrial(_TestTestRunner):
@@ -11,8 +11,11 @@ _enabled = os.environ.get("APPMAP", None)
11
11
  _recording_exported = False
12
12
  if _enabled is None or _enabled.upper() == "TRUE":
13
13
  if _enabled is not None:
14
- # Use setdefault so tests can manage _APPMAP as necessary
14
+ # Use setdefault so tests can manage settings as necessary
15
15
  os.environ.setdefault("_APPMAP", _enabled)
16
+ _display_params = os.environ.get("APPMAP_DISPLAY_PARAMS", "false")
17
+ os.environ.setdefault("_APPMAP_DISPLAY_PARAMS", _display_params)
18
+
16
19
  from _appmap import generation # noqa: F401
17
20
  from _appmap.env import Env # noqa: F401
18
21
  from _appmap.importer import instrument_module # noqa: F401
@@ -52,8 +55,10 @@ if _enabled is None or _enabled.upper() == "TRUE":
52
55
  return Env.current.enabled
53
56
  else:
54
57
  os.environ.pop("_APPMAP", None)
58
+ os.environ.pop("_APPMAP_DISPLAY_PARAMS", None)
55
59
  else:
56
60
  os.environ.setdefault("_APPMAP", "false")
61
+ os.environ.setdefault("_APPMAP_DISPLAY_PARAMS", "false")
57
62
 
58
63
  if not _recording_exported:
59
64
  # Client code that imports appmap.Recording should run correctly
@@ -1,6 +1,13 @@
1
1
  from importlib.metadata import version
2
2
 
3
3
  import pytest
4
+ try:
5
+ from pytest_django.django_compat import is_django_unittest
6
+ except ImportError:
7
+
8
+ def is_django_unittest(item):
9
+ return False
10
+
4
11
 
5
12
  from _appmap import noappmap, testing_framework, wrapt
6
13
  from _appmap.env import Env
@@ -51,7 +58,7 @@ if not Env.current.is_appmap_repo and Env.current.enables("tests"):
51
58
  # running the test case. (This nesting of function calls is
52
59
  # verified by the expected appmap in the test for a unittest
53
60
  # TestCase run by pytest.)
54
- if hasattr(item, "_testcase"):
61
+ if hasattr(item, "_testcase") and not is_django_unittest(item):
55
62
  setattr(
56
63
  item._testcase, # pylint: disable=protected-access
57
64
  "_appmap_pytest_recording",
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "appmap"
3
- version = "2.1.5"
3
+ version = "2.1.7"
4
4
  description = "Create AppMap files by recording a Python application."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -457,11 +457,8 @@ class ObjectProxy(with_metaclass(_ObjectProxyMetaType)):
457
457
  raise NotImplementedError(
458
458
  'object proxy must define __reduce_ex__()')
459
459
 
460
- # Return the qualname of the wrapped function instead of a tuple. This allows an instance of
461
- # subclasses to be pickled as the function it wraps. This seems to be adequate for generating
462
- # AppMaps.
463
- def __reduce_ex__(self, protocol):
464
- return self.__wrapped__.__qualname__
460
+ def __reduce_ex__(self):
461
+ raise NotImplementedError("object proxy must define __reduce_ex__()")
465
462
 
466
463
  class CallableObjectProxy(ObjectProxy):
467
464
 
@@ -508,6 +505,20 @@ class PartialCallableObjectProxy(ObjectProxy):
508
505
 
509
506
  return self.__wrapped__(*_args, **_kwargs)
510
507
 
508
+ def _unpickle_functionwrapper(modname, qualname):
509
+ """
510
+ Given the module name and qualname of a function, return a FunctionWrapper instance for it. This
511
+ simply imports the module, then fetches the appropriate function. Provided AppMap
512
+ instrumentation has been configured correctly when unpickling, the attribute for the function
513
+ will be a FunctionWrapper. If it hasn't been configured correctly, the attribute will simply be
514
+ the original function. (This means the application will function correctly, but no events will
515
+ get generated when the function is called.)
516
+ """
517
+ _, _, original = resolve_path(modname, qualname)
518
+
519
+ return original
520
+
521
+
511
522
  class _FunctionWrapperBase(ObjectProxy):
512
523
  __slots__ = (
513
524
  "_self_instance",
@@ -656,6 +667,20 @@ class _FunctionWrapperBase(ObjectProxy):
656
667
  else:
657
668
  return issubclass(subclass, self.__wrapped__)
658
669
 
670
+ # Implement this here, rather than in ObjectProxy, because _unpickle_functionwrapper will only
671
+ # create new instances of subclasses of _FunctionWrapperBase via the agent's import hooks.
672
+ def __reduce_ex__(self, _):
673
+ modname = self.__wrapped__.__module__
674
+ qualname = getattr(self.__wrapped__, "__qualname__", None)
675
+ if qualname is None:
676
+ qualname = self.__wrapped__.__name__
677
+
678
+ return (
679
+ _unpickle_functionwrapper,
680
+ (modname, qualname),
681
+ )
682
+
683
+
659
684
  class BoundFunctionWrapper(_FunctionWrapperBase):
660
685
 
661
686
  def __new__(cls, *args, **kwargs):
@@ -738,24 +763,18 @@ class BoundFunctionWrapper(_FunctionWrapperBase):
738
763
  return self._self_wrapper(self.__wrapped__, instance, args,
739
764
  kwargs)
740
765
 
741
- class FunctionWrapper(_FunctionWrapperBase):
766
+ def __getattribute__(self, name):
767
+ if name == "__func__":
768
+ # The __func__ attribute of a bound method is the unbound method. The corresponding
769
+ # attribute of a BoundFunctionWrapper is the associated FunctionWrapper (saved in
770
+ # _self_parent when the BFW is created).
771
+ return self._self_parent
742
772
 
743
- __bound_function_wrapper__ = BoundFunctionWrapper
773
+ return super().__getattribute__(name)
744
774
 
745
- # The code here is pretty complicated (see the comment below), and it's not completely clear to
746
- # me whether it actually keeps any state. If it does, __reduce_ex__ needs to return a tuple so a
747
- # new FunctionWrapper will be created. If it doesn't, then __reduce_ex__ can simply return a
748
- # string, which would cause deepcopy to return the original FunctionWrapper.
749
- #
750
-
751
- # def __reduce_ex__(self, protocol):
752
- # return self.__wrapped__.__qualname__
753
-
754
- # return FunctionWrapper, (
755
- # self.__wrapped__,
756
- # self._self_wrapper,
757
- # self._self_enabled,
758
- # )
775
+
776
+ class FunctionWrapper(_FunctionWrapperBase):
777
+ __bound_function_wrapper__ = BoundFunctionWrapper
759
778
 
760
779
  def __init__(self, wrapped, wrapper, enabled=None):
761
780
  # What it is we are wrapping here could be anything. We need to
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
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
File without changes
File without changes
File without changes