| @ -0,0 +1,7 @@ | |||||
| *.so | |||||
| *.py[co] | |||||
| build/ | |||||
| ext/ | |||||
| dist/ | |||||
| *.egg-info | |||||
| __pycache__ | |||||
| @ -0,0 +1,12 @@ | |||||
| sysaudit | |||||
| ======== | |||||
| Backport module of [sys.audit](https://docs.python.org/3.8/library/sys.html#sys.audit) | |||||
| and [sys.addaudithook](https://docs.python.org/3.8/library/sys.html#sys.addaudithook) | |||||
| from Python 3.8. | |||||
| This module provides the audit hooking mechanisms and some helpers to help | |||||
| library developers usage of `sys.audit`. | |||||
| **Note:** This module does _not_ backport any of the built-in | |||||
| [audit events](https://docs.python.org/3.8/library/audit_events.html#audit-events). | |||||
| @ -0,0 +1,17 @@ | |||||
| from setuptools import setup | |||||
| from distutils.core import Extension | |||||
| setup( | |||||
| name="sysaudit", | |||||
| version="0.1.0", | |||||
| description="Backport module for sys.audit and sys.addaudithook from Python 3.8", | |||||
| author="Brett Langdon", | |||||
| author_email="me@brett.is", | |||||
| url="https://github.com/brettlangdon/sysaudit", | |||||
| ext_modules=[ | |||||
| Extension( | |||||
| "sysaudit.csysaudit", sources=["sysaudit/_csysaudit.c"], optional=True | |||||
| ), | |||||
| ], | |||||
| packages=["sysaudit"], | |||||
| ) | |||||
| @ -0,0 +1,4 @@ | |||||
| __all__ = ["audit", "addaudithook", "extras"] | |||||
| from .audit import audit, addaudithook | |||||
| from . import extras | |||||
| @ -0,0 +1,118 @@ | |||||
| #include "Python.h" | |||||
| struct CsysauditState { | |||||
| PyObject* hooks; | |||||
| }; | |||||
| #define csysaudit_state(o) ((struct CsysauditState*)PyModule_GetState(o)) | |||||
| static PyObject* csysaudit_audit(PyObject* self, PyObject* const *args, Py_ssize_t argc) { | |||||
| if (argc == 0) { | |||||
| PyErr_SetString(PyExc_TypeError, "audit() missing 1 required positional argument: 'event'"); | |||||
| return NULL; | |||||
| } | |||||
| PyObject* return_value = NULL; | |||||
| PyObject* event_name = NULL; | |||||
| PyTupleObject* event_args = NULL; | |||||
| PyObject* hooks = NULL; | |||||
| PyObject* hook = NULL; | |||||
| event_name = args[0]; | |||||
| if (!event_name) { | |||||
| PyErr_SetString(PyExc_TypeError, "expected str for argument 'event'"); | |||||
| goto exit; | |||||
| } | |||||
| if (!PyUnicode_Check(event_name)) { | |||||
| PyErr_Format(PyExc_TypeError, "expected str for argument 'event', not %.200s", | |||||
| Py_TYPE(event_name)->tp_name); | |||||
| goto exit; | |||||
| } | |||||
| event_args = (PyTupleObject*)PyTuple_New(argc - 1); | |||||
| if (!event_args) { | |||||
| goto exit; | |||||
| } | |||||
| PyObject** dst = event_args->ob_item; | |||||
| for (Py_ssize_t i = 1; i < argc; i++) { | |||||
| PyObject* item = args[i]; | |||||
| Py_INCREF(item); | |||||
| dst[i - 1] = item; | |||||
| } | |||||
| hooks = PyObject_GetIter(csysaudit_state(self)->hooks); | |||||
| if (!hooks) { | |||||
| goto exit; | |||||
| } | |||||
| while ((hook = PyIter_Next(hooks)) != NULL) { | |||||
| PyObject *o; | |||||
| o = PyObject_CallFunctionObjArgs(hook, event_name, event_args, NULL); | |||||
| if (!o) { | |||||
| break; | |||||
| } | |||||
| Py_DECREF(o); | |||||
| Py_CLEAR(hook); | |||||
| } | |||||
| Py_INCREF(Py_None); | |||||
| return_value = Py_None; | |||||
| exit: | |||||
| Py_XDECREF(hook); | |||||
| Py_XDECREF(hooks); | |||||
| Py_XDECREF(event_args); | |||||
| return return_value; | |||||
| }; | |||||
| static PyObject* csysaudit_addaudithook(PyObject* self, PyObject* const *args, Py_ssize_t nargs, PyObject* kwnames) { | |||||
| PyObject* hook; | |||||
| if (nargs == 0) { | |||||
| PyErr_SetString(PyExc_TypeError, "addaudithook() missing 1 required positional argument: 'hook'"); | |||||
| return NULL; | |||||
| } | |||||
| hook = args[0]; | |||||
| if (PyList_Append(csysaudit_state(self)->hooks, hook) < 0) { | |||||
| return NULL; | |||||
| } | |||||
| Py_RETURN_NONE; | |||||
| }; | |||||
| static PyMethodDef csysaudit_methods[] = { | |||||
| {"audit", (PyCFunction)(void(*)(void))csysaudit_audit, METH_FASTCALL, PyDoc_STR("") }, | |||||
| {"addaudithook", (PyCFunction)(void(*)(void))csysaudit_addaudithook, METH_FASTCALL, PyDoc_STR("") }, | |||||
| { NULL, NULL } | |||||
| }; | |||||
| PyDoc_VAR(csysaudit_doc) = PyDoc_STR(""); | |||||
| static struct PyModuleDef csysaudit = { | |||||
| PyModuleDef_HEAD_INIT, | |||||
| "sysaudit.csysaudit", | |||||
| csysaudit_doc, | |||||
| sizeof(struct CsysauditState), | |||||
| csysaudit_methods, | |||||
| NULL, | |||||
| NULL, | |||||
| NULL, | |||||
| NULL | |||||
| }; | |||||
| PyObject* PyInit_csysaudit() { | |||||
| PyObject* res = PyModule_Create(&csysaudit); | |||||
| if (!res) return NULL; | |||||
| csysaudit_state(res)->hooks = PyList_New(0); | |||||
| return res; | |||||
| }; | |||||
| @ -0,0 +1,39 @@ | |||||
| __all__ = ["audit", "addaudithook"] | |||||
| import sys | |||||
| # Python 3.8+ | |||||
| # DEV: We could check `sys.version_info >= (3, 8)`, but if auditing ever gets | |||||
| # back ported we want to take advantage of that | |||||
| if hasattr(sys, "audit") and hasattr(sys, "addaudithook"): | |||||
| audit = sys.audit | |||||
| addaudithook = sys.addaudithook | |||||
| else: | |||||
| try: | |||||
| from .csysaudit import audit, addaudithook | |||||
| except ImportError: | |||||
| _hooks = list() | |||||
| def audit(event, *args): | |||||
| global _hooks | |||||
| # Grab a copy of hooks so we don't need to lock here | |||||
| hooks = _hooks.copy() | |||||
| for hook in hooks: | |||||
| hook(event, args) | |||||
| def addaudithook(callback): | |||||
| global _hooks | |||||
| # https://docs.python.org/3.8/library/sys.html#sys.addaudithook | |||||
| # Raise an auditing event `sys.addaudithook` with no arguments. | |||||
| # If any existing hooks raise an exception derived from RuntimeError, | |||||
| # the new hook will not be added and the exception suppressed. | |||||
| # As a result, callers cannot assume that their hook has been added | |||||
| # unless they control all existing hooks. | |||||
| try: | |||||
| audit("sys.addaudithook") | |||||
| except RuntimeError: | |||||
| return | |||||
| if callback not in _hooks: | |||||
| _hooks.append(callback) | |||||
| @ -0,0 +1,94 @@ | |||||
| __all__ = ["audithook", "auditfunc", "auditmanager", "auditwsgi"] | |||||
| import contextlib | |||||
| import functools | |||||
| from .audit import audit, addaudithook | |||||
| def audithook(*events): | |||||
| def dec(func): | |||||
| def hook(event, args): | |||||
| if event in events: | |||||
| func(*args) | |||||
| addaudithook(hook) | |||||
| return func | |||||
| return dec | |||||
| def auditfunc(event_prefix): | |||||
| started_event = "{}.started".format(event_prefix) | |||||
| finished_event = "{}.finished".format(event_prefix) | |||||
| def dec(func): | |||||
| @functools.wraps(func) | |||||
| def wrapper(*args, **kwargs): | |||||
| ret = None | |||||
| exc = None | |||||
| try: | |||||
| audit(started_event, args, kwargs) | |||||
| ret = func(*args, **kwargs) | |||||
| return ret | |||||
| except Exception as e: | |||||
| exc = e | |||||
| raise | |||||
| finally: | |||||
| audit(finished_event, ret, exc) | |||||
| return wrapper | |||||
| return dec | |||||
| @contextlib.contextmanager | |||||
| def auditmanager(event_prefix, *args): | |||||
| started_event = "{}.started".format(event_prefix) | |||||
| finished_event = "{}.finished".format(event_prefix) | |||||
| audit(started_event, *args) | |||||
| exc = None | |||||
| try: | |||||
| yield | |||||
| except Exception as e: | |||||
| exc = e | |||||
| raise | |||||
| finally: | |||||
| audit(finished_event, exc) | |||||
| def auditwsgi(app, event_prefix="wsgi"): | |||||
| request_event = "{}.request.started".format(event_prefix) | |||||
| finished_event = "{}.request.finished".format(event_prefix) | |||||
| start_response_started_event = "{}.start_response.started".format(event_prefix) | |||||
| start_response_finished_event = "{}.start_response.finished".format(event_prefix) | |||||
| def wsgi(environ, start_response): | |||||
| audit(request_event, environ, start_response) | |||||
| def audit_start_response(status, headers): | |||||
| audit(start_response_started_event, status, headers) | |||||
| ret = None | |||||
| exc = None | |||||
| try: | |||||
| ret = start_response(status, headers) | |||||
| return ret | |||||
| except Exception as e: | |||||
| exc = e | |||||
| raise | |||||
| finally: | |||||
| audit(start_response_finished_event, ret, exc) | |||||
| ret = None | |||||
| exc = None | |||||
| try: | |||||
| ret = app(environ, audit_start_response) | |||||
| return ret | |||||
| except Exception as e: | |||||
| exc = e | |||||
| raise | |||||
| finally: | |||||
| audit(finished_event, ret, exc) | |||||
| return wsgi | |||||