appmap 2.1.4__tar.gz → 2.1.6__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-2.1.4 → appmap-2.1.6}/PKG-INFO +1 -1
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/event.py +35 -4
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/importer.py +6 -5
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/instrument.py +1 -2
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/recording.py +1 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap.yml +1 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/settings.py +13 -0
- appmap-2.1.6/_appmap/test/data/django/test/test_request.py +8 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/example_class.py +0 -49
- appmap-2.1.6/_appmap/test/data/properties_class.py +71 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_django.py +3 -3
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_params.py +1 -2
- appmap-2.1.6/_appmap/test/test_properties.py +148 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_recording.py +38 -23
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_test_frameworks.py +14 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/utils.py +2 -7
- {appmap-2.1.4 → appmap-2.1.6}/appmap/fastapi.py +1 -1
- {appmap-2.1.4 → appmap-2.1.6}/appmap/pytest.py +8 -1
- {appmap-2.1.4 → appmap-2.1.6}/pyproject.toml +1 -1
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/wrappers.py +40 -21
- appmap-2.1.4/_appmap/test/test_properties.py +0 -102
- {appmap-2.1.4 → appmap-2.1.6}/LICENSE +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/README.md +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/configuration.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/django.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/env.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/fastapi.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/flask.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/generation.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/labels.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/metadata.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/noappmap.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/py_version_check.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/recorder.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/remote_recording.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/singleton.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/appmap_test_base.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/bin/server_runner +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/conftest.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-broken.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-class.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-empty-path.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-func.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-malformed-path.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap_testing/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config/src/package/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config/test/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config/test/foo.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-up/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/djangoapp/urls.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/manage.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/pytest.ini +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/test/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/test/test_app.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/expected.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/fastapi/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/fastapi/test_app.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask/flaskapp.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask/templates/test.html +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask/test_app.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask-instrumented/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask-instrumented/flaskapp.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask-instrumented/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/flask-instrumented/test_app.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/package1/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/package1/package2/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/package1/package2/__main__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/package1/package2/mod1.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/params.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/appmap-no-test-cases.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/pytest-numpy1-no-test-cases.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/pytest-numpy2-no-test-cases.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/simple.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/tests/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/tests/test_noappmap.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest/tests/test_simple.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest-instrumented/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest-instrumented/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/pytest-instrumented/test_instrumented.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/remote.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/.gitignore +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/appmap-no-test-cases.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/expected/pytest-no-test-cases.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/test/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/trial/test/test_deferred.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/appmap-no-test-cases.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/appmap.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/unittest-no-test-cases.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/simple/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/helpers.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/normalize.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_command.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_configuration.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_describe_value.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_django_simplelazyobject.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_env.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_events.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_fastapi.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_flask.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_generation.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_http.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_importer.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_labels.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_metadata.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_runner.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_sqlalchemy.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/test_util.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/test/web_framework.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/testing_framework.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/trace_logger.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/unittest.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/_appmap/web_framework.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/command/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/command/appmap_agent_init.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/command/appmap_agent_status.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/command/appmap_agent_validate.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/command/runner.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/django.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/flask.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/http.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/crypto.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/django.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/flask.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/formats.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/http.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/jobs.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/jwt.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/logger.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/labeling/random.yml +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/sqlalchemy.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/unittest.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap/uvicorn.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/appmap.pth +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/LICENSE +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/__init__.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/arguments.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/decorators.py +0 -0
- {appmap-2.1.4 → appmap-2.1.6}/vendor/_appmap/wrapt/importer.py +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
|
+
import operator
|
|
4
5
|
import threading
|
|
5
6
|
from functools import lru_cache, partial
|
|
6
7
|
from inspect import Parameter, Signature
|
|
@@ -174,24 +175,50 @@ class Param:
|
|
|
174
175
|
ret.update(describe_value(self.name, value))
|
|
175
176
|
return ret
|
|
176
177
|
|
|
178
|
+
def _get_name_parts(filterable):
|
|
179
|
+
"""
|
|
180
|
+
Return the module name and qualname for filterable.obj.
|
|
181
|
+
|
|
182
|
+
If filterable.obj is an operator.attrgetter that we've determined is associated with a property,
|
|
183
|
+
compute the names from the fqname of the filterable. If it's anything else, try to get its
|
|
184
|
+
__module__ and __qualname__, falling back to default values if they're not available.
|
|
185
|
+
"""
|
|
186
|
+
fn = filterable.obj
|
|
187
|
+
assert callable(fn), f"{filterable} doesn't have a callable obj"
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
type(fn) is operator.attrgetter
|
|
191
|
+
and (parts := filterable.fqname.split("."))
|
|
192
|
+
and len(parts) > 2
|
|
193
|
+
):
|
|
194
|
+
# filterable.fqname was set when we identified this filterable as a property
|
|
195
|
+
modname = ".".join(parts[:-2])
|
|
196
|
+
qualname = ".".join(parts[-2:])
|
|
197
|
+
else:
|
|
198
|
+
modname = getattr(fn, "__module__", "unknown")
|
|
199
|
+
qualname = getattr(fn, "__qualname__", None)
|
|
200
|
+
if qualname is None:
|
|
201
|
+
qualname = getattr(fn.__class__, "__name__", "unknown")
|
|
202
|
+
return modname, qualname
|
|
177
203
|
|
|
178
204
|
class CallEvent(Event):
|
|
179
205
|
# pylint: disable=method-cache-max-size-none
|
|
180
206
|
__slots__ = ["_fn", "_fqfn", "static", "receiver", "parameters", "labels", "auxtype"]
|
|
181
207
|
|
|
182
208
|
@staticmethod
|
|
183
|
-
def make(
|
|
209
|
+
def make(filterable):
|
|
184
210
|
"""
|
|
185
211
|
Return a factory for creating new CallEvents based on
|
|
186
212
|
introspecting the given function.
|
|
187
213
|
"""
|
|
188
214
|
|
|
189
215
|
# Delete the labels so the app doesn't see them.
|
|
216
|
+
fn = filterable.obj
|
|
190
217
|
labels = getattr(fn, "_appmap_labels", None)
|
|
191
218
|
if labels:
|
|
192
219
|
del fn._appmap_labels
|
|
193
220
|
|
|
194
|
-
return partial(CallEvent,
|
|
221
|
+
return partial(CallEvent, filterable, labels=labels)
|
|
195
222
|
|
|
196
223
|
@staticmethod
|
|
197
224
|
def make_params(filterable):
|
|
@@ -312,10 +339,14 @@ class CallEvent(Event):
|
|
|
312
339
|
comment = inspect.getcomments(self._fn)
|
|
313
340
|
return comment
|
|
314
341
|
|
|
315
|
-
def __init__(self,
|
|
342
|
+
def __init__(self, filterable, parameters, labels):
|
|
316
343
|
super().__init__("call")
|
|
344
|
+
fn = filterable.obj
|
|
317
345
|
self._fn = fn
|
|
318
|
-
|
|
346
|
+
modname, qualname = _get_name_parts(filterable)
|
|
347
|
+
self._fqfn = FqFnName(modname, qualname)
|
|
348
|
+
|
|
349
|
+
fntype = filterable.fntype
|
|
319
350
|
self.static = fntype in FnType.STATIC | FnType.CLASS | FnType.MODULE
|
|
320
351
|
self.receiver = None
|
|
321
352
|
if fntype in FnType.CLASS | FnType.INSTANCE:
|
|
@@ -134,7 +134,7 @@ def get_members(cls):
|
|
|
134
134
|
static_value = inspect.getattr_static(cls, key)
|
|
135
135
|
# Don't use isinstance to check the type of static_value -- we don't want to invoke the
|
|
136
136
|
# descriptor protocol.
|
|
137
|
-
if Importer.instrument_properties and type(static_value) is property:
|
|
137
|
+
if Importer.instrument_properties and type(static_value) is property:
|
|
138
138
|
properties[key] = (
|
|
139
139
|
static_value,
|
|
140
140
|
{
|
|
@@ -147,7 +147,7 @@ def get_members(cls):
|
|
|
147
147
|
if not is_member_func(static_value):
|
|
148
148
|
continue
|
|
149
149
|
value = getattr(cls, key)
|
|
150
|
-
if value
|
|
150
|
+
if (m := getattr(value, "__module__", None)) and (m is None or m != modname):
|
|
151
151
|
continue
|
|
152
152
|
functions.append((key, static_value, value))
|
|
153
153
|
|
|
@@ -167,7 +167,7 @@ class Importer:
|
|
|
167
167
|
cls.filter_chain = []
|
|
168
168
|
cls._skip_instrumenting = ("appmap", "_appmap")
|
|
169
169
|
cls.instrument_properties = (
|
|
170
|
-
Env.current.get("APPMAP_INSTRUMENT_PROPERTIES", "
|
|
170
|
+
Env.current.get("APPMAP_INSTRUMENT_PROPERTIES", "true").lower() == "true"
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
@classmethod
|
|
@@ -202,7 +202,7 @@ class Importer:
|
|
|
202
202
|
logger.trace(" functions %s", functions)
|
|
203
203
|
|
|
204
204
|
for fn_name, static_fn, fn in functions:
|
|
205
|
-
filterableFn = FilterableFn(filterable,
|
|
205
|
+
filterableFn = FilterableFn(filterable, fn_name, fn, static_fn)
|
|
206
206
|
new_fn = cls.instrument_function(fn_name, filterableFn, selected_functions)
|
|
207
207
|
if new_fn != fn:
|
|
208
208
|
fw = wrapt.wrap_function_wrapper(filterable.obj, fn_name, new_fn)
|
|
@@ -221,7 +221,8 @@ class Importer:
|
|
|
221
221
|
new_fn = cls.instrument_function(prop_name, filterableFn, selected_functions)
|
|
222
222
|
if new_fn != fn:
|
|
223
223
|
new_fn = wrapt.FunctionWrapper(fn, new_fn)
|
|
224
|
-
# Set _appmap_instrumented on the FunctionWrapper, not on the wrapped
|
|
224
|
+
# Set _appmap_instrumented on the FunctionWrapper, not on the wrapped
|
|
225
|
+
# function.
|
|
225
226
|
new_fn._appmap_instrumented = True # pylint: disable=protected-access
|
|
226
227
|
|
|
227
228
|
instrumented_fns[k] = new_fn
|
|
@@ -119,9 +119,8 @@ def call_instrumented(f, instance, args, kwargs):
|
|
|
119
119
|
def instrument(filterable):
|
|
120
120
|
"""return an instrumented function"""
|
|
121
121
|
logger.debug("hooking %s", filterable.fqname)
|
|
122
|
-
fn = filterable.obj
|
|
123
122
|
|
|
124
|
-
make_call_event = event.CallEvent.make(
|
|
123
|
+
make_call_event = event.CallEvent.make(filterable)
|
|
125
124
|
params = CallEvent.make_params(filterable)
|
|
126
125
|
|
|
127
126
|
# django depends on being able to find the cache_clear attribute
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
5
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
6
|
+
|
|
1
7
|
# If the SECRET_KEY isn't defined we get the misleading error message
|
|
2
8
|
# CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
|
|
3
9
|
SECRET_KEY = "3*+d^_kjnr2gz)4q2m(&&^%$p4fj5dk3%lz4pl3g4m-%6!gf&)"
|
|
@@ -10,3 +16,10 @@ ROOT_URLCONF = "djangoapp.urls"
|
|
|
10
16
|
|
|
11
17
|
# Turn off deprecation warning
|
|
12
18
|
USE_TZ = True
|
|
19
|
+
|
|
20
|
+
DATABASES = {
|
|
21
|
+
"default": {
|
|
22
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
23
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -114,55 +114,6 @@ class ExampleClass(Super, ClassMethodMixin):
|
|
|
114
114
|
def return_self(self):
|
|
115
115
|
return self
|
|
116
116
|
|
|
117
|
-
def __init__(self):
|
|
118
|
-
self._read_only = "read only"
|
|
119
|
-
self._fully_accessible = "fully accessible"
|
|
120
|
-
self._undecorated = "undecorated"
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def read_only(self):
|
|
124
|
-
"""Read-only"""
|
|
125
|
-
return self._read_only
|
|
126
|
-
|
|
127
|
-
@property
|
|
128
|
-
def fully_accessible(self):
|
|
129
|
-
"""Fully-accessible"""
|
|
130
|
-
return self._fully_accessible
|
|
131
|
-
|
|
132
|
-
@fully_accessible.setter
|
|
133
|
-
def fully_accessible(self, v):
|
|
134
|
-
self._fully_accessible = v
|
|
135
|
-
|
|
136
|
-
@fully_accessible.deleter
|
|
137
|
-
def fully_accessible(self):
|
|
138
|
-
del self._fully_accessible
|
|
139
|
-
|
|
140
|
-
def get_undecorated(self):
|
|
141
|
-
return self._undecorated
|
|
142
|
-
|
|
143
|
-
def set_undecorated(self, value):
|
|
144
|
-
self._undecorated = value
|
|
145
|
-
|
|
146
|
-
def delete_undecorated(self):
|
|
147
|
-
del self._undecorated
|
|
148
|
-
|
|
149
|
-
undecorated_property = property(get_undecorated, set_undecorated, delete_undecorated)
|
|
150
|
-
|
|
151
|
-
def set_write_only(self, v):
|
|
152
|
-
self._write_only = v
|
|
153
|
-
|
|
154
|
-
def del_write_only(self):
|
|
155
|
-
del self._write_only
|
|
156
|
-
|
|
157
|
-
write_only = property(None, set_write_only, del_write_only, "Write-only")
|
|
158
|
-
|
|
159
|
-
def raise_base_exception(self) -> NoReturn:
|
|
160
|
-
raise BaseException("not derived from Exception") # pylint: disable=broad-exception-raised
|
|
161
117
|
|
|
162
118
|
def modfunc():
|
|
163
119
|
return "Hello world!"
|
|
164
|
-
|
|
165
|
-
if __name__ == "__main__":
|
|
166
|
-
ec = ExampleClass()
|
|
167
|
-
ec.fully_accessible = "updated"
|
|
168
|
-
print(ec.fully_accessible)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from functools import cached_property, partial
|
|
2
|
+
import operator
|
|
3
|
+
from typing import NoReturn
|
|
4
|
+
|
|
5
|
+
def free_read_only(self):
|
|
6
|
+
return self._read_only
|
|
7
|
+
|
|
8
|
+
def free_func():
|
|
9
|
+
return "hello world"
|
|
10
|
+
class PropertiesClass:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self._read_only = "read only"
|
|
13
|
+
self._fully_accessible = "fully accessible"
|
|
14
|
+
self._undecorated = "undecorated"
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def read_only(self):
|
|
18
|
+
"""Read-only"""
|
|
19
|
+
return self._read_only
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def fully_accessible(self):
|
|
23
|
+
"""Fully-accessible"""
|
|
24
|
+
return self._fully_accessible
|
|
25
|
+
|
|
26
|
+
@fully_accessible.setter
|
|
27
|
+
def fully_accessible(self, v):
|
|
28
|
+
self._fully_accessible = v
|
|
29
|
+
|
|
30
|
+
@fully_accessible.deleter
|
|
31
|
+
def fully_accessible(self):
|
|
32
|
+
del self._fully_accessible
|
|
33
|
+
|
|
34
|
+
def get_undecorated(self):
|
|
35
|
+
return self._undecorated
|
|
36
|
+
|
|
37
|
+
def set_undecorated(self, value):
|
|
38
|
+
self._undecorated = value
|
|
39
|
+
|
|
40
|
+
def delete_undecorated(self):
|
|
41
|
+
del self._undecorated
|
|
42
|
+
|
|
43
|
+
undecorated_property = property(get_undecorated, set_undecorated, delete_undecorated)
|
|
44
|
+
|
|
45
|
+
def set_write_only(self, v):
|
|
46
|
+
self._write_only = v
|
|
47
|
+
|
|
48
|
+
def del_write_only(self):
|
|
49
|
+
del self._write_only
|
|
50
|
+
|
|
51
|
+
write_only = property(None, set_write_only, del_write_only, "Write-only")
|
|
52
|
+
|
|
53
|
+
def raise_base_exception(self) -> NoReturn:
|
|
54
|
+
raise BaseException("not derived from Exception") # pylint: disable=broad-exception-raised
|
|
55
|
+
|
|
56
|
+
@cached_property
|
|
57
|
+
def cached_read_only(self):
|
|
58
|
+
return self._read_only
|
|
59
|
+
|
|
60
|
+
operator_read_only = property(operator.attrgetter("cached_read_only"))
|
|
61
|
+
|
|
62
|
+
tastes = {"bacon": "yum"}
|
|
63
|
+
|
|
64
|
+
def __getitem__(self, key):
|
|
65
|
+
return self.tastes[key]
|
|
66
|
+
|
|
67
|
+
taste = property(operator.itemgetter("bacon"))
|
|
68
|
+
|
|
69
|
+
free_read_only_prop = property(free_read_only)
|
|
70
|
+
|
|
71
|
+
static_partial_method = staticmethod(partial(free_func))
|
|
@@ -198,7 +198,7 @@ class TestDjangoApp:
|
|
|
198
198
|
# To really check middleware reset, the tests must run in order,
|
|
199
199
|
# so disable randomly.
|
|
200
200
|
result = pytester.runpytest("-svv", "-p", "no:randomly")
|
|
201
|
-
result.assert_outcomes(passed=
|
|
201
|
+
result.assert_outcomes(passed=6, failed=0, errors=0)
|
|
202
202
|
# Look for the http_server_request event in test_app's appmap. If
|
|
203
203
|
# middleware reset is broken, it won't be there.
|
|
204
204
|
appmap_file = pytester.path / "tmp" / "appmap" / "pytest" / "test_request.appmap.json"
|
|
@@ -212,7 +212,7 @@ class TestDjangoApp:
|
|
|
212
212
|
def test_disabled(self, pytester, monkeypatch):
|
|
213
213
|
monkeypatch.setenv("_APPMAP", "false")
|
|
214
214
|
result = pytester.runpytest("-svv", "-p", "no:randomly", "-k", "test_request")
|
|
215
|
-
result.assert_outcomes(passed=
|
|
215
|
+
result.assert_outcomes(passed=3, failed=0, errors=0)
|
|
216
216
|
assert not (pytester.path / "tmp").exists()
|
|
217
217
|
|
|
218
218
|
def test_disabled_for_process(self, pytester, monkeypatch):
|
|
@@ -222,7 +222,7 @@ class TestDjangoApp:
|
|
|
222
222
|
|
|
223
223
|
# There are two tests for remote recording. They should both fail,
|
|
224
224
|
# because process recording should disable remote recording.
|
|
225
|
-
result.assert_outcomes(passed=
|
|
225
|
+
result.assert_outcomes(passed=4, failed=2, errors=0)
|
|
226
226
|
|
|
227
227
|
assert (pytester.path / "tmp" / "appmap" / "process").exists()
|
|
228
228
|
assert not (pytester.path / "tmp" / "appmap" / "requests").exists()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Tests for methods decorated with @property"""
|
|
2
|
+
|
|
3
|
+
# pyright: reportMissingImports=false
|
|
4
|
+
# pylint: disable=import-error,import-outside-toplevel
|
|
5
|
+
import pytest
|
|
6
|
+
from _appmap.test.helpers import DictIncluding
|
|
7
|
+
|
|
8
|
+
pytestmark = pytest.mark.appmap_enabled
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def setup(with_data_dir): # pylint: disable=unused-argument
|
|
12
|
+
# with_data_dir sets up sys.path so properties_class can be imported
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_getter_instrumented(events):
|
|
17
|
+
from properties_class import PropertiesClass
|
|
18
|
+
|
|
19
|
+
ec = PropertiesClass()
|
|
20
|
+
|
|
21
|
+
actual = PropertiesClass.read_only.__doc__
|
|
22
|
+
assert actual == "Read-only"
|
|
23
|
+
|
|
24
|
+
assert ec.read_only == "read only"
|
|
25
|
+
|
|
26
|
+
with pytest.raises(AttributeError, match=r".*(has no setter|can't set attribute).*"):
|
|
27
|
+
ec.read_only = "not allowed"
|
|
28
|
+
|
|
29
|
+
with pytest.raises(AttributeError, match=r".*(has no deleter|can't delete attribute).*"):
|
|
30
|
+
del ec.read_only
|
|
31
|
+
|
|
32
|
+
assert len(events) == 2
|
|
33
|
+
assert events[0].to_dict() == DictIncluding({
|
|
34
|
+
"event": "call",
|
|
35
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
36
|
+
"method_id": "read_only (get)",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_accessible_instrumented(events):
|
|
41
|
+
from properties_class import PropertiesClass
|
|
42
|
+
|
|
43
|
+
ec = PropertiesClass()
|
|
44
|
+
assert PropertiesClass.fully_accessible.__doc__ == "Fully-accessible"
|
|
45
|
+
|
|
46
|
+
assert ec.fully_accessible == "fully accessible"
|
|
47
|
+
|
|
48
|
+
ec.fully_accessible = "updated"
|
|
49
|
+
# Check the value of the attribute directly, to avoid extra events
|
|
50
|
+
assert ec._fully_accessible == "updated" # pylint: disable=protected-access
|
|
51
|
+
|
|
52
|
+
del ec.fully_accessible
|
|
53
|
+
|
|
54
|
+
assert len(events) == 6
|
|
55
|
+
assert events[0].to_dict() == DictIncluding({
|
|
56
|
+
"event": "call",
|
|
57
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
58
|
+
"method_id": "fully_accessible (get)",
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
assert events[2].to_dict() == DictIncluding({
|
|
62
|
+
"event": "call",
|
|
63
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
64
|
+
"method_id": "fully_accessible (set)",
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
assert events[4].to_dict() == DictIncluding({
|
|
68
|
+
"event": "call",
|
|
69
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
70
|
+
"method_id": "fully_accessible (del)",
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_writable_instrumented(events):
|
|
75
|
+
from properties_class import PropertiesClass
|
|
76
|
+
|
|
77
|
+
ec = PropertiesClass()
|
|
78
|
+
assert PropertiesClass.write_only.__doc__ == "Write-only"
|
|
79
|
+
|
|
80
|
+
with pytest.raises(AttributeError, match=r".*(has no getter|unreadable attribute).*"):
|
|
81
|
+
_ = ec.write_only
|
|
82
|
+
|
|
83
|
+
ec.write_only = "updated example"
|
|
84
|
+
|
|
85
|
+
assert len(events) == 2
|
|
86
|
+
assert events[0].to_dict() == DictIncluding({
|
|
87
|
+
"event": "call",
|
|
88
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
89
|
+
"method_id": "set_write_only (set)",
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_operator_attrgetter(events):
|
|
94
|
+
from properties_class import PropertiesClass
|
|
95
|
+
|
|
96
|
+
ec = PropertiesClass()
|
|
97
|
+
|
|
98
|
+
assert ec.operator_read_only == "read only"
|
|
99
|
+
|
|
100
|
+
with pytest.raises(AttributeError, match=r".*(has no setter|can't set attribute).*"):
|
|
101
|
+
ec.operator_read_only = "not allowed"
|
|
102
|
+
|
|
103
|
+
with pytest.raises(AttributeError, match=r".*(has no deleter|can't delete attribute).*"):
|
|
104
|
+
del ec.operator_read_only
|
|
105
|
+
|
|
106
|
+
assert len(events) == 2
|
|
107
|
+
assert events[0].to_dict() == DictIncluding({
|
|
108
|
+
"event": "call",
|
|
109
|
+
"defined_class": "properties_class.PropertiesClass",
|
|
110
|
+
"method_id": "operator_read_only (get)",
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
def test_operator_itemgetter(events):
|
|
114
|
+
from properties_class import PropertiesClass
|
|
115
|
+
|
|
116
|
+
ec = PropertiesClass()
|
|
117
|
+
assert ec.taste == "yum"
|
|
118
|
+
assert len(events) == 2
|
|
119
|
+
assert events[0].to_dict() == DictIncluding({
|
|
120
|
+
"event": "call",
|
|
121
|
+
# operator.itemgetter.__module__ isn't available before 3.10
|
|
122
|
+
# "defined_class": "operator",
|
|
123
|
+
"method_id": "itemgetter (get)",
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_free_function(events):
|
|
128
|
+
from properties_class import PropertiesClass
|
|
129
|
+
|
|
130
|
+
ec = PropertiesClass()
|
|
131
|
+
assert ec.free_read_only_prop == "read only"
|
|
132
|
+
assert len(events) == 2
|
|
133
|
+
assert events[0].to_dict() == DictIncluding({
|
|
134
|
+
"event": "call",
|
|
135
|
+
"defined_class": "properties_class",
|
|
136
|
+
"method_id": "free_read_only (get)",
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@pytest.mark.xfail(
|
|
141
|
+
raises=AssertionError,
|
|
142
|
+
reason="needs fix for https://github.com/getappmap/appmap-python/issues/365",
|
|
143
|
+
)
|
|
144
|
+
def test_functools_partial(events):
|
|
145
|
+
from properties_class import PropertiesClass
|
|
146
|
+
|
|
147
|
+
PropertiesClass.static_partial_method()
|
|
148
|
+
assert len(events) > 0
|
|
@@ -9,6 +9,7 @@ from threading import Thread
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
11
|
import appmap
|
|
12
|
+
from _appmap.configuration import Config
|
|
12
13
|
from _appmap.event import Event
|
|
13
14
|
from _appmap.recorder import Recorder, ThreadRecorder
|
|
14
15
|
from _appmap.wrapt import FunctionWrapper
|
|
@@ -16,6 +17,17 @@ from _appmap.wrapt import FunctionWrapper
|
|
|
16
17
|
from .normalize import normalize_appmap, remove_line_numbers
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def _call_modfunc(q):
|
|
21
|
+
r = appmap.Recording()
|
|
22
|
+
with r:
|
|
23
|
+
f = q.get()
|
|
24
|
+
f()
|
|
25
|
+
events = r.events
|
|
26
|
+
assert len(events) == 2
|
|
27
|
+
assert events[0].event == "call"
|
|
28
|
+
assert events[0].method_id == "modfunc"
|
|
29
|
+
|
|
30
|
+
|
|
19
31
|
@pytest.mark.appmap_enabled
|
|
20
32
|
@pytest.mark.usefixtures("with_data_dir")
|
|
21
33
|
class TestRecordingWhenEnabled:
|
|
@@ -47,11 +59,12 @@ class TestRecordingWhenEnabled:
|
|
|
47
59
|
), f"expected path {expected_path}"
|
|
48
60
|
|
|
49
61
|
def test_recording_clears(self):
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
# pylint: disable=import-error
|
|
63
|
+
from example_class import ExampleClass # pyright: ignore[reportMissingImports]
|
|
64
|
+
# pylint: enable=import-error
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
rec = appmap.Recording()
|
|
67
|
+
with rec:
|
|
55
68
|
ExampleClass.static_method()
|
|
56
69
|
|
|
57
70
|
# fresh recording shouldn't contain previous traces
|
|
@@ -68,9 +81,9 @@ class TestRecordingWhenEnabled:
|
|
|
68
81
|
assert rec.events[2].method_id == "instance_method"
|
|
69
82
|
|
|
70
83
|
def test_recording_shallow(self):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
# pylint: disable=import-error
|
|
85
|
+
from example_class import ExampleClass # pyright: ignore[reportMissingImports]
|
|
86
|
+
# pylint: enable=import-error
|
|
74
87
|
|
|
75
88
|
rec = appmap.Recording()
|
|
76
89
|
with rec:
|
|
@@ -82,9 +95,9 @@ class TestRecordingWhenEnabled:
|
|
|
82
95
|
assert len(rec.events) == 8
|
|
83
96
|
|
|
84
97
|
def test_recording_wrapped(self):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
# pylint: disable=import-error
|
|
99
|
+
from example_class import ExampleClass # pyright: ignore[reportMissingImports]
|
|
100
|
+
# pylint: enable=import-error
|
|
88
101
|
|
|
89
102
|
rec = appmap.Recording()
|
|
90
103
|
with rec:
|
|
@@ -117,21 +130,23 @@ class TestRecordingWhenEnabled:
|
|
|
117
130
|
f1 = deepcopy(modfunc)
|
|
118
131
|
f1()
|
|
119
132
|
|
|
120
|
-
def test_can_pickle(self):
|
|
121
|
-
|
|
133
|
+
def test_can_pickle(self, monkeypatch):
|
|
134
|
+
# Make sure subprocesses see whatever config is set for us.
|
|
135
|
+
monkeypatch.setenv("APPMAP_CONFIG", str(Config.current._file)) # pylint: disable=protected-access
|
|
122
136
|
|
|
123
|
-
from
|
|
124
|
-
modfunc,
|
|
125
|
-
)
|
|
137
|
+
from multiprocessing import Process, Queue
|
|
126
138
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
# pylint: disable=import-error
|
|
140
|
+
from example_class import modfunc # pyright: ignore[reportMissingImports]
|
|
141
|
+
# pylint: enable=import-error
|
|
142
|
+
|
|
143
|
+
assert isinstance(modfunc, FunctionWrapper), "modfunc isn't instrumented?"
|
|
144
|
+
|
|
145
|
+
q = Queue()
|
|
146
|
+
q.put(modfunc)
|
|
147
|
+
p = Process(target=_call_modfunc, args=(q,))
|
|
148
|
+
p.start()
|
|
149
|
+
p.join()
|
|
135
150
|
|
|
136
151
|
|
|
137
152
|
@pytest.mark.appmap_enabled
|
|
@@ -122,6 +122,20 @@ class TestPytestRunnerPytest(_TestTestRunner):
|
|
|
122
122
|
verify_expected_appmap(testdir, f"-numpy{numpy_version.major}-no-test-cases")
|
|
123
123
|
verify_expected_metadata(testdir)
|
|
124
124
|
|
|
125
|
+
@pytest.mark.example_dir("django")
|
|
126
|
+
def test_pytest_django(testdir):
|
|
127
|
+
result = testdir.runpytest("-svv", "-k", "test_request_test")
|
|
128
|
+
result.assert_outcomes(passed=1)
|
|
129
|
+
# django.test.TestCase is a subclass of unittest.TestCase, so recorder type is unittest
|
|
130
|
+
assert (
|
|
131
|
+
testdir.path
|
|
132
|
+
/ "tmp"
|
|
133
|
+
/ "appmap"
|
|
134
|
+
/ "unittest"
|
|
135
|
+
/ "test_test_request_TestRequest_test_request_test.appmap.json"
|
|
136
|
+
).exists()
|
|
137
|
+
assert not (testdir.path / "tmp" / "appmap" / "requests").exists()
|
|
138
|
+
|
|
125
139
|
|
|
126
140
|
@pytest.mark.example_dir("trial")
|
|
127
141
|
class TestPytestRunnerTrial(_TestTestRunner):
|
|
@@ -8,7 +8,6 @@ from contextlib import contextmanager
|
|
|
8
8
|
from contextvars import ContextVar
|
|
9
9
|
from enum import Enum, IntFlag, auto
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Callable
|
|
12
11
|
|
|
13
12
|
from .env import Env
|
|
14
13
|
|
|
@@ -79,10 +78,8 @@ class FqFnName:
|
|
|
79
78
|
FqFnName makes it easy to reference the parts of the fully-qualified name of a callable.
|
|
80
79
|
"""
|
|
81
80
|
|
|
82
|
-
def __init__(self,
|
|
83
|
-
|
|
84
|
-
self._modname = fn.__module__
|
|
85
|
-
qualname = fn.__qualname__
|
|
81
|
+
def __init__(self, modname, qualname):
|
|
82
|
+
self._modname = modname
|
|
86
83
|
if "." in qualname:
|
|
87
84
|
self._scope = Scope.CLASS
|
|
88
85
|
self._class_name, self._fn_name = qualname.rsplit(".", 1)
|
|
@@ -112,8 +109,6 @@ class FqFnName:
|
|
|
112
109
|
def fn_name(self):
|
|
113
110
|
return self._fn_name
|
|
114
111
|
|
|
115
|
-
FqFnName(fqname)
|
|
116
|
-
|
|
117
112
|
def root_relative_path(path):
|
|
118
113
|
"""Returns the path relative to the current root_dir.
|
|
119
114
|
|
|
@@ -31,7 +31,7 @@ def _add_api_route(wrapped, _, args, kwargs):
|
|
|
31
31
|
|
|
32
32
|
fn = args[1]
|
|
33
33
|
|
|
34
|
-
fqn = utils.FqFnName(fn)
|
|
34
|
+
fqn = utils.FqFnName(fn.__module__, fn.__qualname__)
|
|
35
35
|
scope = Filterable(fqn.scope, fqn.fqclass, None)
|
|
36
36
|
|
|
37
37
|
filterable_fn = FilterableFn(scope, fn.__name__, fn, fn)
|