appmap 1.15.2__tar.gz → 1.16.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 (137) hide show
  1. {appmap-1.15.2 → appmap-1.16.0}/PKG-INFO +14 -5
  2. {appmap-1.15.2 → appmap-1.16.0}/README.md +12 -3
  3. {appmap-1.15.2 → appmap-1.16.0}/_appmap/importer.py +21 -9
  4. appmap-1.15.2/_appmap/test/data/unittest/expected/unittest.appmap.json → appmap-1.16.0/_appmap/test/data/unittest/expected/pytest.appmap.json +4 -4
  5. appmap-1.15.2/_appmap/test/data/unittest/expected/pytest.appmap.json → appmap-1.16.0/_appmap/test/data/unittest/expected/unittest.appmap.json +3 -3
  6. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/simple/test_simple.py +0 -1
  7. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_labels.py +1 -1
  8. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_params.py +1 -1
  9. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_recording.py +1 -1
  10. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_test_frameworks.py +6 -0
  11. appmap-1.16.0/_appmap/unittest.py +69 -0
  12. {appmap-1.15.2 → appmap-1.16.0}/appmap/flask.py +1 -1
  13. {appmap-1.15.2 → appmap-1.16.0}/appmap/pytest.py +1 -2
  14. appmap-1.16.0/appmap/unittest.py +7 -0
  15. {appmap-1.15.2 → appmap-1.16.0}/pyproject.toml +18 -6
  16. appmap-1.16.0/vendor/_appmap/wrapt/LICENSE +24 -0
  17. {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/__init__.py +12 -1
  18. appmap-1.16.0/vendor/_appmap/wrapt/arguments.py +38 -0
  19. {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/decorators.py +34 -9
  20. {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/importer.py +120 -57
  21. {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/wrappers.py +84 -15
  22. appmap-1.15.2/appmap/unittest.py +0 -60
  23. appmap-1.15.2/vendor/wrapt/src/appmap/wrapt/_wrappers.c +0 -3087
  24. {appmap-1.15.2 → appmap-1.16.0}/LICENSE +0 -0
  25. {appmap-1.15.2 → appmap-1.16.0}/_appmap/__init__.py +0 -0
  26. {appmap-1.15.2 → appmap-1.16.0}/_appmap/configuration.py +0 -0
  27. {appmap-1.15.2 → appmap-1.16.0}/_appmap/django.py +0 -0
  28. {appmap-1.15.2 → appmap-1.16.0}/_appmap/env.py +0 -0
  29. {appmap-1.15.2 → appmap-1.16.0}/_appmap/event.py +0 -0
  30. {appmap-1.15.2 → appmap-1.16.0}/_appmap/flask.py +0 -0
  31. {appmap-1.15.2 → appmap-1.16.0}/_appmap/generation.py +0 -0
  32. {appmap-1.15.2 → appmap-1.16.0}/_appmap/instrument.py +0 -0
  33. {appmap-1.15.2 → appmap-1.16.0}/_appmap/labels.py +0 -0
  34. {appmap-1.15.2 → appmap-1.16.0}/_appmap/metadata.py +0 -0
  35. {appmap-1.15.2 → appmap-1.16.0}/_appmap/py_version_check.py +0 -0
  36. {appmap-1.15.2 → appmap-1.16.0}/_appmap/recorder.py +0 -0
  37. {appmap-1.15.2 → appmap-1.16.0}/_appmap/recording.py +0 -0
  38. {appmap-1.15.2 → appmap-1.16.0}/_appmap/remote_recording.py +0 -0
  39. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/__init__.py +0 -0
  40. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/appmap_test_base.py +0 -0
  41. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/conftest.py +0 -0
  42. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-broken.yml +0 -0
  43. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-class.yml +0 -0
  44. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-func.yml +0 -0
  45. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap.yml +0 -0
  46. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap_testing/__init__.py +0 -0
  47. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
  48. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/src/package/__init__.py +0 -0
  49. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/test/__init__.py +0 -0
  50. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/test/foo.py +0 -0
  51. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
  52. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
  53. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
  54. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
  55. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
  56. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/__init__.py +0 -0
  57. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/app.py +0 -0
  58. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/hello_world.html +0 -0
  59. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/middleware.py +0 -0
  60. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/settings.py +0 -0
  61. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/settings_dev.py +0 -0
  62. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/urls.py +0 -0
  63. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/appmap.yml +0 -0
  64. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/init/sitecustomize.py +0 -0
  65. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/manage.py +0 -0
  66. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/pytest.ini +0 -0
  67. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/test/__init__.py +0 -0
  68. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/test/test_app.py +0 -0
  69. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/example_class.py +0 -0
  70. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/expected.appmap.json +0 -0
  71. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/app.py +0 -0
  72. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/appmap.yml +0 -0
  73. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
  74. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/templates/test.html +0 -0
  75. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/test_app.py +0 -0
  76. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/__init__.py +0 -0
  77. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/package2/__init__.py +0 -0
  78. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/package2/mod1.py +0 -0
  79. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/params.py +0 -0
  80. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/appmap.yml +0 -0
  81. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/expected/pytest.appmap.json +0 -0
  82. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
  83. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/simple.py +0 -0
  84. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/test_simple.py +0 -0
  85. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/remote.appmap.json +0 -0
  86. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/.gitignore +0 -0
  87. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/appmap.yml +0 -0
  88. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
  89. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
  90. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/test/__init__.py +0 -0
  91. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/test/test_deferred.py +0 -0
  92. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/.gitignore +0 -0
  93. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/appmap.yml +0 -0
  94. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
  95. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
  96. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
  97. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
  98. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/simple/__init__.py +0 -0
  99. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/helpers.py +0 -0
  100. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/normalize.py +0 -0
  101. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_command.py +0 -0
  102. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_configuration.py +0 -0
  103. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_describe_value.py +0 -0
  104. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_django.py +0 -0
  105. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_django_simplelazyobject.py +0 -0
  106. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_env.py +0 -0
  107. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_events.py +0 -0
  108. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_flask.py +0 -0
  109. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_generation.py +0 -0
  110. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_http.py +0 -0
  111. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_importer.py +0 -0
  112. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_metadata.py +0 -0
  113. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_sqlalchemy.py +0 -0
  114. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_util.py +0 -0
  115. {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/web_framework.py +0 -0
  116. {appmap-1.15.2 → appmap-1.16.0}/_appmap/testing_framework.py +0 -0
  117. {appmap-1.15.2 → appmap-1.16.0}/_appmap/utils.py +0 -0
  118. {appmap-1.15.2 → appmap-1.16.0}/_appmap/web_framework.py +0 -0
  119. {appmap-1.15.2 → appmap-1.16.0}/appmap/__init__.py +0 -0
  120. {appmap-1.15.2 → appmap-1.16.0}/appmap/command/__init__.py +0 -0
  121. {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_init.py +0 -0
  122. {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_status.py +0 -0
  123. {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_validate.py +0 -0
  124. {appmap-1.15.2 → appmap-1.16.0}/appmap/django.py +0 -0
  125. {appmap-1.15.2 → appmap-1.16.0}/appmap/http.py +0 -0
  126. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/__init__.py +0 -0
  127. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/crypto.yml +0 -0
  128. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/django.yml +0 -0
  129. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/flask.yml +0 -0
  130. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/formats.yml +0 -0
  131. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/http.yml +0 -0
  132. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/jobs.yml +0 -0
  133. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/jwt.yml +0 -0
  134. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/logger.yml +0 -0
  135. {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/random.yml +0 -0
  136. {appmap-1.15.2 → appmap-1.16.0}/appmap/sqlalchemy.py +0 -0
  137. {appmap-1.15.2 → appmap-1.16.0}/appmap.pth +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: appmap
3
- Version: 1.15.2
3
+ Version: 1.16.0
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
@@ -26,7 +26,7 @@ Requires-Dist: PyYAML (>=5.3.0)
26
26
  Requires-Dist: importlib-metadata (>=0.8)
27
27
  Requires-Dist: importlib-resources (>=5.4.0,<6.0.0)
28
28
  Requires-Dist: inflection (>=0.3.0)
29
- Requires-Dist: packaging (>=21.3)
29
+ Requires-Dist: packaging (>=19.0)
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  - [About](#about)
@@ -35,6 +35,7 @@ Description-Content-Type: text/markdown
35
35
  - [Getting the code](#getting-the-code)
36
36
  - [Python version support](#python-version-support)
37
37
  - [Dependency management](#dependency-management)
38
+ - [wrapt](#wrapt)
38
39
  - [Linting](#linting)
39
40
  - [Testing](#testing)
40
41
  - [pytest](#pytest)
@@ -102,6 +103,14 @@ oldest version currently supported (see the
102
103
  % poetry install
103
104
  ```
104
105
 
106
+ ### wrapt
107
+ The one dependency that is not managed using `poetry` is `wrapt`. Because it's possible that
108
+ projects that use `appmap` may also need an unmodified version of `wrapt` (e.g. `pylint` depends on
109
+ `astroid`, which in turn depends on `wrapt`), we use
110
+ [vendoring](https://github.com/pradyunsg/vendoring) to vendor `wrapt`.
111
+
112
+ To update `wrapt`, use `tox` (described below) to run the `vendoring` environment.
113
+
105
114
  ## Linting
106
115
  [pylint](https://www.pylint.org/) for linting:
107
116
 
@@ -114,9 +123,9 @@ Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
114
123
 
115
124
  ```
116
125
 
117
- [Note that the current configuration requires a 10.0 for the Travis build to pass. To make
118
- this easier to achieve, convention and refactoring checks have both been disabled. They
119
- should be reenabled as soon as possible.]
126
+ [Note that the current configuration has a threshold set which must be met for the Travis build to
127
+ pass. To make this easier to achieve, a number of checks have both been disabled. They should be
128
+ reenabled as soon as possible.]
120
129
 
121
130
 
122
131
  ## Testing
@@ -4,6 +4,7 @@
4
4
  - [Getting the code](#getting-the-code)
5
5
  - [Python version support](#python-version-support)
6
6
  - [Dependency management](#dependency-management)
7
+ - [wrapt](#wrapt)
7
8
  - [Linting](#linting)
8
9
  - [Testing](#testing)
9
10
  - [pytest](#pytest)
@@ -71,6 +72,14 @@ oldest version currently supported (see the
71
72
  % poetry install
72
73
  ```
73
74
 
75
+ ### wrapt
76
+ The one dependency that is not managed using `poetry` is `wrapt`. Because it's possible that
77
+ projects that use `appmap` may also need an unmodified version of `wrapt` (e.g. `pylint` depends on
78
+ `astroid`, which in turn depends on `wrapt`), we use
79
+ [vendoring](https://github.com/pradyunsg/vendoring) to vendor `wrapt`.
80
+
81
+ To update `wrapt`, use `tox` (described below) to run the `vendoring` environment.
82
+
74
83
  ## Linting
75
84
  [pylint](https://www.pylint.org/) for linting:
76
85
 
@@ -83,9 +92,9 @@ Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
83
92
 
84
93
  ```
85
94
 
86
- [Note that the current configuration requires a 10.0 for the Travis build to pass. To make
87
- this easier to achieve, convention and refactoring checks have both been disabled. They
88
- should be reenabled as soon as possible.]
95
+ [Note that the current configuration has a threshold set which must be met for the Travis build to
96
+ pass. To make this easier to achieve, a number of checks have both been disabled. They should be
97
+ reenabled as soon as possible.]
89
98
 
90
99
 
91
100
  ## Testing
@@ -7,7 +7,7 @@ from collections import namedtuple
7
7
  from collections.abc import MutableSequence
8
8
  from functools import reduce
9
9
 
10
- from appmap import wrapt
10
+ from _appmap import wrapt
11
11
 
12
12
  from .env import Env
13
13
  from .utils import FnType
@@ -165,9 +165,7 @@ class Importer:
165
165
 
166
166
  logger.debug("do_import, mod %s args %s kwargs %s", mod, args, kwargs)
167
167
  if not cls.filter_chain:
168
- cls.filter_chain = reduce(
169
- lambda acc, e: e(acc), cls.filter_stack, NullFilter(None)
170
- )
168
+ cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
171
169
 
172
170
  def instrument_functions(filterable, selected_functions=None):
173
171
  logger.debug(" looking for members of %s", filterable.obj)
@@ -264,12 +262,26 @@ def wrapped_find_spec(find_spec, _, args, kwargs):
264
262
 
265
263
 
266
264
  def wrap_finder_find_spec(finder):
267
- find_spec = getattr(finder, "find_spec", None)
268
- if find_spec is None:
269
- logger.debug("no find_spec for finder %r", finder)
270
- return
265
+ # Prior to 3.11, it worked fine to just grab find_spec from the finder and wrap it. The
266
+ # implementation of builtin finders must have changed with 3.11, because we now need the same
267
+ # kind of workaround we use above for exec_module.
268
+ if sys.version_info[1] < 11:
269
+ find_spec = getattr(finder, "find_spec", None)
270
+ if find_spec is None:
271
+ logger.debug("no find_spec for finder %r", finder)
272
+ return
271
273
 
272
- finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
274
+ finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
275
+ else:
276
+ find_spec = inspect.getattr_static(finder, "find_spec", None)
277
+ if find_spec is None:
278
+ logger.debug("no find_spec for finder %r", finder)
279
+ return
280
+
281
+ if isinstance(find_spec, (classmethod, staticmethod)):
282
+ finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
283
+ else:
284
+ finder.find_spec = wrap_finder_function(finder.find_spec, wrapped_find_spec)
273
285
 
274
286
 
275
287
  class MetapathObserver(MutableSequence):
@@ -12,13 +12,13 @@
12
12
  "recording": {
13
13
  "defined_class": "simple.test_simple.UnitTestTest",
14
14
  "method_id": "test_hello_world",
15
- "source_location": "simple/test_simple.py:14"
15
+ "source_location": "simple/test_simple.py:12"
16
16
  },
17
17
  "name": "Unit test test hello world",
18
18
  "feature": "Hello world",
19
19
  "app": "Simple",
20
20
  "recorder": {
21
- "name": "unittest",
21
+ "name": "pytest",
22
22
  "type": "tests"
23
23
  },
24
24
  "test_status": "succeeded"
@@ -28,7 +28,7 @@
28
28
  "defined_class": "simple.test_simple.UnitTestTest",
29
29
  "method_id": "test_hello_world",
30
30
  "path": "simple/test_simple.py",
31
- "lineno": 14,
31
+ "lineno": 13,
32
32
  "static": false,
33
33
  "receiver": {
34
34
  "name": "self",
@@ -178,7 +178,7 @@
178
178
  {
179
179
  "name": "test_hello_world",
180
180
  "type": "function",
181
- "location": "simple/test_simple.py:14",
181
+ "location": "simple/test_simple.py:13",
182
182
  "static": false
183
183
  }
184
184
  ]
@@ -18,7 +18,7 @@
18
18
  "feature": "Hello world",
19
19
  "app": "Simple",
20
20
  "recorder": {
21
- "name": "pytest",
21
+ "name": "unittest",
22
22
  "type": "tests"
23
23
  },
24
24
  "test_status": "succeeded"
@@ -28,7 +28,7 @@
28
28
  "defined_class": "simple.test_simple.UnitTestTest",
29
29
  "method_id": "test_hello_world",
30
30
  "path": "simple/test_simple.py",
31
- "lineno": 14,
31
+ "lineno": 13,
32
32
  "static": false,
33
33
  "receiver": {
34
34
  "name": "self",
@@ -178,7 +178,7 @@
178
178
  {
179
179
  "name": "test_hello_world",
180
180
  "type": "function",
181
- "location": "simple/test_simple.py:14",
181
+ "location": "simple/test_simple.py:13",
182
182
  "static": false
183
183
  }
184
184
  ]
@@ -7,7 +7,6 @@ import simple
7
7
  # finders correctly.
8
8
  from decouple import config
9
9
 
10
- import appmap.unittest
11
10
 
12
11
 
13
12
  class UnitTestTest(unittest.TestCase):
@@ -1,6 +1,6 @@
1
1
  import pytest
2
2
 
3
- from appmap.wrapt import BoundFunctionWrapper, FunctionWrapper
3
+ from _appmap.wrapt import BoundFunctionWrapper, FunctionWrapper
4
4
 
5
5
 
6
6
  @pytest.mark.appmap_enabled
@@ -7,9 +7,9 @@ import sys
7
7
 
8
8
  import pytest
9
9
 
10
+ from _appmap import wrapt
10
11
  from _appmap.event import CallEvent
11
12
  from _appmap.importer import FilterableCls, FilterableFn
12
- from appmap import wrapt
13
13
 
14
14
  empty_args = {"name": "args", "class": "builtins.tuple", "kind": "rest", "value": "()"}
15
15
 
@@ -10,7 +10,7 @@ import pytest
10
10
  import appmap
11
11
  from _appmap.event import Event
12
12
  from _appmap.recorder import Recorder, ThreadRecorder
13
- from appmap.wrapt import FunctionWrapper
13
+ from _appmap.wrapt import FunctionWrapper
14
14
 
15
15
  from .normalize import normalize_appmap, remove_line_numbers
16
16
 
@@ -136,6 +136,12 @@ def test_write_appmap(tmp_path):
136
136
 
137
137
  @pytest.fixture(name="testdir")
138
138
  def fixture_runner_testdir(request, data_dir, pytester, monkeypatch):
139
+ # We need to set environment variables to control how tests are run. This will only work
140
+ # properly if pytester runs pytest in a subprocess.
141
+ assert (
142
+ pytester._method == "subprocess" # pylint:disable=protected-access
143
+ ), "must run pytest in a subprocess"
144
+
139
145
  # The init subdirectory contains a sitecustomize.py file that
140
146
  # imports the appmap module. This simulates the way a real
141
147
  # installation works, performing the same function as the the
@@ -0,0 +1,69 @@
1
+ import sys
2
+ import unittest
3
+ from contextlib import contextmanager
4
+
5
+ from _appmap import testing_framework, wrapt
6
+ from _appmap.utils import get_function_location
7
+
8
+ _session = testing_framework.session("unittest", "tests")
9
+
10
+
11
+ def _get_test_location(cls, method_name):
12
+ fn = getattr(cls, method_name)
13
+ return get_function_location(fn)
14
+
15
+
16
+ if sys.version_info[1] < 8:
17
+ # Prior to 3.8, unittest called the test case's test method directly, which left us without an
18
+ # opportunity to hook it. So, instead, instrument unittest.case._Outcome.testPartExecutor, a
19
+ # method used to run test cases. `isTest` will be True when the part is the actual test method,
20
+ # False when it's setUp or teardown.
21
+ @wrapt.patch_function_wrapper("unittest.case", "_Outcome.testPartExecutor")
22
+ @contextmanager
23
+ def testPartExecutor(wrapped, _, args, kwargs):
24
+ def _args(test_case, *_, isTest=False, **__):
25
+ return (test_case, isTest)
26
+
27
+ test_case, is_test = _args(*args, **kwargs)
28
+ already_recording = getattr(test_case, "_appmap_pytest_recording", None)
29
+ # fmt: off
30
+ if (
31
+ (not is_test)
32
+ or isinstance(test_case, unittest.case._SubTest) # pylint: disable=protected-access
33
+ or already_recording
34
+ ):
35
+ # fmt: on
36
+ with wrapped(*args, **kwargs):
37
+ yield
38
+ return
39
+
40
+ method_name = test_case.id().split(".")[-1]
41
+ location = _get_test_location(test_case.__class__, method_name)
42
+ with _session.record(
43
+ test_case.__class__, method_name, location=location
44
+ ) as metadata:
45
+ if metadata:
46
+ with wrapped(
47
+ *args, **kwargs
48
+ ), testing_framework.collect_result_metadata(metadata):
49
+ yield
50
+ else:
51
+ # session.record may return None
52
+ yield
53
+
54
+ else:
55
+ # As of 3.8, unittest.case.TestCase now calls the test's method indirectly, through
56
+ # TestCase._callTestMethod. Hook that to manage a recording session.
57
+ @wrapt.patch_function_wrapper("unittest.case", "TestCase._callTestMethod")
58
+ def callTestMethod(wrapped, test_case, args, kwargs):
59
+ already_recording = getattr(test_case, "_appmap_pytest_recording", None)
60
+ if already_recording:
61
+ wrapped(*args, **kwargs)
62
+ return
63
+
64
+ method_name = test_case.id().split(".")[-1]
65
+ location = _get_test_location(test_case.__class__, method_name)
66
+ with _session.record(test_case.__class__, method_name, location=location) as metadata:
67
+ if metadata:
68
+ with testing_framework.collect_result_metadata(metadata):
69
+ wrapped(*args, **kwargs)
@@ -9,6 +9,7 @@ from flask.cli import ScriptInfo
9
9
  from werkzeug.exceptions import BadRequest, UnsupportedMediaType
10
10
  from werkzeug.middleware.dispatcher import DispatcherMiddleware
11
11
 
12
+ from _appmap import wrapt
12
13
  from _appmap.env import Env
13
14
  from _appmap.event import HttpServerRequestEvent, HttpServerResponseEvent
14
15
  from _appmap.flask import app as remote_recording_app
@@ -16,7 +17,6 @@ from _appmap.metadata import Metadata
16
17
  from _appmap.utils import patch_class, values_dict
17
18
  from _appmap.web_framework import JSON_ERRORS, AppmapMiddleware, MiddlewareInserter
18
19
  from _appmap.web_framework import TemplateHandler as BaseTemplateHandler
19
- from appmap import wrapt
20
20
 
21
21
  try:
22
22
  # pylint: disable=unused-import
@@ -1,8 +1,7 @@
1
1
  import pytest
2
2
 
3
- from _appmap import testing_framework
3
+ from _appmap import testing_framework, wrapt
4
4
  from _appmap.env import Env
5
- from appmap import wrapt
6
5
 
7
6
  logger = Env.current.getLogger(__name__)
8
7
 
@@ -0,0 +1,7 @@
1
+ from _appmap.env import Env
2
+
3
+ logger = Env.current.getLogger(__name__)
4
+
5
+ if not Env.current.is_appmap_repo and Env.current.enables("unittest"):
6
+ logger.debug("Test recording is enabled (unittest)")
7
+ import _appmap.unittest # pyright: ignore pylint: disable=unused-import
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "appmap"
3
- version = "1.15.2"
3
+ version = "1.16.0"
4
4
  description = "Create AppMap files by recording a Python application."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -22,10 +22,10 @@ classifiers = [
22
22
  'Topic :: Software Development :: Documentation'
23
23
  ]
24
24
  include = ['appmap.pth']
25
+ exclude = ['_appmap/wrapt']
25
26
 
26
27
  packages = [
27
- { include = "appmap"}, {include = "_appmap" },
28
- { include = "appmap/wrapt/*.py", from="vendor/wrapt/src" }
28
+ { include = "appmap"}, {include = "_appmap" }, {include = "_appmap/wrapt", from = "vendor"}
29
29
  ]
30
30
 
31
31
  [tool.poetry.dependencies]
@@ -36,7 +36,7 @@ PyYAML = ">=5.3.0"
36
36
  inflection = ">=0.3.0"
37
37
  importlib-metadata = ">=0.8"
38
38
  importlib-resources = "^5.4.0"
39
- packaging = ">=21.3"
39
+ packaging = ">=19.0"
40
40
  # If you include "Django" as an optional dependency here, you'll be able to use poetry to install it
41
41
  # in your dev environment. However, doing so causes poetry v1.2.0 to remove it from the virtualenv
42
42
  # *created and managed by tox*, i.e. not your dev environment.
@@ -87,7 +87,7 @@ line-length = 100
87
87
  extend-exclude = '''
88
88
  /(
89
89
  | vendor
90
- | appmap/wrapt
90
+ | _appmap/wrapt
91
91
  )/
92
92
  '''
93
93
 
@@ -95,6 +95,18 @@ extend-exclude = '''
95
95
  profile = "black"
96
96
  extend_skip = [
97
97
  "vendor",
98
- "appmap/wrapt"
98
+ "_appmap/wrapt"
99
99
  ]
100
100
 
101
+ [tool.vendoring]
102
+ destination = "vendor/_appmap/"
103
+ requirements = "vendor/vendor.txt"
104
+ namespace = ""
105
+
106
+ protected-files = ["vendor.txt"]
107
+ patches-dir = "vendor/patches"
108
+
109
+ [tool.vendoring.transformations]
110
+ drop = [
111
+ "**/*.so",
112
+ ]
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2013-2023, Graham Dumpleton
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ POSSIBILITY OF SUCH DAMAGE.
@@ -1,4 +1,4 @@
1
- __version_info__ = ('1', '12', '1')
1
+ __version_info__ = ('1', '15', '0')
2
2
  __version__ = '.'.join(__version_info__)
3
3
 
4
4
  from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
@@ -13,4 +13,15 @@ from .decorators import (adapter_factory, AdapterFactory, decorator,
13
13
  from .importer import (register_post_import_hook, when_imported,
14
14
  notify_module_loaded, discover_post_import_hooks)
15
15
 
16
+ # Import of inspect.getcallargs() included for backward compatibility. An
17
+ # implementation of this was previously bundled and made available here for
18
+ # Python <2.7. Avoid using this in future.
19
+
16
20
  from inspect import getcallargs
21
+
22
+ # Variant of inspect.formatargspec() included here for forward compatibility.
23
+ # This is being done because Python 3.11 dropped inspect.formatargspec() but
24
+ # code for handling signature changing decorators relied on it. Exposing the
25
+ # bundled implementation here in case any user of wrapt was also needing it.
26
+
27
+ from .arguments import formatargspec
@@ -0,0 +1,38 @@
1
+ # The inspect.formatargspec() function was dropped in Python 3.11 but we need
2
+ # need it for when constructing signature changing decorators based on result of
3
+ # inspect.getargspec() or inspect.getfullargspec(). The code here implements
4
+ # inspect.formatargspec() base on Parameter and Signature from inspect module,
5
+ # which were added in Python 3.6. Thanks to Cyril Jouve for the implementation.
6
+
7
+ try:
8
+ from inspect import Parameter, Signature
9
+ except ImportError:
10
+ from inspect import formatargspec
11
+ else:
12
+ def formatargspec(args, varargs=None, varkw=None, defaults=None,
13
+ kwonlyargs=(), kwonlydefaults={}, annotations={}):
14
+ if kwonlydefaults is None:
15
+ kwonlydefaults = {}
16
+ ndefaults = len(defaults) if defaults else 0
17
+ parameters = [
18
+ Parameter(
19
+ arg,
20
+ Parameter.POSITIONAL_OR_KEYWORD,
21
+ default=defaults[i] if i >= 0 else Parameter.empty,
22
+ annotation=annotations.get(arg, Parameter.empty),
23
+ ) for i, arg in enumerate(args, ndefaults - len(args))
24
+ ]
25
+ if varargs:
26
+ parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL))
27
+ parameters.extend(
28
+ Parameter(
29
+ kwonlyarg,
30
+ Parameter.KEYWORD_ONLY,
31
+ default=kwonlydefaults.get(kwonlyarg, Parameter.empty),
32
+ annotation=annotations.get(kwonlyarg, Parameter.empty),
33
+ ) for kwonlyarg in kwonlyargs
34
+ )
35
+ if varkw:
36
+ parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD))
37
+ return_annotation = annotations.get('return', Signature.empty)
38
+ return str(Signature(parameters, return_annotation=return_annotation))
@@ -31,10 +31,11 @@ else:
31
31
  del builtins
32
32
 
33
33
  from functools import partial
34
- from inspect import ismethod, isclass, formatargspec
35
- from collections import namedtuple
34
+ from inspect import isclass
36
35
  from threading import Lock, RLock
37
36
 
37
+ from .arguments import formatargspec
38
+
38
39
  try:
39
40
  from inspect import signature
40
41
  except ImportError:
@@ -173,7 +174,7 @@ adapter_factory = DelegatedAdapterFactory
173
174
  # function so the wrapper is effectively indistinguishable from the
174
175
  # original wrapped function.
175
176
 
176
- def decorator(wrapper=None, enabled=None, adapter=None):
177
+ def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper):
177
178
  # The decorator should be supplied with a single positional argument
178
179
  # which is the wrapper function to be used to implement the
179
180
  # decorator. This may be preceded by a step whereby the keyword
@@ -183,7 +184,7 @@ def decorator(wrapper=None, enabled=None, adapter=None):
183
184
  # decorator. In that case parts of the function '__code__' and
184
185
  # '__defaults__' attributes are used from the adapter function
185
186
  # rather than those of the wrapped function. This allows for the
186
- # argument specification from inspect.getargspec() and similar
187
+ # argument specification from inspect.getfullargspec() and similar
187
188
  # functions to be overridden with a prototype for a different
188
189
  # function than what was wrapped. The 'enabled' argument provides a
189
190
  # way to enable/disable the use of the decorator. If the type of
@@ -194,6 +195,8 @@ def decorator(wrapper=None, enabled=None, adapter=None):
194
195
  # if 'enabled' is callable it will be called to obtain the value to
195
196
  # be checked. If False, the wrapper will not be called and instead
196
197
  # the original wrapped function will be called directly instead.
198
+ # The 'proxy' argument provides a way of passing a custom version of
199
+ # the FunctionWrapper class used in decorating the function.
197
200
 
198
201
  if wrapper is not None:
199
202
  # Helper function for creating wrapper of the appropriate
@@ -206,16 +209,37 @@ def decorator(wrapper=None, enabled=None, adapter=None):
206
209
 
207
210
  if not callable(adapter):
208
211
  ns = {}
212
+
213
+ # Check if the signature argument specification has
214
+ # annotations. If it does then we need to remember
215
+ # it but also drop it when attempting to manufacture
216
+ # a standin adapter function. This is necessary else
217
+ # it will try and look up any types referenced in
218
+ # the annotations in the empty namespace we use,
219
+ # which will fail.
220
+
221
+ annotations = {}
222
+
209
223
  if not isinstance(adapter, string_types):
224
+ if len(adapter) == 7:
225
+ annotations = adapter[-1]
226
+ adapter = adapter[:-1]
210
227
  adapter = formatargspec(*adapter)
228
+
211
229
  exec_('def adapter{}: pass'.format(adapter), ns, ns)
212
230
  adapter = ns['adapter']
213
231
 
232
+ # Override the annotations for the manufactured
233
+ # adapter function so they match the original
234
+ # adapter signature argument specification.
235
+
236
+ if annotations:
237
+ adapter.__annotations__ = annotations
238
+
214
239
  return AdapterWrapper(wrapped=wrapped, wrapper=wrapper,
215
240
  enabled=enabled, adapter=adapter)
216
241
 
217
- return FunctionWrapper(wrapped=wrapped, wrapper=wrapper,
218
- enabled=enabled)
242
+ return proxy(wrapped=wrapped, wrapper=wrapper, enabled=enabled)
219
243
 
220
244
  # The wrapper has been provided so return the final decorator.
221
245
  # The decorator is itself one of our function wrappers so we
@@ -360,7 +384,7 @@ def decorator(wrapper=None, enabled=None, adapter=None):
360
384
  # This one is a bit strange because binding was actually
361
385
  # performed on the wrapper created by our decorator
362
386
  # factory. We need to apply that binding to the decorator
363
- # wrapper function which which the decorator factory
387
+ # wrapper function that the decorator factory
364
388
  # was applied to.
365
389
 
366
390
  target_wrapper = wrapper.__get__(None, instance)
@@ -384,7 +408,7 @@ def decorator(wrapper=None, enabled=None, adapter=None):
384
408
  # This one is a bit strange because binding was actually
385
409
  # performed on the wrapper created by our decorator
386
410
  # factory. We need to apply that binding to the decorator
387
- # wrapper function which which the decorator factory
411
+ # wrapper function that the decorator factory
388
412
  # was applied to.
389
413
 
390
414
  target_wrapper = wrapper.__get__(instance, type(instance))
@@ -408,7 +432,8 @@ def decorator(wrapper=None, enabled=None, adapter=None):
408
432
  # decorator again wrapped in a partial using the collected
409
433
  # arguments.
410
434
 
411
- return partial(decorator, enabled=enabled, adapter=adapter)
435
+ return partial(decorator, enabled=enabled, adapter=adapter,
436
+ proxy=proxy)
412
437
 
413
438
  # Decorator for implementing thread synchronization. It can be used as a
414
439
  # decorator, in which case the synchronization context is determined by