{"id":9209,"library":"pyccolo","title":"Pyccolo: Declarative Instrumentation for Python","description":"Pyccolo (pronounced like \"piccolo\") is a library for declarative instrumentation in Python, allowing users to specify *what* instrumentation to perform rather than *how* to implement it. It aims for ergonomics, composability, and portability across various Python versions. It achieves this by embedding instrumentation at the source code level. The library is actively maintained, with the current version being 0.0.85, and supports Python versions from 3.6 up to 3.14 (since v0.0.73).","status":"active","version":"0.0.85","language":"en","source_language":"en","source_url":"https://github.com/smacke/pyccolo","tags":["instrumentation","dynamic analysis","AST transformation","tracing","metaprogramming"],"install":[{"cmd":"pip install pyccolo","lang":"bash","label":"Install Pyccolo"}],"dependencies":[],"imports":[{"note":"Commonly imported as `pyc.BaseTracer` after `import pyccolo as pyc`.","symbol":"BaseTracer","correct":"from pyccolo import BaseTracer"},{"note":"Commonly imported as `pyc.before_stmt` for event handlers.","symbol":"before_stmt","correct":"from pyccolo import before_stmt"},{"note":"Used for explicitly overriding values with None in handlers.","symbol":"Null","correct":"from pyccolo import Null"}],"quickstart":{"code":"import pyccolo as pyc\n\nclass MyTracer(pyc.BaseTracer):\n    def should_instrument_file(self, filename: str) -> bool:\n        # Only instrument files ending with 'my_script.py'\n        # For broader instrumentation, adjust this logic.\n        return 'my_script.py' in filename\n\n    @pyc.before_stmt\n    def on_before_statement(self, ret, node, frame, event):\n        print(f\"Executing statement: {node.lineno}: {node.__class__.__name__}\")\n\n# Example usage with a dummy script content\nscript_content = \"\"\"\nprint(\"Hello from my_script.py\")\nx = 1 + 2\nif x == 3:\n    print(\"x is 3\")\n\"\"\"\n\n# To simulate a file, we can use exec with a custom globals dict\n# and then trace its execution within a temporary module scope.\nimport types\nimport sys\n\n# Create a dummy module to hold our script content, so should_instrument_file can identify it\nmy_script_module = types.ModuleType('my_script')\nmy_script_module.__file__ = '<string>my_script.py'\nsys.modules['my_script'] = my_script_module\n\n# Execute the script content within the dummy module's namespace\nwith MyTracer():\n    exec(script_content, my_script_module.__dict__)\n\n# Clean up the dummy module\ndel sys.modules['my_script']","lang":"python","description":"This quickstart demonstrates how to create a custom tracer that logs statements before execution. It highlights the use of `BaseTracer` and the `should_instrument_file` method to control which files are instrumented, as well as a `before_stmt` event handler."},"warnings":[{"fix":"Subclass `pyccolo.BaseTracer` and override the `should_instrument_file(self, filename: str) -> bool` method to return `True` for the desired module filenames.","message":"Pyccolo's instrumentation for imported modules is opt-in, not automatic. If you expect a module imported within a tracing context to be instrumented, you must explicitly enable it.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Use `return pyccolo.Null` if you want to set the overridden value to `None`. Otherwise, `return None` will cause the original expression's value to be used.","message":"When using handlers that can override expression return values (e.g., `before_attribute_load`), returning `None` means 'no override'. If you intend to explicitly override a value with `None`, you must return `pyccolo.Null`.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure your project is running on Python 3.8 or newer when using syntax augmentation. Also, activate the correct tracer (e.g., `pyccolo.examples.OptionalChainer`) that implements the desired syntax transformation.","message":"Advanced syntax augmentation features, such as optional chaining (`?.`), are only supported on Python 3.8 and newer. Using them on older Python versions or without the specific tracer enabled will result in a `SyntaxError`.","severity":"gotcha","affected_versions":"<3.8"},{"fix":"Pyccolo aims to execute both its handlers and any active `sys.settrace` functions. If conflicts arise, simplify your tracing setup to isolate the issue, and consult Pyccolo's documentation or examples (e.g., how it co-exists with `coverage.py`).","message":"While Pyccolo is designed to be composable with existing `sys.settrace` functions, complex interactions can still occur. If you observe unexpected behavior with other tracing tools, it might be due to subtle conflicts.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Run `pip install pyccolo` in your terminal to install the library.","cause":"The `pyccolo` library has not been installed or is not accessible in the current Python environment.","error":"ModuleNotFoundError: No module named 'pyccolo'"},{"fix":"Ensure your custom `BaseTracer` implementation's `should_instrument_file` method correctly identifies and allows the target file(s) to be instrumented. Remember that instrumentation is opt-in for imports.","cause":"An imported module or file was expected to be instrumented by a `pyccolo` tracer, but its code was executed without the tracer's handlers being applied.","error":"TypeError: 'NoneType' object is not callable (or similar error related to uninstrumented code)"},{"fix":"Verify that your Python interpreter is version 3.8 or higher. If the syntax is a `pyccolo` augmentation, ensure the corresponding tracer (e.g., `pyccolo.examples.OptionalChainer`) is activated.","cause":"Attempting to use new Python syntax features (like the optional chaining `?.`) that are either not supported by your Python version or require a specific `pyccolo` syntax augmentation tracer that isn't active.","error":"SyntaxError: invalid syntax (when using optional chaining '?.')"},{"fix":"If the intention is to explicitly set the expression's value to `None` from a handler, return `pyccolo.Null` instead of `None`.","cause":"A handler was intended to override an expression's return value with `None`, but it implicitly returned `None` causing the original value to be used instead.","error":"Unexpected expression value after handler execution (handler appeared to do nothing)"}]}