| @ -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 | |||