appmap 2.1.2__tar.gz → 2.1.4__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 (182) hide show
  1. {appmap-2.1.2 → appmap-2.1.4}/PKG-INFO +1 -1
  2. {appmap-2.1.2 → appmap-2.1.4}/_appmap/configuration.py +40 -16
  3. {appmap-2.1.2 → appmap-2.1.4}/_appmap/importer.py +21 -9
  4. {appmap-2.1.2 → appmap-2.1.4}/_appmap/instrument.py +1 -1
  5. appmap-2.1.2/_appmap/test/data/unittest/appmap.yml → appmap-2.1.4/_appmap/test/data/pytest/appmap-no-test-cases.yml +1 -0
  6. appmap-2.1.4/_appmap/test/data/pytest/appmap.yml +5 -0
  7. appmap-2.1.2/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json → appmap-2.1.4/_appmap/test/data/pytest/expected/pytest-numpy1-no-test-cases.appmap.json +1 -1
  8. appmap-2.1.4/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +281 -0
  9. appmap-2.1.2/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json → appmap-2.1.4/_appmap/test/data/pytest/expected/pytest-numpy2-no-test-cases.appmap.json +1 -1
  10. appmap-2.1.4/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +281 -0
  11. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest/expected/status_errored.metadata.json +1 -1
  12. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest/expected/status_failed.metadata.json +1 -1
  13. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +1 -1
  14. appmap-2.1.4/_appmap/test/data/trial/appmap-no-test-cases.yml +4 -0
  15. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/trial/appmap.yml +1 -0
  16. appmap-2.1.4/_appmap/test/data/trial/expected/pytest-no-test-cases.appmap.json +28 -0
  17. appmap-2.1.4/_appmap/test/data/unittest/appmap-no-test-cases.yml +4 -0
  18. {appmap-2.1.2/_appmap/test/data/pytest → appmap-2.1.4/_appmap/test/data/unittest}/appmap.yml +1 -0
  19. appmap-2.1.4/_appmap/test/data/unittest/expected/unittest-no-test-cases.appmap.json +148 -0
  20. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_configuration.py +5 -2
  21. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_events.py +0 -1
  22. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_fastapi.py +2 -4
  23. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_params.py +1 -1
  24. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_properties.py +1 -4
  25. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_test_frameworks.py +24 -1
  26. {appmap-2.1.2 → appmap-2.1.4}/_appmap/testing_framework.py +15 -10
  27. appmap-2.1.4/_appmap/unittest.py +39 -0
  28. appmap-2.1.4/appmap/command/__init__.py +0 -0
  29. {appmap-2.1.2 → appmap-2.1.4}/appmap/fastapi.py +1 -1
  30. {appmap-2.1.2 → appmap-2.1.4}/appmap/pytest.py +2 -0
  31. {appmap-2.1.2 → appmap-2.1.4}/pyproject.toml +3 -1
  32. {appmap-2.1.2 → appmap-2.1.4}/vendor/_appmap/wrapt/wrappers.py +18 -13
  33. appmap-2.1.2/_appmap/unittest.py +0 -84
  34. {appmap-2.1.2 → appmap-2.1.4}/LICENSE +0 -0
  35. {appmap-2.1.2 → appmap-2.1.4}/README.md +0 -0
  36. {appmap-2.1.2 → appmap-2.1.4}/_appmap/__init__.py +0 -0
  37. {appmap-2.1.2 → appmap-2.1.4}/_appmap/django.py +0 -0
  38. {appmap-2.1.2 → appmap-2.1.4}/_appmap/env.py +0 -0
  39. {appmap-2.1.2 → appmap-2.1.4}/_appmap/event.py +0 -0
  40. {appmap-2.1.2 → appmap-2.1.4}/_appmap/fastapi.py +0 -0
  41. {appmap-2.1.2 → appmap-2.1.4}/_appmap/flask.py +0 -0
  42. {appmap-2.1.2 → appmap-2.1.4}/_appmap/generation.py +0 -0
  43. {appmap-2.1.2 → appmap-2.1.4}/_appmap/labels.py +0 -0
  44. {appmap-2.1.2 → appmap-2.1.4}/_appmap/metadata.py +0 -0
  45. {appmap-2.1.2 → appmap-2.1.4}/_appmap/noappmap.py +0 -0
  46. {appmap-2.1.2 → appmap-2.1.4}/_appmap/py_version_check.py +0 -0
  47. {appmap-2.1.2 → appmap-2.1.4}/_appmap/recorder.py +0 -0
  48. {appmap-2.1.2 → appmap-2.1.4}/_appmap/recording.py +0 -0
  49. {appmap-2.1.2 → appmap-2.1.4}/_appmap/remote_recording.py +0 -0
  50. {appmap-2.1.2 → appmap-2.1.4}/_appmap/singleton.py +0 -0
  51. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/__init__.py +0 -0
  52. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/appmap_test_base.py +0 -0
  53. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/bin/server_runner +0 -0
  54. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/conftest.py +0 -0
  55. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
  56. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-broken.yml +0 -0
  57. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-class.yml +0 -0
  58. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-empty-path.yml +0 -0
  59. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
  60. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-func.yml +0 -0
  61. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-malformed-path.yml +0 -0
  62. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
  63. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap.yml +0 -0
  64. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap_testing/__init__.py +0 -0
  65. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
  66. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config/src/package/__init__.py +0 -0
  67. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config/test/__init__.py +0 -0
  68. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config/test/foo.py +0 -0
  69. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
  70. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
  71. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
  72. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
  73. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
  74. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-up/appmap.yml +0 -0
  75. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
  76. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
  77. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/appmap.yml +0 -0
  78. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
  79. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
  80. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
  81. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
  82. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/settings.py +0 -0
  83. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
  84. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/djangoapp/urls.py +0 -0
  85. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/init/sitecustomize.py +0 -0
  86. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/manage.py +0 -0
  87. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/pytest.ini +0 -0
  88. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/test/__init__.py +0 -0
  89. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/test/test_app.py +0 -0
  90. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
  91. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/example_class.py +0 -0
  92. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/expected.appmap.json +0 -0
  93. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/fastapi/appmap.yml +0 -0
  94. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
  95. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
  96. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
  97. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/fastapi/test_app.py +0 -0
  98. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask/appmap.yml +0 -0
  99. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask/flaskapp.py +0 -0
  100. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
  101. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask/templates/test.html +0 -0
  102. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask/test_app.py +0 -0
  103. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask-instrumented/appmap.yml +0 -0
  104. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask-instrumented/flaskapp.py +0 -0
  105. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask-instrumented/init/sitecustomize.py +0 -0
  106. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/flask-instrumented/test_app.py +0 -0
  107. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/package1/__init__.py +0 -0
  108. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/package1/package2/__init__.py +0 -0
  109. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/package1/package2/__main__.py +0 -0
  110. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/package1/package2/mod1.py +0 -0
  111. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/params.py +0 -0
  112. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest/simple.py +0 -0
  113. {appmap-2.1.2/_appmap/test/data/trial/test → appmap-2.1.4/_appmap/test/data/pytest/tests}/__init__.py +0 -0
  114. {appmap-2.1.2/_appmap/test/data/pytest → appmap-2.1.4/_appmap/test/data/pytest/tests}/test_noappmap.py +0 -0
  115. {appmap-2.1.2/_appmap/test/data/pytest → appmap-2.1.4/_appmap/test/data/pytest/tests}/test_simple.py +0 -0
  116. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest-instrumented/appmap.yml +0 -0
  117. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest-instrumented/init/sitecustomize.py +0 -0
  118. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/pytest-instrumented/test_instrumented.py +0 -0
  119. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/remote.appmap.json +0 -0
  120. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/trial/.gitignore +0 -0
  121. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
  122. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
  123. {appmap-2.1.2/appmap/command → appmap-2.1.4/_appmap/test/data/trial/test}/__init__.py +0 -0
  124. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/trial/test/test_deferred.py +0 -0
  125. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
  126. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
  127. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
  128. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
  129. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
  130. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
  131. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
  132. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/simple/__init__.py +0 -0
  133. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
  134. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
  135. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/helpers.py +0 -0
  136. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/normalize.py +0 -0
  137. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_command.py +0 -0
  138. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_describe_value.py +0 -0
  139. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_django.py +0 -0
  140. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_django_simplelazyobject.py +0 -0
  141. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_env.py +0 -0
  142. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_flask.py +0 -0
  143. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_generation.py +0 -0
  144. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_http.py +0 -0
  145. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_importer.py +0 -0
  146. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_labels.py +0 -0
  147. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_metadata.py +0 -0
  148. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_recording.py +0 -0
  149. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_runner.py +0 -0
  150. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_sqlalchemy.py +0 -0
  151. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/test_util.py +0 -0
  152. {appmap-2.1.2 → appmap-2.1.4}/_appmap/test/web_framework.py +0 -0
  153. {appmap-2.1.2 → appmap-2.1.4}/_appmap/trace_logger.py +0 -0
  154. {appmap-2.1.2 → appmap-2.1.4}/_appmap/utils.py +0 -0
  155. {appmap-2.1.2 → appmap-2.1.4}/_appmap/web_framework.py +0 -0
  156. {appmap-2.1.2 → appmap-2.1.4}/appmap/__init__.py +0 -0
  157. {appmap-2.1.2 → appmap-2.1.4}/appmap/command/appmap_agent_init.py +0 -0
  158. {appmap-2.1.2 → appmap-2.1.4}/appmap/command/appmap_agent_status.py +0 -0
  159. {appmap-2.1.2 → appmap-2.1.4}/appmap/command/appmap_agent_validate.py +0 -0
  160. {appmap-2.1.2 → appmap-2.1.4}/appmap/command/runner.py +0 -0
  161. {appmap-2.1.2 → appmap-2.1.4}/appmap/django.py +0 -0
  162. {appmap-2.1.2 → appmap-2.1.4}/appmap/flask.py +0 -0
  163. {appmap-2.1.2 → appmap-2.1.4}/appmap/http.py +0 -0
  164. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/__init__.py +0 -0
  165. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/crypto.yml +0 -0
  166. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/django.yml +0 -0
  167. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/flask.yml +0 -0
  168. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/formats.yml +0 -0
  169. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/http.yml +0 -0
  170. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/jobs.yml +0 -0
  171. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/jwt.yml +0 -0
  172. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/logger.yml +0 -0
  173. {appmap-2.1.2 → appmap-2.1.4}/appmap/labeling/random.yml +0 -0
  174. {appmap-2.1.2 → appmap-2.1.4}/appmap/sqlalchemy.py +0 -0
  175. {appmap-2.1.2 → appmap-2.1.4}/appmap/unittest.py +0 -0
  176. {appmap-2.1.2 → appmap-2.1.4}/appmap/uvicorn.py +0 -0
  177. {appmap-2.1.2 → appmap-2.1.4}/appmap.pth +0 -0
  178. {appmap-2.1.2 → appmap-2.1.4}/vendor/_appmap/wrapt/LICENSE +0 -0
  179. {appmap-2.1.2 → appmap-2.1.4}/vendor/_appmap/wrapt/__init__.py +0 -0
  180. {appmap-2.1.2 → appmap-2.1.4}/vendor/_appmap/wrapt/arguments.py +0 -0
  181. {appmap-2.1.2 → appmap-2.1.4}/vendor/_appmap/wrapt/decorators.py +0 -0
  182. {appmap-2.1.2 → appmap-2.1.4}/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.2
3
+ Version: 2.1.4
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
@@ -5,13 +5,16 @@ Manage Configuration AppMap recorder for Python.
5
5
  import ast
6
6
  import importlib.metadata
7
7
  import inspect
8
+ import json
8
9
  import os
10
+ import re
9
11
  import sys
10
12
  from os.path import realpath
11
13
  from pathlib import Path
12
14
  from textwrap import dedent
13
15
 
14
16
  import yaml
17
+ from yaml import SafeLoader
15
18
  from yaml.parser import ParserError
16
19
 
17
20
  from _appmap.labels import LabelSet
@@ -49,20 +52,19 @@ def _get_sys_prefix():
49
52
  return realpath(sys.prefix)
50
53
 
51
54
 
55
+ _EXCLUDE_PATTERN = re.compile(r"\..*|node_modules|.*test.*|site-packages")
56
+
57
+
52
58
  def find_top_packages(rootdir):
53
59
  """
54
- Scan a directory tree for packages that should appear in the
55
- default config file.
60
+ Scan a directory tree for packages that should appear in the default config file.
56
61
 
57
- Examine directories in rootdir, to see if they contains an
58
- __init__.py. If it does, add it to the list of packages and don't
59
- scan any of its subdirectories. If it doesn't, scan its
62
+ Examine each directory in rootdir, to see if it contains an __init__.py. If it does, add it to
63
+ the list of packages and don't scan any of its subdirectories. If it doesn't, scan its
60
64
  subdirectories to find __init__.py.
61
65
 
62
- Some directories are automatically excluded from the search:
63
- * sys.prefix
64
- * Hidden directories (i.e. those that start with a '.')
65
- * node_modules
66
+ Directory traversal will stop at directories that match _EXCLUDE_PATTERN. Such a directory (and
67
+ its subdirectories) will not be added to the returned packages.
66
68
 
67
69
  For example, in a directory like this
68
70
 
@@ -70,7 +72,7 @@ def find_top_packages(rootdir):
70
72
  LICENSE Makefile appveyor.yml docs/ src/ tests/
71
73
  MANIFEST.in README.rst blog/ setup.py tddium.yml tox.ini
72
74
 
73
- docs, src, tests, and blog will get scanned.
75
+ docs, src, blog will get scanned. tests will be ignored.
74
76
 
75
77
  Only src has a subdirectory containing an __init__.py:
76
78
 
@@ -104,7 +106,7 @@ def find_top_packages(rootdir):
104
106
  packages = set()
105
107
 
106
108
  def excluded(d):
107
- excluded = d == "node_modules" or d[0] == "."
109
+ excluded = _EXCLUDE_PATTERN.search(d) is not None
108
110
  if excluded:
109
111
  logger.trace("excluding dir %s", d)
110
112
  return excluded
@@ -129,6 +131,19 @@ def find_top_packages(rootdir):
129
131
  class AppMapInvalidConfigException(Exception):
130
132
  pass
131
133
 
134
+ # We don't have any control over the PyYAML class hierarchy, so we can't control how many ancestors
135
+ # SafeLoader has....
136
+ class _ConfigLoader(SafeLoader): # pylint: disable=too-many-ancestors
137
+ def construct_mapping(self, node, deep=False):
138
+ mapping = super().construct_mapping(node, deep=deep)
139
+ # Allow record_test_cases to be set using a string (in addition to allowing a boolean).
140
+ if "record_test_cases" in mapping:
141
+ val = mapping["record_test_cases"]
142
+ if isinstance(val, str):
143
+ mapping["record_test_cases"] = val.lower() == "true"
144
+ return mapping
145
+
146
+
132
147
  class Config(metaclass=SingletonMeta):
133
148
  """Singleton Config class"""
134
149
 
@@ -144,6 +159,9 @@ class Config(metaclass=SingletonMeta):
144
159
  if "labels" in self._config:
145
160
  self.labels.append(self._config["labels"])
146
161
 
162
+ def __repr__(self):
163
+ return json.dumps(self._config["packages"])
164
+
147
165
  @property
148
166
  def name(self):
149
167
  return self._config["name"]
@@ -152,11 +170,16 @@ class Config(metaclass=SingletonMeta):
152
170
  def packages(self):
153
171
  return self._config["packages"]
154
172
 
173
+ @property
174
+ def record_test_cases(self):
175
+ return self._config.get("record_test_cases", False)
176
+
155
177
  @property
156
178
  def default(self):
157
179
  ret = {
158
180
  "name": self.default_name,
159
181
  "language": "python",
182
+ "record_test_cases": False,
160
183
  "packages": self.default_packages,
161
184
  }
162
185
  env = Env.current
@@ -229,7 +252,7 @@ class Config(metaclass=SingletonMeta):
229
252
  Env.current.enabled = False
230
253
  self.file_valid = False
231
254
  try:
232
- self._config = yaml.safe_load(path.read_text(encoding="utf-8"))
255
+ self._config = yaml.load(path.read_text(encoding="utf-8"), Loader=_ConfigLoader)
233
256
  if not self._config:
234
257
  # It parsed, but was (effectively) empty.
235
258
  self._config = self.default
@@ -330,7 +353,6 @@ It will be created with this configuration:
330
353
  except SyntaxError:
331
354
  return False
332
355
 
333
-
334
356
  def startswith(prefix, sequence):
335
357
  """
336
358
  Check if a sequence starts with the prefix.
@@ -373,7 +395,8 @@ class DistMatcher(PathMatcher):
373
395
  def __init__(self, dist, *args, **kwargs):
374
396
  super().__init__(*args, **kwargs)
375
397
  self.dist = dist
376
- self.files = [str(pp.locate()) for pp in importlib.metadata.files(dist)]
398
+ dist_files = importlib.metadata.files(dist)
399
+ self.files = [str(pp.locate()) for pp in dist_files] if dist_files is not None else []
377
400
 
378
401
  def matches(self, filterable):
379
402
  try:
@@ -419,8 +442,8 @@ class MatcherFilter(Filter):
419
442
  # appropriate.
420
443
  #
421
444
  rule = self.match(filterable)
422
- wrapped = getattr(filterable.obj, "_appmap_wrapped", None)
423
- if wrapped is None:
445
+ wrapped = getattr(filterable.obj, "_appmap_instrumented", None)
446
+ if not wrapped:
424
447
  logger.trace(" wrapping %s", filterable.fqname)
425
448
  Config.current.labels.apply(filterable)
426
449
  ret = instrument(filterable)
@@ -483,6 +506,7 @@ if _startup_messages_shown is None:
483
506
  # pylint: disable=protected-access
484
507
  c._load_config(show_warnings=True)
485
508
  logger.info("file: %s", c._file if c.file_present else "[no appmap.yml]")
509
+ logger.info("config: %r", c)
486
510
  logger.debug("package_functions: %s", c.package_functions)
487
511
  logger.info("env: %r", os.environ)
488
512
  os.environ["_APPMAP_MESSAGES_SHOWN"] = "true"
@@ -42,8 +42,8 @@ class FilterableFn(
42
42
  ):
43
43
  __slots__ = ()
44
44
 
45
- def __new__(cls, scope, fn, static_fn, auxtype=None):
46
- fqname = "%s.%s" % (scope.fqname, fn.__name__)
45
+ def __new__(cls, scope, fn_name, fn, static_fn, auxtype=None): # pylint: disable=too-many-arguments
46
+ fqname = "%s.%s" % (scope.fqname, fn_name)
47
47
  self = super(FilterableFn, cls).__new__(cls, scope.scope, fqname, fn, static_fn, auxtype)
48
48
  return self
49
49
 
@@ -132,7 +132,9 @@ def get_members(cls):
132
132
  if key.startswith("__"):
133
133
  continue
134
134
  static_value = inspect.getattr_static(cls, key)
135
- if isinstance(static_value, property):
135
+ # Don't use isinstance to check the type of static_value -- we don't want to invoke the
136
+ # descriptor protocol.
137
+ if Importer.instrument_properties and type(static_value) is property: # pylint: disable=unidiomatic-typecheck
136
138
  properties[key] = (
137
139
  static_value,
138
140
  {
@@ -164,6 +166,9 @@ class Importer:
164
166
  cls.filter_stack = []
165
167
  cls.filter_chain = []
166
168
  cls._skip_instrumenting = ("appmap", "_appmap")
169
+ cls.instrument_properties = (
170
+ Env.current.get("APPMAP_INSTRUMENT_PROPERTIES", "false").lower() == "true"
171
+ )
167
172
 
168
173
  @classmethod
169
174
  def use_filter(cls, filter_class):
@@ -191,27 +196,34 @@ class Importer:
191
196
  cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
192
197
 
193
198
  def instrument_functions(filterable, selected_functions=None):
199
+ # pylint: disable=too-many-locals
194
200
  logger.trace(" looking for members of %s", filterable.obj)
195
201
  functions, properties = get_members(filterable.obj)
196
202
  logger.trace(" functions %s", functions)
197
203
 
198
204
  for fn_name, static_fn, fn in functions:
199
- filterableFn = FilterableFn(filterable, fn, static_fn)
205
+ filterableFn = FilterableFn(filterable, fn.__name__, fn, static_fn)
200
206
  new_fn = cls.instrument_function(fn_name, filterableFn, selected_functions)
201
207
  if new_fn != fn:
202
- wrapt.wrap_function_wrapper(filterable.obj, fn_name, new_fn)
208
+ fw = wrapt.wrap_function_wrapper(filterable.obj, fn_name, new_fn)
209
+ fw._appmap_instrumented = True # pylint: disable=protected-access
210
+
203
211
  # Now that we've instrumented all the functions, go through the properties and update
204
212
  # them
205
213
  for prop_name, (prop, prop_fns) in properties.items():
206
214
  instrumented_fns = {}
207
215
  for k, (fn, auxtype) in prop_fns.items():
208
- if fn is None or getattr(fn, "_appmap_wrapped", None):
216
+ if fn is None:
209
217
  continue
210
- filterableFn = FilterableFn(filterable, fn, fn, auxtype)
211
- new_fn = cls.instrument_function(fn.__name__, filterableFn, selected_functions)
218
+ filterableFn = FilterableFn(filterable, prop_name, fn, fn, auxtype)
219
+ if getattr(fn, "_appmap_instrumented", None):
220
+ continue
221
+ new_fn = cls.instrument_function(prop_name, filterableFn, selected_functions)
212
222
  if new_fn != fn:
213
223
  new_fn = wrapt.FunctionWrapper(fn, new_fn)
214
- setattr(new_fn, "_appmap_wrapped", True)
224
+ # Set _appmap_instrumented on the FunctionWrapper, not on the wrapped function
225
+ new_fn._appmap_instrumented = True # pylint: disable=protected-access
226
+
215
227
  instrumented_fns[k] = new_fn
216
228
  if len(instrumented_fns) > 0:
217
229
  instrumented_fns["doc"] = prop.__doc__
@@ -138,5 +138,5 @@ def instrument(filterable):
138
138
  return call_instrumented(f, instance, args, kwargs)
139
139
 
140
140
  ret = instrumented_fn
141
- setattr(ret, "_appmap_wrapped", True)
141
+ setattr(ret, "_appmap_instrumented", True)
142
142
  return ret
@@ -1,3 +1,4 @@
1
1
  name: Simple
2
+ record_test_cases: false
2
3
  packages:
3
4
  - path: simple
@@ -0,0 +1,5 @@
1
+ name: Simple
2
+ record_test_cases: true
3
+ packages:
4
+ - path: simple
5
+ - path: tests
@@ -13,7 +13,7 @@
13
13
  "name": "pytest",
14
14
  "type": "tests"
15
15
  },
16
- "source_location": "test_simple.py:5",
16
+ "source_location": "tests/test_simple.py:5",
17
17
  "name": "hello world",
18
18
  "feature": "Hello world",
19
19
  "test_status": "succeeded"
@@ -0,0 +1,281 @@
1
+ {
2
+ "version": "1.9",
3
+ "metadata": {
4
+ "language": {
5
+ "name": "python"
6
+ },
7
+ "client": {
8
+ "name": "appmap",
9
+ "url": "https://github.com/applandinc/appmap-python"
10
+ },
11
+ "source_location": "tests/test_simple.py:5",
12
+ "name": "hello world",
13
+ "feature": "Hello world",
14
+ "app": "Simple",
15
+ "recorder": {
16
+ "name": "pytest",
17
+ "type": "tests"
18
+ },
19
+ "test_status": "succeeded"
20
+ },
21
+ "events": [
22
+ {
23
+ "static": true,
24
+ "parameters": [],
25
+ "id": 1,
26
+ "event": "call",
27
+ "thread_id": 1,
28
+ "defined_class": "tests.test_simple",
29
+ "method_id": "test_hello_world",
30
+ "path": "tests/test_simple.py",
31
+ "lineno": 6
32
+ },
33
+ {
34
+ "static": false,
35
+ "receiver": {
36
+ "kind": "req",
37
+ "value": "<simple.Simple object at 0xabcdef>",
38
+ "name": "self",
39
+ "class": "simple.Simple"
40
+ },
41
+ "parameters": [],
42
+ "id": 2,
43
+ "event": "call",
44
+ "thread_id": 1,
45
+ "defined_class": "simple.Simple",
46
+ "method_id": "hello_world",
47
+ "path": "simple.py",
48
+ "lineno": 8
49
+ },
50
+ {
51
+ "static": false,
52
+ "receiver": {
53
+ "kind": "req",
54
+ "value": "<simple.Simple object at 0xabcdef>",
55
+ "name": "self",
56
+ "class": "simple.Simple"
57
+ },
58
+ "parameters": [],
59
+ "id": 3,
60
+ "event": "call",
61
+ "thread_id": 1,
62
+ "defined_class": "simple.Simple",
63
+ "method_id": "hello",
64
+ "path": "simple.py",
65
+ "lineno": 2
66
+ },
67
+ {
68
+ "return_value": {
69
+ "value": "'Hello'",
70
+ "class": "builtins.str"
71
+ },
72
+ "parent_id": 3,
73
+ "id": 4,
74
+ "event": "return",
75
+ "thread_id": 1
76
+ },
77
+ {
78
+ "static": false,
79
+ "receiver": {
80
+ "kind": "req",
81
+ "value": "<simple.Simple object at 0xabcdef>",
82
+ "name": "self",
83
+ "class": "simple.Simple"
84
+ },
85
+ "parameters": [],
86
+ "id": 5,
87
+ "event": "call",
88
+ "thread_id": 1,
89
+ "defined_class": "simple.Simple",
90
+ "method_id": "world",
91
+ "path": "simple.py",
92
+ "lineno": 5
93
+ },
94
+ {
95
+ "return_value": {
96
+ "value": "'world!'",
97
+ "class": "builtins.str"
98
+ },
99
+ "parent_id": 5,
100
+ "id": 6,
101
+ "event": "return",
102
+ "thread_id": 1
103
+ },
104
+ {
105
+ "return_value": {
106
+ "value": "'Hello world!'",
107
+ "class": "builtins.str"
108
+ },
109
+ "parent_id": 2,
110
+ "id": 7,
111
+ "event": "return",
112
+ "thread_id": 1
113
+ },
114
+ {
115
+ "static": false,
116
+ "receiver": {
117
+ "kind": "req",
118
+ "value": "<simple.Simple object at 0xabcdef>",
119
+ "name": "self",
120
+ "class": "simple.Simple"
121
+ },
122
+ "parameters": [],
123
+ "id": 8,
124
+ "event": "call",
125
+ "thread_id": 1,
126
+ "defined_class": "simple.Simple",
127
+ "method_id": "show_numpy_dict",
128
+ "path": "simple.py",
129
+ "lineno": 11
130
+ },
131
+ {
132
+ "static": false,
133
+ "receiver": {
134
+ "kind": "req",
135
+ "value": "<simple.Simple object at 0xabcdef>",
136
+ "name": "self",
137
+ "class": "simple.Simple"
138
+ },
139
+ "parameters": [
140
+ {
141
+ "kind": "req",
142
+ "value": "{0: 'zero', 1: 'one'}",
143
+ "name": "d",
144
+ "class": "builtins.dict",
145
+ "properties": [
146
+ {
147
+ "name": "0",
148
+ "class": "builtins.str"
149
+ },
150
+ {
151
+ "name": "1",
152
+ "class": "builtins.str"
153
+ }
154
+ ],
155
+ "size": 2
156
+ }
157
+ ],
158
+ "id": 9,
159
+ "event": "call",
160
+ "thread_id": 1,
161
+ "defined_class": "simple.Simple",
162
+ "method_id": "get_numpy_dict",
163
+ "path": "simple.py",
164
+ "lineno": 18
165
+ },
166
+ {
167
+ "return_value": {
168
+ "value": "{0: 'zero', 1: 'one'}",
169
+ "class": "builtins.dict",
170
+ "properties": [
171
+ {
172
+ "name": "0",
173
+ "class": "builtins.str"
174
+ },
175
+ {
176
+ "name": "1",
177
+ "class": "builtins.str"
178
+ }
179
+ ],
180
+ "size": 2
181
+ },
182
+ "parent_id": 9,
183
+ "id": 10,
184
+ "event": "return",
185
+ "thread_id": 1
186
+ },
187
+ {
188
+ "return_value": {
189
+ "value": "{0: 'zero', 1: 'one'}",
190
+ "class": "builtins.dict",
191
+ "properties": [
192
+ {
193
+ "name": "0",
194
+ "class": "builtins.str"
195
+ },
196
+ {
197
+ "name": "1",
198
+ "class": "builtins.str"
199
+ }
200
+ ],
201
+ "size": 2
202
+ },
203
+ "parent_id": 8,
204
+ "id": 11,
205
+ "event": "return",
206
+ "thread_id": 1
207
+ },
208
+ {
209
+ "return_value": {
210
+ "value": "None",
211
+ "class": "builtins.NoneType"
212
+ },
213
+ "parent_id": 1,
214
+ "id": 12,
215
+ "event": "return",
216
+ "thread_id": 1
217
+ }
218
+ ],
219
+ "classMap": [
220
+ {
221
+ "name": "simple",
222
+ "type": "package",
223
+ "children": [
224
+ {
225
+ "name": "Simple",
226
+ "type": "class",
227
+ "children": [
228
+ {
229
+ "name": "get_numpy_dict",
230
+ "type": "function",
231
+ "location": "simple.py:18",
232
+ "static": false
233
+ },
234
+ {
235
+ "name": "hello",
236
+ "type": "function",
237
+ "location": "simple.py:2",
238
+ "static": false
239
+ },
240
+ {
241
+ "name": "hello_world",
242
+ "type": "function",
243
+ "location": "simple.py:8",
244
+ "static": false
245
+ },
246
+ {
247
+ "name": "show_numpy_dict",
248
+ "type": "function",
249
+ "location": "simple.py:11",
250
+ "static": false
251
+ },
252
+ {
253
+ "name": "world",
254
+ "type": "function",
255
+ "location": "simple.py:5",
256
+ "static": false
257
+ }
258
+ ]
259
+ }
260
+ ]
261
+ },
262
+ {
263
+ "name": "tests",
264
+ "type": "package",
265
+ "children": [
266
+ {
267
+ "name": "test_simple",
268
+ "type": "class",
269
+ "children": [
270
+ {
271
+ "name": "test_hello_world",
272
+ "type": "function",
273
+ "location": "tests/test_simple.py:6",
274
+ "static": true
275
+ }
276
+ ]
277
+ }
278
+ ]
279
+ }
280
+ ]
281
+ }
@@ -13,7 +13,7 @@
13
13
  "name": "pytest",
14
14
  "type": "tests"
15
15
  },
16
- "source_location": "test_simple.py:5",
16
+ "source_location": "tests/test_simple.py:5",
17
17
  "name": "hello world",
18
18
  "feature": "Hello world",
19
19
  "test_status": "succeeded"