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.
- {appmap-1.15.2 → appmap-1.16.0}/PKG-INFO +14 -5
- {appmap-1.15.2 → appmap-1.16.0}/README.md +12 -3
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/importer.py +21 -9
- 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
- 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
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/simple/test_simple.py +0 -1
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_labels.py +1 -1
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_params.py +1 -1
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_recording.py +1 -1
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_test_frameworks.py +6 -0
- appmap-1.16.0/_appmap/unittest.py +69 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/flask.py +1 -1
- {appmap-1.15.2 → appmap-1.16.0}/appmap/pytest.py +1 -2
- appmap-1.16.0/appmap/unittest.py +7 -0
- {appmap-1.15.2 → appmap-1.16.0}/pyproject.toml +18 -6
- appmap-1.16.0/vendor/_appmap/wrapt/LICENSE +24 -0
- {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/__init__.py +12 -1
- appmap-1.16.0/vendor/_appmap/wrapt/arguments.py +38 -0
- {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/decorators.py +34 -9
- {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/importer.py +120 -57
- {appmap-1.15.2/vendor/wrapt/src/appmap → appmap-1.16.0/vendor/_appmap}/wrapt/wrappers.py +84 -15
- appmap-1.15.2/appmap/unittest.py +0 -60
- appmap-1.15.2/vendor/wrapt/src/appmap/wrapt/_wrappers.c +0 -3087
- {appmap-1.15.2 → appmap-1.16.0}/LICENSE +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/configuration.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/django.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/env.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/event.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/flask.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/generation.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/instrument.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/labels.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/metadata.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/py_version_check.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/recorder.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/recording.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/remote_recording.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/appmap_test_base.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/conftest.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-broken.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-class.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap-func.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap_testing/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/src/package/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/test/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config/test/foo.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/app.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/hello_world.html +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/middleware.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/settings.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/settings_dev.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/app/urls.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/init/sitecustomize.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/manage.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/pytest.ini +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/test/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/django/test/test_app.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/example_class.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/expected.appmap.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/app.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/templates/test.html +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/flask/test_app.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/package2/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/package1/package2/mod1.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/params.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/expected/pytest.appmap.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/simple.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/pytest/test_simple.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/remote.appmap.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/.gitignore +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/test/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/trial/test/test_deferred.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/.gitignore +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/appmap.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/data/unittest/simple/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/helpers.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/normalize.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_command.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_configuration.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_describe_value.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_django.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_django_simplelazyobject.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_env.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_events.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_flask.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_generation.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_http.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_importer.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_metadata.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_sqlalchemy.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/test_util.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/test/web_framework.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/testing_framework.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/utils.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/_appmap/web_framework.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/command/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_init.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_status.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/command/appmap_agent_validate.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/django.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/http.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/__init__.py +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/crypto.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/django.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/flask.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/formats.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/http.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/jobs.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/jwt.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/logger.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/labeling/random.yml +0 -0
- {appmap-1.15.2 → appmap-1.16.0}/appmap/sqlalchemy.py +0 -0
- {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.
|
|
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 (>=
|
|
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
|
|
118
|
-
this easier to achieve,
|
|
119
|
-
|
|
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
|
|
87
|
-
this easier to achieve,
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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:
|
|
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": "
|
|
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":
|
|
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:
|
|
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": "
|
|
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":
|
|
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:
|
|
181
|
+
"location": "simple/test_simple.py:13",
|
|
182
182
|
"static": false
|
|
183
183
|
}
|
|
184
184
|
]
|
|
@@ -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
|
|
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
|
|
@@ -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.
|
|
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 = ">=
|
|
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
|
-
|
|
|
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
|
-
"
|
|
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', '
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|