appmap 3.0.0__tar.gz → 4.0.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-3.0.0 → appmap-4.0.0}/PKG-INFO +16 -8
- {appmap-3.0.0 → appmap-4.0.0}/README.md +1 -1
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/__init__.py +1 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/configuration.py +12 -7
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/env.py +4 -1
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/event.py +20 -13
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/importer.py +31 -21
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/py_version_check.py +1 -1
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/params.py +7 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_command.py +3 -3
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_params.py +158 -4
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_properties.py +1 -2
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_runner.py +31 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/command/runner.py +18 -25
- {appmap-3.0.0 → appmap-4.0.0}/pyproject.toml +13 -4
- {appmap-3.0.0 → appmap-4.0.0}/.gitignore +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/LICENSE +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/django.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/fastapi.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/flask.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/generation.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/instrument.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/labels.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/metadata.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/noappmap.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/recorder.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/recording.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/remote_recording.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/singleton.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/appmap_test_base.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/bin/server_runner +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/conftest.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-all-paths-malformed.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-broken.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-class.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-empty-path.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-exclude-fn.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-func.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-malformed-path.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap-no-pyyaml.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap_testing/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/appmap_testing/django_simplelazyobject.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config/src/package/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config/test/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config/test/foo.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-exclude/.hide/hidden_mod/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-exclude/node_modules/node_mod/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-exclude/src/package/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-exclude/test/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-exclude/venv/venv_mod/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-up/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-up/project/p1/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/config-up/project/p2/sub1/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/djangoapp.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/hello_world.html +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/middleware.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/settings.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/settings_dev.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/djangoapp/urls.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/manage.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/pytest.ini +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/test/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/test/test_app.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/test/test_request.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/django/test/test_unittest_setup.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/example_class.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/expected.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/fastapi/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/fastapi/fastapiapp/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/fastapi/fastapiapp/main.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/fastapi/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/fastapi/test_app.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask/flaskapp.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask/templates/test.html +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask/test_app.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/flaskapp.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/flask-instrumented/test_app.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/package1/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/package1/package2/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/package1/package2/__main__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/package1/package2/mod1.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/properties_class.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/appmap-no-test-cases.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy1-no-test-cases.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy1.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy2-no-test-cases.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/pytest-numpy2.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_errored.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_failed.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_xfailed.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/expected/status_xsucceeded.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/simple.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/tests/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/tests/test_noappmap.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest/tests/test_simple.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/pytest-instrumented/test_instrumented.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/remote.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/.gitignore +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/appmap-no-test-cases.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/expected/pytest-no-test-cases.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/expected/pytest.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/test/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/trial/test/test_deferred.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/appmap-no-test-cases.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/appmap.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/pytest.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_errored.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_failed.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_xfailed.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/status_xsucceeded.metadata.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/unittest-no-test-cases.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/expected/unittest.appmap.json +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/init/sitecustomize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/simple/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/simple/test_noappmap.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/data/unittest/simple/test_simple.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/helpers.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/normalize.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_configuration.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_describe_value.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_django.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_django_simplelazyobject.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_env.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_events.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_fastapi.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_flask.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_generation.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_http.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_importer.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_labels.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_metadata.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_recording.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_sqlalchemy.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_test_frameworks.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/test_util.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/test/web_framework.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/testing_framework.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/trace_logger.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/unittest.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/utils.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/web_framework.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/LICENSE +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/arguments.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/decorators.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/importer.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/_appmap/wrapt/wrappers.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/command/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/command/appmap_agent_init.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/command/appmap_agent_status.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/command/appmap_agent_validate.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/django.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/fastapi.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/flask.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/http.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/crypto.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/django.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/flask.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/formats.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/http.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/jobs.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/jwt.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/logger.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/labeling/random.yml +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/pytest.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/sqlalchemy.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/unittest.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap/uvicorn.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/appmap.pth +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/LICENSE +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/__init__.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/arguments.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/decorators.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/importer.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/_appmap/wrapt/wrappers.py +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/patches/wrapt.patch +0 -0
- {appmap-3.0.0 → appmap-4.0.0}/vendor/vendor.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: appmap
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Create AppMap files by recording a Python application.
|
|
5
5
|
Project-URL: Homepage, https://github.com/applandinc/appmap-python
|
|
6
6
|
Author-email: Alan Potter <alan@app.land>, Viraj Kanwade <viraj.kanwade@forgeahead.io>, Rafał Rzepecki <rafal@app.land>
|
|
@@ -9,14 +9,24 @@ License-File: LICENSE
|
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Framework :: Django
|
|
11
11
|
Classifier: Framework :: Django :: 3.2
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Framework :: Django :: 6.0
|
|
12
15
|
Classifier: Framework :: Flask
|
|
13
16
|
Classifier: Framework :: Pytest
|
|
14
17
|
Classifier: Intended Audience :: Developers
|
|
15
18
|
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
26
|
Classifier: Topic :: Software Development
|
|
17
27
|
Classifier: Topic :: Software Development :: Debuggers
|
|
18
28
|
Classifier: Topic :: Software Development :: Documentation
|
|
19
|
-
Requires-Python: >=3.
|
|
29
|
+
Requires-Python: >=3.11
|
|
20
30
|
Requires-Dist: importlib-resources>=5.4.0
|
|
21
31
|
Requires-Dist: inflection>=0.3.0
|
|
22
32
|
Requires-Dist: packaging>=19.0
|
|
@@ -33,8 +43,7 @@ Requires-Dist: httpretty<2.0,>=1.0.5; extra == 'dev'
|
|
|
33
43
|
Requires-Dist: httpx<1.0,>=0.27.0; extra == 'dev'
|
|
34
44
|
Requires-Dist: incremental<24.7.0; extra == 'dev'
|
|
35
45
|
Requires-Dist: isort<6.0,>=5.10.1; extra == 'dev'
|
|
36
|
-
Requires-Dist: numpy
|
|
37
|
-
Requires-Dist: numpy>=2.0; (python_version >= '3.9') and extra == 'dev'
|
|
46
|
+
Requires-Dist: numpy>=2.0; extra == 'dev'
|
|
38
47
|
Requires-Dist: pprintpp<1.0,>=0.4.0; extra == 'dev'
|
|
39
48
|
Requires-Dist: psutil<7.0,>=6.0.0; extra == 'dev'
|
|
40
49
|
Requires-Dist: pyfakefs<6.0,>=5.3.5; extra == 'dev'
|
|
@@ -66,8 +75,7 @@ Requires-Dist: flask; extra == 'test'
|
|
|
66
75
|
Requires-Dist: httpretty<2.0,>=1.0.5; extra == 'test'
|
|
67
76
|
Requires-Dist: httpx<1.0,>=0.27.0; extra == 'test'
|
|
68
77
|
Requires-Dist: incremental<24.7.0; extra == 'test'
|
|
69
|
-
Requires-Dist: numpy
|
|
70
|
-
Requires-Dist: numpy>=2.0; (python_version >= '3.9') and extra == 'test'
|
|
78
|
+
Requires-Dist: numpy>=2.0; extra == 'test'
|
|
71
79
|
Requires-Dist: psutil<7.0,>=6.0.0; extra == 'test'
|
|
72
80
|
Requires-Dist: pyfakefs<6.0,>=5.3.5; extra == 'test'
|
|
73
81
|
Requires-Dist: pytest-console-scripts<2.0,>=1.4.1; extra == 'test'
|
|
@@ -185,7 +193,7 @@ cd appmap-python
|
|
|
185
193
|
APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest
|
|
186
194
|
|
|
187
195
|
# Run tests with a specific Python version
|
|
188
|
-
APPMAP_DISPLAY_PARAMS=true uv run --python 3.
|
|
196
|
+
APPMAP_DISPLAY_PARAMS=true uv run --python 3.11 appmap-python pytest
|
|
189
197
|
|
|
190
198
|
# Run tests in parallel
|
|
191
199
|
APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -n auto
|
|
@@ -96,7 +96,7 @@ cd appmap-python
|
|
|
96
96
|
APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest
|
|
97
97
|
|
|
98
98
|
# Run tests with a specific Python version
|
|
99
|
-
APPMAP_DISPLAY_PARAMS=true uv run --python 3.
|
|
99
|
+
APPMAP_DISPLAY_PARAMS=true uv run --python 3.11 appmap-python pytest
|
|
100
100
|
|
|
101
101
|
# Run tests in parallel
|
|
102
102
|
APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -n auto
|
|
@@ -12,6 +12,7 @@ def initialize(**kwargs):
|
|
|
12
12
|
importer.initialize()
|
|
13
13
|
recorder.initialize()
|
|
14
14
|
configuration.initialize() # needs to be initialized after recorder
|
|
15
|
+
importer.install() # needs the filters registered by configuration
|
|
15
16
|
metadata.initialize()
|
|
16
17
|
web_framework.initialize()
|
|
17
18
|
recording.initialize()
|
|
@@ -185,13 +185,10 @@ class Config(metaclass=SingletonMeta):
|
|
|
185
185
|
env = Env.current
|
|
186
186
|
output_dir = env.output_dir
|
|
187
187
|
root_dir = env.root_dir
|
|
188
|
-
|
|
188
|
+
# If output_dir isn't relative to root_dir, APPMAP_OUTPUT_DIR is set, and we've already
|
|
189
|
+
# logged a warning.
|
|
190
|
+
if output_dir.is_relative_to(root_dir):
|
|
189
191
|
ret["appmap_dir"] = str(output_dir.relative_to(root_dir))
|
|
190
|
-
except ValueError:
|
|
191
|
-
# The only way we can get here is if APPMAP_OUTPUT_DIR is set, and we've already logged
|
|
192
|
-
# a warning. (Note that PurePath.is_relative_to wasn't added till 3.9, so it can't be
|
|
193
|
-
# used here.)
|
|
194
|
-
pass
|
|
195
192
|
|
|
196
193
|
return ret
|
|
197
194
|
|
|
@@ -508,5 +505,13 @@ if _startup_messages_shown is None:
|
|
|
508
505
|
logger.info("file: %s", c._file if c.file_present else "[no appmap.yml]")
|
|
509
506
|
logger.info("config: %r", c)
|
|
510
507
|
logger.debug("package_functions: %s", c.package_functions)
|
|
511
|
-
|
|
508
|
+
# Only log AppMap's own settings, never the full environment: arbitrary
|
|
509
|
+
# environment variables (API keys, credentials, tokens, etc.) must never
|
|
510
|
+
# end up in application logs.
|
|
511
|
+
appmap_env = {
|
|
512
|
+
k: v
|
|
513
|
+
for k, v in os.environ.items()
|
|
514
|
+
if k in ("APPMAP", "_APPMAP") or k.startswith(("APPMAP_", "_APPMAP_"))
|
|
515
|
+
}
|
|
516
|
+
logger.info("env: %r", appmap_env)
|
|
512
517
|
os.environ["_APPMAP_MESSAGES_SHOWN"] = "true"
|
|
@@ -168,7 +168,10 @@ class Env(metaclass=SingletonMeta):
|
|
|
168
168
|
trace_logger.install()
|
|
169
169
|
|
|
170
170
|
log_level = self.get("APPMAP_LOG_LEVEL", "warn").upper()
|
|
171
|
-
|
|
171
|
+
# No log file unless the user opts in: it can contain data (e.g.
|
|
172
|
+
# rendered parameter values) that shouldn't be written to disk or
|
|
173
|
+
# committed to source control by default.
|
|
174
|
+
disable_log = os.environ.get("APPMAP_DISABLE_LOG_FILE", "true").upper() != "FALSE"
|
|
172
175
|
log_config = self.get("APPMAP_LOG_CONFIG")
|
|
173
176
|
config_dict = {
|
|
174
177
|
"version": 1,
|
|
@@ -156,10 +156,11 @@ class Event:
|
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
class Param:
|
|
159
|
-
__slots__ = ["name", "kind", "default", "default_class"]
|
|
159
|
+
__slots__ = ["name", "kind", "positional_only", "default", "default_class"]
|
|
160
160
|
|
|
161
161
|
def __init__(self, sigp):
|
|
162
162
|
self.name = sigp.name
|
|
163
|
+
self.positional_only = sigp.kind == Parameter.POSITIONAL_ONLY
|
|
163
164
|
has_default = sigp.default is not Signature.empty
|
|
164
165
|
if sigp.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD):
|
|
165
166
|
self.kind = "opt" if has_default else "req"
|
|
@@ -275,28 +276,34 @@ class CallEvent(Event):
|
|
|
275
276
|
# we're popping the front repeatedly, lists are better at this
|
|
276
277
|
args = list(args)
|
|
277
278
|
|
|
279
|
+
# Names of keyword arguments bound to named parameters. Python
|
|
280
|
+
# doesn't include them in **kwargs, so neither do we.
|
|
281
|
+
bound = set()
|
|
278
282
|
for p in params:
|
|
279
|
-
if p.kind
|
|
280
|
-
#
|
|
281
|
-
# positional.
|
|
282
|
-
if p.name in kwargs:
|
|
283
|
+
if p.kind in ("req", "opt"):
|
|
284
|
+
# These can be either keyword or positional, unless
|
|
285
|
+
# they're positional-only.
|
|
286
|
+
if not p.positional_only and p.name in kwargs:
|
|
283
287
|
value = kwargs[p.name]
|
|
288
|
+
bound.add(p.name)
|
|
289
|
+
elif args:
|
|
290
|
+
value = args.pop(0)
|
|
291
|
+
elif p.kind == "opt":
|
|
292
|
+
value = p.default
|
|
284
293
|
else:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
else:
|
|
288
|
-
continue # required argument missing
|
|
289
|
-
elif p.kind == "keyreq":
|
|
294
|
+
continue # required argument missing
|
|
295
|
+
elif p.kind in ("keyreq", "key"):
|
|
290
296
|
if p.name in kwargs:
|
|
291
297
|
value = kwargs[p.name]
|
|
298
|
+
bound.add(p.name)
|
|
299
|
+
elif p.kind == "key":
|
|
300
|
+
value = p.default
|
|
292
301
|
else:
|
|
293
302
|
continue # required argument missing
|
|
294
|
-
elif p.kind in ("opt", "key"):
|
|
295
|
-
value = kwargs.get(p.name, p.default)
|
|
296
303
|
elif p.kind == "rest":
|
|
297
304
|
value = tuple(args)
|
|
298
305
|
elif p.kind == "keyrest":
|
|
299
|
-
value = kwargs
|
|
306
|
+
value = {k: v for k, v in kwargs.items() if k not in bound} if bound else kwargs
|
|
300
307
|
else:
|
|
301
308
|
# If all the parameter types are handled, this
|
|
302
309
|
# shouldn't ever happen...
|
|
@@ -166,7 +166,7 @@ class Importer:
|
|
|
166
166
|
@classmethod
|
|
167
167
|
def initialize(cls):
|
|
168
168
|
cls.filter_stack = []
|
|
169
|
-
cls.filter_chain =
|
|
169
|
+
cls.filter_chain = None
|
|
170
170
|
cls._skip_instrumenting = ("appmap", "_appmap")
|
|
171
171
|
cls.instrument_properties = (
|
|
172
172
|
Env.current.get("APPMAP_INSTRUMENT_PROPERTIES", "true").lower() == "true"
|
|
@@ -176,6 +176,10 @@ class Importer:
|
|
|
176
176
|
def use_filter(cls, filter_class):
|
|
177
177
|
cls.filter_stack.append(filter_class)
|
|
178
178
|
|
|
179
|
+
@classmethod
|
|
180
|
+
def build_filter_chain(cls):
|
|
181
|
+
cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
|
|
182
|
+
|
|
179
183
|
@classmethod
|
|
180
184
|
def instrument_function(cls, fn_name, filterableFn: FilterableFn, selected_functions=None):
|
|
181
185
|
# Only instrument the function if it was specifically called out for the package
|
|
@@ -194,8 +198,10 @@ class Importer:
|
|
|
194
198
|
return
|
|
195
199
|
|
|
196
200
|
logger.trace("do_import, mod %s args %s kwargs %s", mod, args, kwargs)
|
|
197
|
-
if
|
|
198
|
-
|
|
201
|
+
if cls.filter_chain is None:
|
|
202
|
+
# Only possible while _appmap is being (re)initialized, before install() has run.
|
|
203
|
+
logger.trace("no filter chain yet, not instrumenting %s", mod.__name__)
|
|
204
|
+
return
|
|
199
205
|
|
|
200
206
|
def instrument_functions(filterable, selected_functions=None):
|
|
201
207
|
# pylint: disable=too-many-locals
|
|
@@ -314,26 +320,18 @@ def wrapped_find_spec(find_spec, _, args, kwargs):
|
|
|
314
320
|
|
|
315
321
|
|
|
316
322
|
def wrap_finder_find_spec(finder):
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
# Builtin finders define find_spec as a classmethod or staticmethod, which wrapt has trouble
|
|
324
|
+
# identifying (see the comment in wrapped_find_spec above), so wrap the descriptor found in
|
|
325
|
+
# __dict__ instead.
|
|
326
|
+
find_spec = inspect.getattr_static(finder, "find_spec", None)
|
|
327
|
+
if find_spec is None:
|
|
328
|
+
logger.trace("no find_spec for finder %r", finder)
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
if isinstance(find_spec, (classmethod, staticmethod)):
|
|
326
332
|
finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
|
|
327
333
|
else:
|
|
328
|
-
find_spec =
|
|
329
|
-
if find_spec is None:
|
|
330
|
-
logger.trace("no find_spec for finder %r", finder)
|
|
331
|
-
return
|
|
332
|
-
|
|
333
|
-
if isinstance(find_spec, (classmethod, staticmethod)):
|
|
334
|
-
finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
|
|
335
|
-
else:
|
|
336
|
-
finder.find_spec = wrap_finder_function(finder.find_spec, wrapped_find_spec)
|
|
334
|
+
finder.find_spec = wrap_finder_function(finder.find_spec, wrapped_find_spec)
|
|
337
335
|
|
|
338
336
|
|
|
339
337
|
class MetapathObserver(MutableSequence):
|
|
@@ -362,6 +360,18 @@ class MetapathObserver(MutableSequence):
|
|
|
362
360
|
|
|
363
361
|
def initialize():
|
|
364
362
|
Importer.initialize()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def install():
|
|
366
|
+
"""
|
|
367
|
+
Build the filter chain and hook the finders. Must run after the filters have been registered
|
|
368
|
+
(i.e. after configuration.initialize()).
|
|
369
|
+
|
|
370
|
+
The chain is built before the finders are hooked because building it can import modules (e.g.
|
|
371
|
+
since 3.13, importlib.metadata.files imports csv lazily). If the hooks were already in place,
|
|
372
|
+
those imports would need the chain that's still being built.
|
|
373
|
+
"""
|
|
374
|
+
Importer.build_filter_chain()
|
|
365
375
|
# If we're not enabled, there's no reason to hook the finders.
|
|
366
376
|
if Env.current.enabled:
|
|
367
377
|
logger.trace("sys.metapath: %s", sys.meta_path)
|
|
@@ -22,6 +22,9 @@ class C:
|
|
|
22
22
|
def with_defaults(self, p1=1, p2=2):
|
|
23
23
|
return "%s %s" % (p1, p2)
|
|
24
24
|
|
|
25
|
+
def mixed(self, a, b=None, c="dflt", *args, d, e="e", **kwargs):
|
|
26
|
+
return "%s %s %s %s %s %s %s" % (a, b, c, args, d, e, kwargs)
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
if sys.version_info >= (3, 8):
|
|
27
30
|
exec(
|
|
@@ -33,5 +36,9 @@ C.positional_only = positional_only
|
|
|
33
36
|
def keyword_only(self, *, p1, p2):
|
|
34
37
|
return '%s %s' % (p1, p2)
|
|
35
38
|
C.keyword_only = keyword_only
|
|
39
|
+
|
|
40
|
+
def positional_only_kwargs(self, a, b="dflt", /, **kwargs):
|
|
41
|
+
return '%s %s %s' % (a, b, kwargs)
|
|
42
|
+
C.positional_only_kwargs = positional_only_kwargs
|
|
36
43
|
"""
|
|
37
44
|
)
|
|
@@ -115,14 +115,14 @@ class TestAgentValidate:
|
|
|
115
115
|
def test_python_version(self, capsys, mocker):
|
|
116
116
|
mocker.patch(
|
|
117
117
|
"_appmap.py_version_check._get_py_version",
|
|
118
|
-
return_value=(3,
|
|
118
|
+
return_value=(3, 10),
|
|
119
119
|
)
|
|
120
120
|
mocker.patch(
|
|
121
121
|
"_appmap.py_version_check._get_platform_version",
|
|
122
|
-
return_value="3.
|
|
122
|
+
return_value="3.10",
|
|
123
123
|
)
|
|
124
124
|
|
|
125
|
-
self.check_errors(capsys, 1, 1, r"Minimum Python version supported is \d
|
|
125
|
+
self.check_errors(capsys, 1, 1, r"Minimum Python version supported is \d+\.\d+, found")
|
|
126
126
|
|
|
127
127
|
def test_django_version(self, capsys, mocker):
|
|
128
128
|
mocker.patch(
|
|
@@ -266,9 +266,6 @@ class TestInstanceMethods(TestMethodBase):
|
|
|
266
266
|
},
|
|
267
267
|
)
|
|
268
268
|
|
|
269
|
-
@pytest.mark.skipif(
|
|
270
|
-
sys.version_info < (3, 8), reason="positional-only added in 3.8"
|
|
271
|
-
)
|
|
272
269
|
@pytest.mark.parametrize("params", ["positional_only"], indirect=True)
|
|
273
270
|
def test_positional_only(self, params):
|
|
274
271
|
evt = params.C().positional_only(1, 2)
|
|
@@ -282,7 +279,6 @@ class TestInstanceMethods(TestMethodBase):
|
|
|
282
279
|
evt, 1, {"class": "builtins.int", "kind": "req", "name": "p2", "value": "2"}
|
|
283
280
|
)
|
|
284
281
|
|
|
285
|
-
@pytest.mark.skipif(sys.version_info < (3, 8), reason="keyword-only added in 3.8")
|
|
286
282
|
@pytest.mark.parametrize("params", ["keyword_only"], indirect=True)
|
|
287
283
|
def test_keyword_only(self, params):
|
|
288
284
|
evt = params.C().keyword_only(p2=2, p1=1)
|
|
@@ -325,3 +321,161 @@ class TestInstanceMethods(TestMethodBase):
|
|
|
325
321
|
self.assert_parameter(
|
|
326
322
|
evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
|
|
327
323
|
)
|
|
324
|
+
|
|
325
|
+
@pytest.mark.parametrize("params", ["with_defaults"], indirect=True)
|
|
326
|
+
def test_defaults_positional(self, params):
|
|
327
|
+
evt = params.C().with_defaults(3, 4)
|
|
328
|
+
assert len(evt.parameters) == 2
|
|
329
|
+
|
|
330
|
+
self.assert_parameter(
|
|
331
|
+
evt, 0, {"class": "builtins.int", "kind": "opt", "name": "p1", "value": "3"}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
self.assert_parameter(
|
|
335
|
+
evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
@pytest.mark.parametrize("params", ["with_defaults"], indirect=True)
|
|
339
|
+
def test_defaults_positional_and_keyword(self, params):
|
|
340
|
+
evt = params.C().with_defaults(3, p2=4)
|
|
341
|
+
assert len(evt.parameters) == 2
|
|
342
|
+
|
|
343
|
+
self.assert_parameter(
|
|
344
|
+
evt, 0, {"class": "builtins.int", "kind": "opt", "name": "p1", "value": "3"}
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
self.assert_parameter(
|
|
348
|
+
evt, 1, {"class": "builtins.int", "kind": "opt", "name": "p2", "value": "4"}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class TestMixedParams(TestMethodBase):
|
|
353
|
+
"""
|
|
354
|
+
C.mixed has the signature (self, a, b=None, c="dflt", *args, d, e="e", **kwargs),
|
|
355
|
+
covering every parameter kind.
|
|
356
|
+
"""
|
|
357
|
+
|
|
358
|
+
def assert_values(self, evt, expected):
|
|
359
|
+
actual = {p["name"]: (p["kind"], p["value"]) for p in evt.parameters}
|
|
360
|
+
assert actual == expected
|
|
361
|
+
|
|
362
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
363
|
+
def test_positional_opt(self, params):
|
|
364
|
+
evt = params.C().mixed("A", "B", "C", d="D")
|
|
365
|
+
self.assert_values(
|
|
366
|
+
evt,
|
|
367
|
+
{
|
|
368
|
+
"a": ("req", "A"),
|
|
369
|
+
"b": ("opt", "B"),
|
|
370
|
+
"c": ("opt", "C"),
|
|
371
|
+
"args": ("rest", "()"),
|
|
372
|
+
"d": ("keyreq", "D"),
|
|
373
|
+
"e": ("key", "e"),
|
|
374
|
+
"kwargs": ("keyrest", "{}"),
|
|
375
|
+
},
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
379
|
+
def test_partial_positional_opt(self, params):
|
|
380
|
+
evt = params.C().mixed("A", "B", d="D")
|
|
381
|
+
self.assert_values(
|
|
382
|
+
evt,
|
|
383
|
+
{
|
|
384
|
+
"a": ("req", "A"),
|
|
385
|
+
"b": ("opt", "B"),
|
|
386
|
+
"c": ("opt", "dflt"),
|
|
387
|
+
"args": ("rest", "()"),
|
|
388
|
+
"d": ("keyreq", "D"),
|
|
389
|
+
"e": ("key", "e"),
|
|
390
|
+
"kwargs": ("keyrest", "{}"),
|
|
391
|
+
},
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
395
|
+
def test_keyword_opt(self, params):
|
|
396
|
+
evt = params.C().mixed("A", c="C", d="D")
|
|
397
|
+
self.assert_values(
|
|
398
|
+
evt,
|
|
399
|
+
{
|
|
400
|
+
"a": ("req", "A"),
|
|
401
|
+
"b": ("opt", "None"),
|
|
402
|
+
"c": ("opt", "C"),
|
|
403
|
+
"args": ("rest", "()"),
|
|
404
|
+
"d": ("keyreq", "D"),
|
|
405
|
+
"e": ("key", "e"),
|
|
406
|
+
"kwargs": ("keyrest", "{}"),
|
|
407
|
+
},
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
411
|
+
def test_rest_after_opt(self, params):
|
|
412
|
+
evt = params.C().mixed("A", "B", "C", "X", "Y", d="D")
|
|
413
|
+
self.assert_values(
|
|
414
|
+
evt,
|
|
415
|
+
{
|
|
416
|
+
"a": ("req", "A"),
|
|
417
|
+
"b": ("opt", "B"),
|
|
418
|
+
"c": ("opt", "C"),
|
|
419
|
+
"args": ("rest", "('X', 'Y')"),
|
|
420
|
+
"d": ("keyreq", "D"),
|
|
421
|
+
"e": ("key", "e"),
|
|
422
|
+
"kwargs": ("keyrest", "{}"),
|
|
423
|
+
},
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
427
|
+
def test_keyword_only(self, params):
|
|
428
|
+
evt = params.C().mixed("A", d="D", e="E", f="F")
|
|
429
|
+
self.assert_values(
|
|
430
|
+
evt,
|
|
431
|
+
{
|
|
432
|
+
"a": ("req", "A"),
|
|
433
|
+
"b": ("opt", "None"),
|
|
434
|
+
"c": ("opt", "dflt"),
|
|
435
|
+
"args": ("rest", "()"),
|
|
436
|
+
"d": ("keyreq", "D"),
|
|
437
|
+
"e": ("key", "E"),
|
|
438
|
+
"kwargs": ("keyrest", "{'f': 'F'}"),
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
@pytest.mark.parametrize("params", ["mixed"], indirect=True)
|
|
443
|
+
def test_keyword_req(self, params):
|
|
444
|
+
evt = params.C().mixed(a="A", d="D", f="F")
|
|
445
|
+
self.assert_values(
|
|
446
|
+
evt,
|
|
447
|
+
{
|
|
448
|
+
"a": ("req", "A"),
|
|
449
|
+
"b": ("opt", "None"),
|
|
450
|
+
"c": ("opt", "dflt"),
|
|
451
|
+
"args": ("rest", "()"),
|
|
452
|
+
"d": ("keyreq", "D"),
|
|
453
|
+
"e": ("key", "e"),
|
|
454
|
+
"kwargs": ("keyrest", "{'f': 'F'}"),
|
|
455
|
+
},
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
@pytest.mark.parametrize("params", ["positional_only_kwargs"], indirect=True)
|
|
459
|
+
def test_positional_only_name_in_kwargs(self, params):
|
|
460
|
+
# Keyword arguments named like positional-only parameters go to **kwargs
|
|
461
|
+
evt = params.C().positional_only_kwargs("A", "B", a="X", b="Y")
|
|
462
|
+
self.assert_values(
|
|
463
|
+
evt,
|
|
464
|
+
{
|
|
465
|
+
"a": ("req", "A"),
|
|
466
|
+
"b": ("opt", "B"),
|
|
467
|
+
"kwargs": ("keyrest", "{'a': 'X', 'b': 'Y'}"),
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
@pytest.mark.parametrize("params", ["positional_only_kwargs"], indirect=True)
|
|
472
|
+
def test_positional_only_default_name_in_kwargs(self, params):
|
|
473
|
+
evt = params.C().positional_only_kwargs("A", b="Y")
|
|
474
|
+
self.assert_values(
|
|
475
|
+
evt,
|
|
476
|
+
{
|
|
477
|
+
"a": ("req", "A"),
|
|
478
|
+
"b": ("opt", "dflt"),
|
|
479
|
+
"kwargs": ("keyrest", "{'b': 'Y'}"),
|
|
480
|
+
},
|
|
481
|
+
)
|
|
@@ -118,8 +118,7 @@ def test_operator_itemgetter(events):
|
|
|
118
118
|
assert len(events) == 2
|
|
119
119
|
assert events[0].to_dict() == DictIncluding({
|
|
120
120
|
"event": "call",
|
|
121
|
-
|
|
122
|
-
# "defined_class": "operator",
|
|
121
|
+
"defined_class": "operator",
|
|
123
122
|
"method_id": "itemgetter (get)",
|
|
124
123
|
})
|
|
125
124
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import re
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -37,6 +38,20 @@ def test_runner_multi_recording_type(script_runner, flag, expected):
|
|
|
37
38
|
assert len(re.findall("(?m)^APPMAP_RECORD_PYTEST=true$", result.stdout)) == expected
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
"flags,expected",
|
|
43
|
+
[
|
|
44
|
+
([], "true"),
|
|
45
|
+
(["--no-enable-log"], "true"),
|
|
46
|
+
(["--enable-log"], "false"),
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_runner_log_file_disabled_by_default(script_runner, flags, expected):
|
|
50
|
+
result = script_runner.run(["appmap-python", *flags, "--record", "process"])
|
|
51
|
+
assert result.returncode == 0
|
|
52
|
+
assert re.search(f"(?m)^APPMAP_DISABLE_LOG_FILE={expected}$", result.stdout) is not None
|
|
53
|
+
|
|
54
|
+
|
|
40
55
|
@pytest.mark.script_launch_mode("subprocess")
|
|
41
56
|
class TestEnv:
|
|
42
57
|
def test_appmap_present(self, script_runner):
|
|
@@ -50,3 +65,19 @@ class TestEnv:
|
|
|
50
65
|
)
|
|
51
66
|
assert result.returncode == 0
|
|
52
67
|
assert re.match(r"true", result.stdout) is not None
|
|
68
|
+
|
|
69
|
+
def test_internal_state_not_leaked_to_child(self, script_runner):
|
|
70
|
+
# appmap-python is itself instrumented at interpreter startup (via
|
|
71
|
+
# appmap.pth), which can set internal, process-scoped _APPMAP*
|
|
72
|
+
# markers using whatever it inherited, before this script has
|
|
73
|
+
# computed the environment the child command should actually run
|
|
74
|
+
# with. Simulate that by pre-setting one such marker (the
|
|
75
|
+
# once-per-process "startup messages already shown" guard) and
|
|
76
|
+
# confirm it doesn't leak into the child's environment, which would
|
|
77
|
+
# otherwise silently suppress the child's own startup logging.
|
|
78
|
+
env = {**os.environ, "_APPMAP_MESSAGES_SHOWN": "true"}
|
|
79
|
+
result = script_runner.run(
|
|
80
|
+
["appmap-python", "printenv", "_APPMAP_MESSAGES_SHOWN"], env=env
|
|
81
|
+
)
|
|
82
|
+
assert result.returncode != 0
|
|
83
|
+
assert result.stdout == ""
|
|
@@ -53,29 +53,12 @@ _parser.add_argument(
|
|
|
53
53
|
default=argparse.SUPPRESS,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
else:
|
|
64
|
-
# You can see why BooleanOptionalAction was added. This is close, though not
|
|
65
|
-
# really as good....
|
|
66
|
-
_enable_log_group = _parser.add_mutually_exclusive_group()
|
|
67
|
-
_enable_log_group.add_argument(
|
|
68
|
-
"--enable-log",
|
|
69
|
-
help="create a log file",
|
|
70
|
-
dest="enable_log",
|
|
71
|
-
action="store_true",
|
|
72
|
-
)
|
|
73
|
-
_enable_log_group.add_argument(
|
|
74
|
-
"--no-enable-log",
|
|
75
|
-
help="don't create a log file",
|
|
76
|
-
dest="enable_log",
|
|
77
|
-
action="store_false",
|
|
78
|
-
)
|
|
56
|
+
_parser.add_argument(
|
|
57
|
+
"--enable-log",
|
|
58
|
+
help="create a log file",
|
|
59
|
+
action=argparse.BooleanOptionalAction,
|
|
60
|
+
default=False,
|
|
61
|
+
)
|
|
79
62
|
|
|
80
63
|
_parser.add_argument(
|
|
81
64
|
"command",
|
|
@@ -122,7 +105,7 @@ def run():
|
|
|
122
105
|
envvars[f"APPMAP_RECORD_{disabled.upper()}"] = "false"
|
|
123
106
|
|
|
124
107
|
envvars["APPMAP_DISABLE_LOG_FILE"] = (
|
|
125
|
-
"
|
|
108
|
+
"false" if parsed_args.get("enable_log", False) else "true"
|
|
126
109
|
)
|
|
127
110
|
|
|
128
111
|
if len(cmd) == 0:
|
|
@@ -130,7 +113,17 @@ def run():
|
|
|
130
113
|
print(f"{k}={v}")
|
|
131
114
|
sys.exit(0)
|
|
132
115
|
|
|
133
|
-
|
|
116
|
+
# appmap-python is itself instrumented on interpreter startup (via
|
|
117
|
+
# appmap.pth), before this point, using whatever environment it inherited
|
|
118
|
+
# rather than the envvars computed above. That incidental self-init can
|
|
119
|
+
# set internal, process-scoped state under _APPMAP*-prefixed names (e.g.
|
|
120
|
+
# the once-per-process "startup messages already shown" guard); left in
|
|
121
|
+
# place, it would carry over into the child's environment and suppress
|
|
122
|
+
# or corrupt the child's own startup behavior. Drop all of it and let
|
|
123
|
+
# envvars below re-set whatever the child actually needs.
|
|
124
|
+
child_env = {k: v for k, v in os.environ.items() if not k.startswith("_APPMAP")}
|
|
125
|
+
child_env.update(envvars)
|
|
126
|
+
os.execvpe(cmd[0], cmd, child_env)
|
|
134
127
|
|
|
135
128
|
|
|
136
129
|
if __name__ == "__main__":
|